aboutsummaryrefslogtreecommitdiff
path: root/nixpkgs/nixos/modules
diff options
context:
space:
mode:
authorKatharina Fey <kookie@spacekookie.de>2019-10-05 12:43:18 +0000
committerKatharina Fey <kookie@spacekookie.de>2019-10-05 12:44:52 +0000
commitcf85056ba64caf3267d43255ef4a1243e9c8ee3b (patch)
tree3051519e9c8275b870aac43f80af875715c9d124 /nixpkgs/nixos/modules
parent1148b1d122bc03e9a3665856c9b7bb96bd4e3994 (diff)
parent2436c27541b2f52deea3a4c1691216a02152e729 (diff)
Add 'nixpkgs/' from commit '2436c27541b2f52deea3a4c1691216a02152e729'
git-subtree-dir: nixpkgs git-subtree-mainline: 1148b1d122bc03e9a3665856c9b7bb96bd4e3994 git-subtree-split: 2436c27541b2f52deea3a4c1691216a02152e729
Diffstat (limited to 'nixpkgs/nixos/modules')
-rw-r--r--nixpkgs/nixos/modules/config/appstream.nix25
-rw-r--r--nixpkgs/nixos/modules/config/debug-info.nix45
-rw-r--r--nixpkgs/nixos/modules/config/fonts/corefonts.nix36
-rw-r--r--nixpkgs/nixos/modules/config/fonts/fontconfig-penultimate.nix292
-rw-r--r--nixpkgs/nixos/modules/config/fonts/fontconfig-ultimate.nix86
-rw-r--r--nixpkgs/nixos/modules/config/fonts/fontconfig.nix476
-rw-r--r--nixpkgs/nixos/modules/config/fonts/fontdir.nix47
-rw-r--r--nixpkgs/nixos/modules/config/fonts/fonts.nix51
-rw-r--r--nixpkgs/nixos/modules/config/fonts/ghostscript.nix32
-rw-r--r--nixpkgs/nixos/modules/config/gnu.nix46
-rw-r--r--nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix86
-rw-r--r--nixpkgs/nixos/modules/config/i18n.nix160
-rw-r--r--nixpkgs/nixos/modules/config/iproute2.nix32
-rw-r--r--nixpkgs/nixos/modules/config/krb5/default.nix367
-rw-r--r--nixpkgs/nixos/modules/config/ldap.nix293
-rw-r--r--nixpkgs/nixos/modules/config/locale.nix94
-rw-r--r--nixpkgs/nixos/modules/config/malloc.nix91
-rw-r--r--nixpkgs/nixos/modules/config/networking.nix219
-rw-r--r--nixpkgs/nixos/modules/config/no-x-libs.nix41
-rw-r--r--nixpkgs/nixos/modules/config/nsswitch.nix116
-rw-r--r--nixpkgs/nixos/modules/config/power-management.nix106
-rw-r--r--nixpkgs/nixos/modules/config/pulseaudio.nix326
-rw-r--r--nixpkgs/nixos/modules/config/qt5.nix102
-rw-r--r--nixpkgs/nixos/modules/config/resolvconf.nix149
-rw-r--r--nixpkgs/nixos/modules/config/shells-environment.nix204
-rw-r--r--nixpkgs/nixos/modules/config/swap.nix222
-rw-r--r--nixpkgs/nixos/modules/config/sysctl.nix63
-rw-r--r--nixpkgs/nixos/modules/config/system-environment.nix99
-rw-r--r--nixpkgs/nixos/modules/config/system-path.nix150
-rw-r--r--nixpkgs/nixos/modules/config/terminfo.nix33
-rw-r--r--nixpkgs/nixos/modules/config/unix-odbc-drivers.nix38
-rw-r--r--nixpkgs/nixos/modules/config/update-users-groups.pl283
-rw-r--r--nixpkgs/nixos/modules/config/users-groups.nix601
-rw-r--r--nixpkgs/nixos/modules/config/vpnc.nix41
-rw-r--r--nixpkgs/nixos/modules/config/vte.nix52
-rw-r--r--nixpkgs/nixos/modules/config/xdg/autostart.nix22
-rw-r--r--nixpkgs/nixos/modules/config/xdg/icons.nix38
-rw-r--r--nixpkgs/nixos/modules/config/xdg/menus.nix25
-rw-r--r--nixpkgs/nixos/modules/config/xdg/mime.nix36
-rw-r--r--nixpkgs/nixos/modules/config/xdg/portal.nix58
-rw-r--r--nixpkgs/nixos/modules/config/xdg/sounds.nix22
-rw-r--r--nixpkgs/nixos/modules/config/zram.nix189
-rw-r--r--nixpkgs/nixos/modules/hardware/acpilight.nix24
-rw-r--r--nixpkgs/nixos/modules/hardware/all-firmware.nix69
-rw-r--r--nixpkgs/nixos/modules/hardware/bladeRF.nix28
-rw-r--r--nixpkgs/nixos/modules/hardware/brightnessctl.nix31
-rw-r--r--nixpkgs/nixos/modules/hardware/ckb-next.nix49
-rw-r--r--nixpkgs/nixos/modules/hardware/cpu/amd-microcode.nix29
-rw-r--r--nixpkgs/nixos/modules/hardware/cpu/intel-microcode.nix29
-rw-r--r--nixpkgs/nixos/modules/hardware/device-tree.nix56
-rw-r--r--nixpkgs/nixos/modules/hardware/digitalbitbox.nix30
-rw-r--r--nixpkgs/nixos/modules/hardware/ksm.nix34
-rw-r--r--nixpkgs/nixos/modules/hardware/ledger.nix14
-rw-r--r--nixpkgs/nixos/modules/hardware/logitech.nix28
-rw-r--r--nixpkgs/nixos/modules/hardware/mcelog.nix35
-rw-r--r--nixpkgs/nixos/modules/hardware/network/b43.nix34
-rw-r--r--nixpkgs/nixos/modules/hardware/network/broadcom-43xx.nix3
-rw-r--r--nixpkgs/nixos/modules/hardware/network/intel-2200bg.nix30
-rw-r--r--nixpkgs/nixos/modules/hardware/network/smc-2632w/default.nix9
-rw-r--r--nixpkgs/nixos/modules/hardware/network/smc-2632w/firmware/cis/SMC2632W-v1.02.cis8
-rw-r--r--nixpkgs/nixos/modules/hardware/network/zydas-zd1211.nix5
-rw-r--r--nixpkgs/nixos/modules/hardware/nitrokey.nix41
-rw-r--r--nixpkgs/nixos/modules/hardware/onlykey.nix33
-rw-r--r--nixpkgs/nixos/modules/hardware/onlykey.udev4
-rw-r--r--nixpkgs/nixos/modules/hardware/opengl.nix169
-rw-r--r--nixpkgs/nixos/modules/hardware/openrazer.nix133
-rw-r--r--nixpkgs/nixos/modules/hardware/pcmcia.nix59
-rw-r--r--nixpkgs/nixos/modules/hardware/printers.nix135
-rw-r--r--nixpkgs/nixos/modules/hardware/raid/hpsa.nix61
-rw-r--r--nixpkgs/nixos/modules/hardware/sensor/iio.nix30
-rw-r--r--nixpkgs/nixos/modules/hardware/steam-hardware.nix25
-rw-r--r--nixpkgs/nixos/modules/hardware/usb-wwan.nix26
-rw-r--r--nixpkgs/nixos/modules/hardware/video/amdgpu-pro.nix68
-rw-r--r--nixpkgs/nixos/modules/hardware/video/amdgpu.nix9
-rw-r--r--nixpkgs/nixos/modules/hardware/video/ati.nix40
-rw-r--r--nixpkgs/nixos/modules/hardware/video/bumblebee.nix93
-rw-r--r--nixpkgs/nixos/modules/hardware/video/capture/mwprocapture.nix61
-rw-r--r--nixpkgs/nixos/modules/hardware/video/displaylink.nix66
-rw-r--r--nixpkgs/nixos/modules/hardware/video/nvidia.nix212
-rw-r--r--nixpkgs/nixos/modules/hardware/video/radeon.nix3
-rw-r--r--nixpkgs/nixos/modules/hardware/video/uvcvideo/default.nix64
-rw-r--r--nixpkgs/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix45
-rw-r--r--nixpkgs/nixos/modules/hardware/video/webcam/facetimehd.nix44
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/default.nix71
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/default.xml244
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/fcitx.nix43
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/ibus.nix67
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/nabi.nix16
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/uim.nix37
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/channel.nix47
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-base.nix33
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix66
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix21
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde-new-kernel.nix7
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix46
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix7
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix12
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix665
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix7
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix66
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix63
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix52
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/sd-image.nix200
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix160
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt89
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-pc.nix164
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix173
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/system-tarball.nix93
-rw-r--r--nixpkgs/nixos/modules/installer/netboot/netboot-base.nix17
-rw-r--r--nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix10
-rw-r--r--nixpkgs/nixos/modules/installer/netboot/netboot.nix108
-rw-r--r--nixpkgs/nixos/modules/installer/scan/detected.nix12
-rw-r--r--nixpkgs/nixos/modules/installer/scan/not-detected.nix6
-rw-r--r--nixpkgs/nixos/modules/installer/tools/get-version-suffix22
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix6
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix13
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh52
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-enter.sh74
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl617
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-install.sh155
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-option.sh327
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-rebuild.sh404
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-version.sh14
-rw-r--r--nixpkgs/nixos/modules/installer/tools/tools.nix185
-rw-r--r--nixpkgs/nixos/modules/installer/virtualbox-demo.nix61
-rw-r--r--nixpkgs/nixos/modules/misc/assertions.nix34
-rw-r--r--nixpkgs/nixos/modules/misc/crashdump.nix75
-rw-r--r--nixpkgs/nixos/modules/misc/documentation.nix207
-rw-r--r--nixpkgs/nixos/modules/misc/extra-arguments.nix7
-rw-r--r--nixpkgs/nixos/modules/misc/ids.nix657
-rw-r--r--nixpkgs/nixos/modules/misc/label.nix72
-rw-r--r--nixpkgs/nixos/modules/misc/lib.nix15
-rw-r--r--nixpkgs/nixos/modules/misc/locate.nix182
-rw-r--r--nixpkgs/nixos/modules/misc/meta.nix61
-rw-r--r--nixpkgs/nixos/modules/misc/nixops-autoluks.nix43
-rw-r--r--nixpkgs/nixos/modules/misc/nixpkgs.nix245
-rw-r--r--nixpkgs/nixos/modules/misc/passthru.nix16
-rw-r--r--nixpkgs/nixos/modules/misc/version.nix105
-rw-r--r--nixpkgs/nixos/modules/module-list.nix962
-rw-r--r--nixpkgs/nixos/modules/profiles/all-hardware.nix57
-rw-r--r--nixpkgs/nixos/modules/profiles/base.nix56
-rw-r--r--nixpkgs/nixos/modules/profiles/clone-config.nix109
-rw-r--r--nixpkgs/nixos/modules/profiles/demo.nix19
-rw-r--r--nixpkgs/nixos/modules/profiles/docker-container.nix54
-rw-r--r--nixpkgs/nixos/modules/profiles/graphical.nix22
-rw-r--r--nixpkgs/nixos/modules/profiles/hardened.nix122
-rw-r--r--nixpkgs/nixos/modules/profiles/headless.nix25
-rw-r--r--nixpkgs/nixos/modules/profiles/installation-device.nix109
-rw-r--r--nixpkgs/nixos/modules/profiles/minimal.nix17
-rw-r--r--nixpkgs/nixos/modules/profiles/qemu-guest.nix19
-rw-r--r--nixpkgs/nixos/modules/programs/adb.nix29
-rw-r--r--nixpkgs/nixos/modules/programs/atop.nix36
-rw-r--r--nixpkgs/nixos/modules/programs/autojump.nix33
-rw-r--r--nixpkgs/nixos/modules/programs/bash/bash.nix236
-rw-r--r--nixpkgs/nixos/modules/programs/bash/inputrc37
-rw-r--r--nixpkgs/nixos/modules/programs/bcc.nix9
-rw-r--r--nixpkgs/nixos/modules/programs/blcr.nix27
-rw-r--r--nixpkgs/nixos/modules/programs/browserpass.nix32
-rw-r--r--nixpkgs/nixos/modules/programs/captive-browser.nix122
-rw-r--r--nixpkgs/nixos/modules/programs/ccache.nix83
-rw-r--r--nixpkgs/nixos/modules/programs/cdemu.nix58
-rw-r--r--nixpkgs/nixos/modules/programs/chromium.nix89
-rw-r--r--nixpkgs/nixos/modules/programs/clickshare.nix21
-rw-r--r--nixpkgs/nixos/modules/programs/command-not-found/command-not-found.nix95
-rw-r--r--nixpkgs/nixos/modules/programs/command-not-found/command-not-found.pl51
-rw-r--r--nixpkgs/nixos/modules/programs/criu.nix26
-rw-r--r--nixpkgs/nixos/modules/programs/dconf.nix44
-rw-r--r--nixpkgs/nixos/modules/programs/digitalbitbox/default.nix39
-rw-r--r--nixpkgs/nixos/modules/programs/digitalbitbox/doc.xml74
-rw-r--r--nixpkgs/nixos/modules/programs/dmrconfig.nix38
-rw-r--r--nixpkgs/nixos/modules/programs/environment.nix63
-rw-r--r--nixpkgs/nixos/modules/programs/evince.nix42
-rw-r--r--nixpkgs/nixos/modules/programs/file-roller.nix39
-rw-r--r--nixpkgs/nixos/modules/programs/firejail.nix48
-rw-r--r--nixpkgs/nixos/modules/programs/fish.nix240
-rw-r--r--nixpkgs/nixos/modules/programs/fish_completion-generator.patch11
-rw-r--r--nixpkgs/nixos/modules/programs/freetds.nix61
-rw-r--r--nixpkgs/nixos/modules/programs/fuse.nix37
-rw-r--r--nixpkgs/nixos/modules/programs/gnome-disks.nix46
-rw-r--r--nixpkgs/nixos/modules/programs/gnome-documents.nix50
-rw-r--r--nixpkgs/nixos/modules/programs/gnome-terminal.nix36
-rw-r--r--nixpkgs/nixos/modules/programs/gnupg.nix113
-rw-r--r--nixpkgs/nixos/modules/programs/gpaste.nix34
-rw-r--r--nixpkgs/nixos/modules/programs/gphoto2.nix30
-rw-r--r--nixpkgs/nixos/modules/programs/iftop.nix18
-rw-r--r--nixpkgs/nixos/modules/programs/iotop.nix17
-rw-r--r--nixpkgs/nixos/modules/programs/java.nix58
-rw-r--r--nixpkgs/nixos/modules/programs/kbdlight.nix16
-rw-r--r--nixpkgs/nixos/modules/programs/less.nix131
-rw-r--r--nixpkgs/nixos/modules/programs/light.nix27
-rw-r--r--nixpkgs/nixos/modules/programs/mininet.nix39
-rw-r--r--nixpkgs/nixos/modules/programs/mosh.nix43
-rw-r--r--nixpkgs/nixos/modules/programs/mtr.nix38
-rw-r--r--nixpkgs/nixos/modules/programs/nano.nix42
-rw-r--r--nixpkgs/nixos/modules/programs/nm-applet.nix14
-rw-r--r--nixpkgs/nixos/modules/programs/npm.nix46
-rw-r--r--nixpkgs/nixos/modules/programs/oblogout.nix176
-rw-r--r--nixpkgs/nixos/modules/programs/plotinus.nix36
-rw-r--r--nixpkgs/nixos/modules/programs/plotinus.xml30
-rw-r--r--nixpkgs/nixos/modules/programs/qt5ct.nix31
-rw-r--r--nixpkgs/nixos/modules/programs/screen.nix32
-rw-r--r--nixpkgs/nixos/modules/programs/seahorse.nix44
-rw-r--r--nixpkgs/nixos/modules/programs/sedutil.nix18
-rw-r--r--nixpkgs/nixos/modules/programs/shadow.nix115
-rw-r--r--nixpkgs/nixos/modules/programs/shell.nix54
-rw-r--r--nixpkgs/nixos/modules/programs/singularity.nix29
-rw-r--r--nixpkgs/nixos/modules/programs/slock.nix26
-rw-r--r--nixpkgs/nixos/modules/programs/spacefm.nix55
-rw-r--r--nixpkgs/nixos/modules/programs/ssh.nix267
-rw-r--r--nixpkgs/nixos/modules/programs/ssmtp.nix165
-rw-r--r--nixpkgs/nixos/modules/programs/sway.nix93
-rw-r--r--nixpkgs/nixos/modules/programs/sysdig.nix14
-rw-r--r--nixpkgs/nixos/modules/programs/system-config-printer.nix32
-rw-r--r--nixpkgs/nixos/modules/programs/systemtap.nix28
-rw-r--r--nixpkgs/nixos/modules/programs/thefuck.nix39
-rw-r--r--nixpkgs/nixos/modules/programs/tmux.nix184
-rw-r--r--nixpkgs/nixos/modules/programs/tsm-client.nix287
-rw-r--r--nixpkgs/nixos/modules/programs/udevil.nix14
-rw-r--r--nixpkgs/nixos/modules/programs/usbtop.nix21
-rw-r--r--nixpkgs/nixos/modules/programs/venus.nix173
-rw-r--r--nixpkgs/nixos/modules/programs/vim.nix23
-rw-r--r--nixpkgs/nixos/modules/programs/virtualbox.nix8
-rw-r--r--nixpkgs/nixos/modules/programs/wavemon.nix28
-rw-r--r--nixpkgs/nixos/modules/programs/way-cooler.nix78
-rw-r--r--nixpkgs/nixos/modules/programs/waybar.nix20
-rw-r--r--nixpkgs/nixos/modules/programs/wireshark.nix42
-rw-r--r--nixpkgs/nixos/modules/programs/x2goserver.nix148
-rw-r--r--nixpkgs/nixos/modules/programs/xfs_quota.nix110
-rw-r--r--nixpkgs/nixos/modules/programs/xonsh.nix60
-rw-r--r--nixpkgs/nixos/modules/programs/xss-lock.nix44
-rw-r--r--nixpkgs/nixos/modules/programs/yabar.nix162
-rw-r--r--nixpkgs/nixos/modules/programs/zmap.nix18
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.nix138
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.xml155
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zinputrc42
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zsh-autoenv.nix28
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix60
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix100
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zsh.nix244
-rw-r--r--nixpkgs/nixos/modules/rename.nix292
-rw-r--r--nixpkgs/nixos/modules/security/acme.nix312
-rw-r--r--nixpkgs/nixos/modules/security/acme.xml97
-rw-r--r--nixpkgs/nixos/modules/security/apparmor-suid.nix45
-rw-r--r--nixpkgs/nixos/modules/security/apparmor.nix59
-rw-r--r--nixpkgs/nixos/modules/security/audit.nix123
-rw-r--r--nixpkgs/nixos/modules/security/auditd.nix31
-rw-r--r--nixpkgs/nixos/modules/security/ca.nix95
-rw-r--r--nixpkgs/nixos/modules/security/chromium-suid-sandbox.nix29
-rw-r--r--nixpkgs/nixos/modules/security/dhparams.nix175
-rw-r--r--nixpkgs/nixos/modules/security/duosec.nix203
-rw-r--r--nixpkgs/nixos/modules/security/google_oslogin.nix68
-rw-r--r--nixpkgs/nixos/modules/security/hidepid.nix27
-rw-r--r--nixpkgs/nixos/modules/security/hidepid.xml28
-rw-r--r--nixpkgs/nixos/modules/security/lock-kernel-modules.nix48
-rw-r--r--nixpkgs/nixos/modules/security/misc.nix137
-rw-r--r--nixpkgs/nixos/modules/security/oath.nix50
-rw-r--r--nixpkgs/nixos/modules/security/pam.nix795
-rw-r--r--nixpkgs/nixos/modules/security/pam_mount.nix72
-rw-r--r--nixpkgs/nixos/modules/security/pam_usb.nix42
-rw-r--r--nixpkgs/nixos/modules/security/polkit.nix105
-rw-r--r--nixpkgs/nixos/modules/security/prey.nix51
-rw-r--r--nixpkgs/nixos/modules/security/rngd.nix53
-rw-r--r--nixpkgs/nixos/modules/security/rtkit.nix45
-rw-r--r--nixpkgs/nixos/modules/security/sudo.nix231
-rw-r--r--nixpkgs/nixos/modules/security/systemd-confinement.nix199
-rw-r--r--nixpkgs/nixos/modules/security/wrappers/default.nix202
-rw-r--r--nixpkgs/nixos/modules/security/wrappers/wrapper.c239
-rw-r--r--nixpkgs/nixos/modules/services/admin/oxidized.nix116
-rw-r--r--nixpkgs/nixos/modules/services/admin/salt/master.nix63
-rw-r--r--nixpkgs/nixos/modules/services/admin/salt/minion.nix67
-rw-r--r--nixpkgs/nixos/modules/services/amqp/activemq/ActiveMQBroker.java19
-rw-r--r--nixpkgs/nixos/modules/services/amqp/activemq/default.nix131
-rw-r--r--nixpkgs/nixos/modules/services/amqp/rabbitmq.nix207
-rw-r--r--nixpkgs/nixos/modules/services/audio/alsa.nix134
-rw-r--r--nixpkgs/nixos/modules/services/audio/icecast.nix130
-rw-r--r--nixpkgs/nixos/modules/services/audio/jack.nix290
-rw-r--r--nixpkgs/nixos/modules/services/audio/liquidsoap.nix69
-rw-r--r--nixpkgs/nixos/modules/services/audio/mopidy.nix108
-rw-r--r--nixpkgs/nixos/modules/services/audio/mpd.nix202
-rw-r--r--nixpkgs/nixos/modules/services/audio/roon-server.nix73
-rw-r--r--nixpkgs/nixos/modules/services/audio/slimserver.nix72
-rw-r--r--nixpkgs/nixos/modules/services/audio/snapserver.nix216
-rw-r--r--nixpkgs/nixos/modules/services/audio/spotifyd.nix42
-rw-r--r--nixpkgs/nixos/modules/services/audio/squeezelite.nix50
-rw-r--r--nixpkgs/nixos/modules/services/audio/ympd.nix57
-rw-r--r--nixpkgs/nixos/modules/services/backup/automysqlbackup.nix115
-rw-r--r--nixpkgs/nixos/modules/services/backup/bacula.nix412
-rw-r--r--nixpkgs/nixos/modules/services/backup/borgbackup.nix615
-rw-r--r--nixpkgs/nixos/modules/services/backup/duplicati.nix67
-rw-r--r--nixpkgs/nixos/modules/services/backup/duplicity.nix141
-rw-r--r--nixpkgs/nixos/modules/services/backup/mysql-backup.nix129
-rw-r--r--nixpkgs/nixos/modules/services/backup/postgresql-backup.nix124
-rw-r--r--nixpkgs/nixos/modules/services/backup/postgresql-wal-receiver.nix204
-rw-r--r--nixpkgs/nixos/modules/services/backup/restic-rest-server.nix107
-rw-r--r--nixpkgs/nixos/modules/services/backup/restic.nix165
-rw-r--r--nixpkgs/nixos/modules/services/backup/rsnapshot.nix75
-rw-r--r--nixpkgs/nixos/modules/services/backup/tarsnap.nix402
-rw-r--r--nixpkgs/nixos/modules/services/backup/tsm.nix106
-rw-r--r--nixpkgs/nixos/modules/services/backup/zfs-replication.nix90
-rw-r--r--nixpkgs/nixos/modules/services/backup/znapzend.nix399
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix31
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/default.nix60
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix73
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix74
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix167
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix328
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix334
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix457
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix162
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix284
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix133
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix344
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix390
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix82
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix94
-rw-r--r--nixpkgs/nixos/modules/services/computing/boinc/client.nix129
-rw-r--r--nixpkgs/nixos/modules/services/computing/slurm/slurm.nix371
-rw-r--r--nixpkgs/nixos/modules/services/computing/torque/mom.nix63
-rw-r--r--nixpkgs/nixos/modules/services/computing/torque/server.nix96
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix267
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix186
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildkite-agent.nix252
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix151
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix206
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix194
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hail.nix61
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix448
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix228
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix205
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix68
-rw-r--r--nixpkgs/nixos/modules/services/databases/4store-endpoint.nix74
-rw-r--r--nixpkgs/nixos/modules/services/databases/4store.nix72
-rw-r--r--nixpkgs/nixos/modules/services/databases/aerospike.nix156
-rw-r--r--nixpkgs/nixos/modules/services/databases/cassandra.nix479
-rw-r--r--nixpkgs/nixos/modules/services/databases/clickhouse.nix71
-rw-r--r--nixpkgs/nixos/modules/services/databases/cockroachdb.nix217
-rw-r--r--nixpkgs/nixos/modules/services/databases/couchdb.nix201
-rw-r--r--nixpkgs/nixos/modules/services/databases/firebird.nix166
-rw-r--r--nixpkgs/nixos/modules/services/databases/foundationdb.nix429
-rw-r--r--nixpkgs/nixos/modules/services/databases/foundationdb.xml443
-rw-r--r--nixpkgs/nixos/modules/services/databases/hbase.nix127
-rw-r--r--nixpkgs/nixos/modules/services/databases/influxdb.nix197
-rw-r--r--nixpkgs/nixos/modules/services/databases/memcached.nix116
-rw-r--r--nixpkgs/nixos/modules/services/databases/monetdb.nix100
-rw-r--r--nixpkgs/nixos/modules/services/databases/mongodb.nix193
-rw-r--r--nixpkgs/nixos/modules/services/databases/mysql.nix425
-rw-r--r--nixpkgs/nixos/modules/services/databases/neo4j.nix652
-rw-r--r--nixpkgs/nixos/modules/services/databases/openldap.nix282
-rw-r--r--nixpkgs/nixos/modules/services/databases/opentsdb.nix109
-rw-r--r--nixpkgs/nixos/modules/services/databases/pgmanage.nix206
-rw-r--r--nixpkgs/nixos/modules/services/databases/postgresql.nix355
-rw-r--r--nixpkgs/nixos/modules/services/databases/postgresql.xml143
-rw-r--r--nixpkgs/nixos/modules/services/databases/redis.nix226
-rw-r--r--nixpkgs/nixos/modules/services/databases/rethinkdb.nix110
-rw-r--r--nixpkgs/nixos/modules/services/databases/riak-cs.nix202
-rw-r--r--nixpkgs/nixos/modules/services/databases/riak.nix163
-rw-r--r--nixpkgs/nixos/modules/services/databases/stanchion.nix194
-rw-r--r--nixpkgs/nixos/modules/services/databases/virtuoso.nix98
-rw-r--r--nixpkgs/nixos/modules/services/desktops/accountsservice.nix54
-rw-r--r--nixpkgs/nixos/modules/services/desktops/bamf.nix23
-rw-r--r--nixpkgs/nixos/modules/services/desktops/blueman.nix25
-rw-r--r--nixpkgs/nixos/modules/services/desktops/deepin/deepin.nix125
-rw-r--r--nixpkgs/nixos/modules/services/desktops/dleyna-renderer.nix28
-rw-r--r--nixpkgs/nixos/modules/services/desktops/dleyna-server.nix28
-rw-r--r--nixpkgs/nixos/modules/services/desktops/flatpak.nix53
-rw-r--r--nixpkgs/nixos/modules/services/desktops/flatpak.xml56
-rw-r--r--nixpkgs/nixos/modules/services/desktops/geoclue2.nix259
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/at-spi2-core.nix42
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix29
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/evolution-data-server.nix41
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/glib-networking.nix33
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-keyring.nix47
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix39
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix39
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix18
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix45
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-user-share.nix36
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/rygel.nix30
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/sushi.nix38
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/tracker-miners.nix41
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/tracker.nix41
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gsignond.nix45
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gvfs.nix59
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pantheon/contractor.nix41
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pantheon/files.nix38
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire.nix37
-rw-r--r--nixpkgs/nixos/modules/services/desktops/profile-sync-daemon.nix77
-rw-r--r--nixpkgs/nixos/modules/services/desktops/system-config-printer.nix38
-rw-r--r--nixpkgs/nixos/modules/services/desktops/telepathy.nix39
-rw-r--r--nixpkgs/nixos/modules/services/desktops/tumbler.nix50
-rw-r--r--nixpkgs/nixos/modules/services/desktops/zeitgeist.nix26
-rw-r--r--nixpkgs/nixos/modules/services/development/bloop.nix54
-rw-r--r--nixpkgs/nixos/modules/services/development/hoogle.nix76
-rw-r--r--nixpkgs/nixos/modules/services/development/jupyter/default.nix185
-rw-r--r--nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix60
-rw-r--r--nixpkgs/nixos/modules/services/editors/emacs.nix102
-rw-r--r--nixpkgs/nixos/modules/services/editors/emacs.xml580
-rw-r--r--nixpkgs/nixos/modules/services/editors/infinoted.nix158
-rw-r--r--nixpkgs/nixos/modules/services/games/factorio.nix226
-rw-r--r--nixpkgs/nixos/modules/services/games/minecraft-server.nix234
-rw-r--r--nixpkgs/nixos/modules/services/games/minetest-server.nix107
-rw-r--r--nixpkgs/nixos/modules/services/games/terraria.nix149
-rw-r--r--nixpkgs/nixos/modules/services/hardware/acpid.nix156
-rw-r--r--nixpkgs/nixos/modules/services/hardware/actkbd.nix133
-rw-r--r--nixpkgs/nixos/modules/services/hardware/bluetooth.nix89
-rw-r--r--nixpkgs/nixos/modules/services/hardware/bolt.nix34
-rw-r--r--nixpkgs/nixos/modules/services/hardware/brltty.nix50
-rw-r--r--nixpkgs/nixos/modules/services/hardware/freefall.nix64
-rw-r--r--nixpkgs/nixos/modules/services/hardware/fwupd.nix127
-rw-r--r--nixpkgs/nixos/modules/services/hardware/illum.nix35
-rw-r--r--nixpkgs/nixos/modules/services/hardware/interception-tools.nix61
-rw-r--r--nixpkgs/nixos/modules/services/hardware/irqbalance.nix30
-rw-r--r--nixpkgs/nixos/modules/services/hardware/lcd.nix172
-rw-r--r--nixpkgs/nixos/modules/services/hardware/lirc.nix100
-rw-r--r--nixpkgs/nixos/modules/services/hardware/nvidia-optimus.nix43
-rw-r--r--nixpkgs/nixos/modules/services/hardware/pcscd.nix69
-rw-r--r--nixpkgs/nixos/modules/services/hardware/pommed.nix50
-rw-r--r--nixpkgs/nixos/modules/services/hardware/ratbagd.nix32
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane.nix162
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix115
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix71
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix26
-rw-r--r--nixpkgs/nixos/modules/services/hardware/tcsd.nix151
-rw-r--r--nixpkgs/nixos/modules/services/hardware/thermald.nix52
-rw-r--r--nixpkgs/nixos/modules/services/hardware/thinkfan.nix152
-rw-r--r--nixpkgs/nixos/modules/services/hardware/throttled.nix30
-rw-r--r--nixpkgs/nixos/modules/services/hardware/tlp.nix120
-rw-r--r--nixpkgs/nixos/modules/services/hardware/trezord.nix82
-rw-r--r--nixpkgs/nixos/modules/services/hardware/trezord.xml26
-rw-r--r--nixpkgs/nixos/modules/services/hardware/triggerhappy.nix122
-rw-r--r--nixpkgs/nixos/modules/services/hardware/u2f.nix23
-rw-r--r--nixpkgs/nixos/modules/services/hardware/udev.nix318
-rw-r--r--nixpkgs/nixos/modules/services/hardware/udisks2.nix47
-rw-r--r--nixpkgs/nixos/modules/services/hardware/undervolt.nix134
-rw-r--r--nixpkgs/nixos/modules/services/hardware/upower.nix59
-rw-r--r--nixpkgs/nixos/modules/services/hardware/usbmuxd.nix74
-rw-r--r--nixpkgs/nixos/modules/services/hardware/vdr.nix81
-rw-r--r--nixpkgs/nixos/modules/services/logging/SystemdJournal2Gelf.nix59
-rw-r--r--nixpkgs/nixos/modules/services/logging/awstats.nix117
-rw-r--r--nixpkgs/nixos/modules/services/logging/fluentd.nix58
-rw-r--r--nixpkgs/nixos/modules/services/logging/graylog.nix170
-rw-r--r--nixpkgs/nixos/modules/services/logging/heartbeat.nix74
-rw-r--r--nixpkgs/nixos/modules/services/logging/journalbeat.nix107
-rw-r--r--nixpkgs/nixos/modules/services/logging/journaldriver.nix112
-rw-r--r--nixpkgs/nixos/modules/services/logging/journalwatch.nix264
-rw-r--r--nixpkgs/nixos/modules/services/logging/klogd.nix37
-rw-r--r--nixpkgs/nixos/modules/services/logging/logcheck.nix237
-rw-r--r--nixpkgs/nixos/modules/services/logging/logrotate.nix46
-rw-r--r--nixpkgs/nixos/modules/services/logging/logstash.nix180
-rw-r--r--nixpkgs/nixos/modules/services/logging/rsyslogd.nix105
-rw-r--r--nixpkgs/nixos/modules/services/logging/syslog-ng.nix97
-rw-r--r--nixpkgs/nixos/modules/services/logging/syslogd.nix130
-rw-r--r--nixpkgs/nixos/modules/services/mail/clamsmtp.nix181
-rw-r--r--nixpkgs/nixos/modules/services/mail/davmail.nix99
-rw-r--r--nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix120
-rw-r--r--nixpkgs/nixos/modules/services/mail/dovecot.nix400
-rw-r--r--nixpkgs/nixos/modules/services/mail/dspam.nix150
-rw-r--r--nixpkgs/nixos/modules/services/mail/exim.nix122
-rw-r--r--nixpkgs/nixos/modules/services/mail/freepops.nix89
-rw-r--r--nixpkgs/nixos/modules/services/mail/mail.nix33
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailcatcher.nix61
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailhog.nix43
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailman.nix297
-rw-r--r--nixpkgs/nixos/modules/services/mail/mlmmj.nix156
-rw-r--r--nixpkgs/nixos/modules/services/mail/nullmailer.nix246
-rw-r--r--nixpkgs/nixos/modules/services/mail/offlineimap.nix72
-rw-r--r--nixpkgs/nixos/modules/services/mail/opendkim.nix133
-rw-r--r--nixpkgs/nixos/modules/services/mail/opensmtpd.nix131
-rw-r--r--nixpkgs/nixos/modules/services/mail/pfix-srsd.nix56
-rw-r--r--nixpkgs/nixos/modules/services/mail/postfix.nix898
-rw-r--r--nixpkgs/nixos/modules/services/mail/postgrey.nix194
-rw-r--r--nixpkgs/nixos/modules/services/mail/postsrsd.nix135
-rw-r--r--nixpkgs/nixos/modules/services/mail/roundcube.nix175
-rw-r--r--nixpkgs/nixos/modules/services/mail/rspamd.nix418
-rw-r--r--nixpkgs/nixos/modules/services/mail/rss2email.nix133
-rw-r--r--nixpkgs/nixos/modules/services/mail/spamassassin.nix199
-rw-r--r--nixpkgs/nixos/modules/services/misc/airsonic.nix153
-rw-r--r--nixpkgs/nixos/modules/services/misc/apache-kafka.nix155
-rw-r--r--nixpkgs/nixos/modules/services/misc/autofs.nix97
-rw-r--r--nixpkgs/nixos/modules/services/misc/autorandr.nix52
-rw-r--r--nixpkgs/nixos/modules/services/misc/beanstalkd.nix52
-rw-r--r--nixpkgs/nixos/modules/services/misc/bees.nix123
-rw-r--r--nixpkgs/nixos/modules/services/misc/bepasty.nix183
-rw-r--r--nixpkgs/nixos/modules/services/misc/calibre-server.nix63
-rw-r--r--nixpkgs/nixos/modules/services/misc/canto-daemon.nix37
-rw-r--r--nixpkgs/nixos/modules/services/misc/cfdyndns.nix70
-rw-r--r--nixpkgs/nixos/modules/services/misc/cgminer.nix145
-rw-r--r--nixpkgs/nixos/modules/services/misc/clipmenu.nix31
-rwxr-xr-xnixpkgs/nixos/modules/services/misc/confd.nix90
-rw-r--r--nixpkgs/nixos/modules/services/misc/couchpotato.nix45
-rw-r--r--nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix66
-rw-r--r--nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb69
-rw-r--r--nixpkgs/nixos/modules/services/misc/devmon.nix30
-rw-r--r--nixpkgs/nixos/modules/services/misc/dictd.nix69
-rw-r--r--nixpkgs/nixos/modules/services/misc/disnix.nix97
-rw-r--r--nixpkgs/nixos/modules/services/misc/docker-registry.nix155
-rw-r--r--nixpkgs/nixos/modules/services/misc/dwm-status.nix73
-rw-r--r--nixpkgs/nixos/modules/services/misc/dysnomia.nix217
-rw-r--r--nixpkgs/nixos/modules/services/misc/errbot.nix101
-rw-r--r--nixpkgs/nixos/modules/services/misc/etcd.nix196
-rw-r--r--nixpkgs/nixos/modules/services/misc/ethminer.nix115
-rw-r--r--nixpkgs/nixos/modules/services/misc/exhibitor.nix420
-rw-r--r--nixpkgs/nixos/modules/services/misc/felix.nix109
-rw-r--r--nixpkgs/nixos/modules/services/misc/folding-at-home.nix68
-rw-r--r--nixpkgs/nixos/modules/services/misc/fstrim.nix46
-rw-r--r--nixpkgs/nixos/modules/services/misc/gammu-smsd.nix253
-rw-r--r--nixpkgs/nixos/modules/services/misc/geoip-updater.nix306
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitea.nix455
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitit.nix724
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitlab.nix851
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitlab.xml126
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitolite.nix231
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitweb.nix59
-rw-r--r--nixpkgs/nixos/modules/services/misc/gogs.nix279
-rw-r--r--nixpkgs/nixos/modules/services/misc/gollum.nix110
-rw-r--r--nixpkgs/nixos/modules/services/misc/gpsd.nix119
-rw-r--r--nixpkgs/nixos/modules/services/misc/greenclip.nix31
-rw-r--r--nixpkgs/nixos/modules/services/misc/headphones.nix87
-rw-r--r--nixpkgs/nixos/modules/services/misc/home-assistant.nix250
-rw-r--r--nixpkgs/nixos/modules/services/misc/ihaskell.nix62
-rw-r--r--nixpkgs/nixos/modules/services/misc/irkerd.nix67
-rw-r--r--nixpkgs/nixos/modules/services/misc/jackett.nix82
-rw-r--r--nixpkgs/nixos/modules/services/misc/jellyfin.nix54
-rw-r--r--nixpkgs/nixos/modules/services/misc/leaps.nix62
-rw-r--r--nixpkgs/nixos/modules/services/misc/lidarr.nix82
-rw-r--r--nixpkgs/nixos/modules/services/misc/logkeys.nix30
-rw-r--r--nixpkgs/nixos/modules/services/misc/mathics.nix54
-rw-r--r--nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml25
-rw-r--r--nixpkgs/nixos/modules/services/misc/matrix-synapse.nix702
-rw-r--r--nixpkgs/nixos/modules/services/misc/mbpfan.nix109
-rw-r--r--nixpkgs/nixos/modules/services/misc/mediatomb.nix288
-rw-r--r--nixpkgs/nixos/modules/services/misc/mesos-master.nix125
-rw-r--r--nixpkgs/nixos/modules/services/misc/mesos-slave.nix220
-rw-r--r--nixpkgs/nixos/modules/services/misc/metabase.nix103
-rw-r--r--nixpkgs/nixos/modules/services/misc/mwlib.nix258
-rw-r--r--nixpkgs/nixos/modules/services/misc/nix-daemon.nix515
-rw-r--r--nixpkgs/nixos/modules/services/misc/nix-gc.nix61
-rw-r--r--nixpkgs/nixos/modules/services/misc/nix-optimise.nix51
-rw-r--r--nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix61
-rw-r--r--nixpkgs/nixos/modules/services/misc/nixos-manual.nix73
-rw-r--r--nixpkgs/nixos/modules/services/misc/novacomd.nix31
-rw-r--r--nixpkgs/nixos/modules/services/misc/nzbget.nix93
-rw-r--r--nixpkgs/nixos/modules/services/misc/octoprint.nix130
-rw-r--r--nixpkgs/nixos/modules/services/misc/osrm.nix85
-rw-r--r--nixpkgs/nixos/modules/services/misc/packagekit.nix65
-rw-r--r--nixpkgs/nixos/modules/services/misc/paperless.nix185
-rw-r--r--nixpkgs/nixos/modules/services/misc/parsoid.nix104
-rw-r--r--nixpkgs/nixos/modules/services/misc/plex.nix151
-rw-r--r--nixpkgs/nixos/modules/services/misc/pykms.nix86
-rw-r--r--nixpkgs/nixos/modules/services/misc/radarr.nix75
-rw-r--r--nixpkgs/nixos/modules/services/misc/redmine.nix404
-rw-r--r--nixpkgs/nixos/modules/services/misc/ripple-data-api.nix194
-rw-r--r--nixpkgs/nixos/modules/services/misc/rippled.nix432
-rw-r--r--nixpkgs/nixos/modules/services/misc/rogue.nix62
-rw-r--r--nixpkgs/nixos/modules/services/misc/safeeyes.nix50
-rw-r--r--nixpkgs/nixos/modules/services/misc/serviio.nix92
-rw-r--r--nixpkgs/nixos/modules/services/misc/sickbeard.nix92
-rw-r--r--nixpkgs/nixos/modules/services/misc/siproxd.nix180
-rw-r--r--nixpkgs/nixos/modules/services/misc/snapper.nix152
-rw-r--r--nixpkgs/nixos/modules/services/misc/sonarr.nix76
-rw-r--r--nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix30
-rw-r--r--nixpkgs/nixos/modules/services/misc/ssm-agent.nix46
-rw-r--r--nixpkgs/nixos/modules/services/misc/sssd.nix96
-rw-r--r--nixpkgs/nixos/modules/services/misc/subsonic.nix165
-rw-r--r--nixpkgs/nixos/modules/services/misc/sundtek.nix33
-rw-r--r--nixpkgs/nixos/modules/services/misc/svnserve.nix44
-rw-r--r--nixpkgs/nixos/modules/services/misc/synergy.nix132
-rw-r--r--nixpkgs/nixos/modules/services/misc/sysprof.nix19
-rw-r--r--nixpkgs/nixos/modules/services/misc/taskserver/default.nix568
-rw-r--r--nixpkgs/nixos/modules/services/misc/taskserver/doc.xml135
-rw-r--r--nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py688
-rw-r--r--nixpkgs/nixos/modules/services/misc/tautulli.nix77
-rw-r--r--nixpkgs/nixos/modules/services/misc/tiddlywiki.nix52
-rw-r--r--nixpkgs/nixos/modules/services/misc/tzupdate.nix45
-rw-r--r--nixpkgs/nixos/modules/services/misc/uhub.nix186
-rw-r--r--nixpkgs/nixos/modules/services/misc/weechat.nix58
-rw-r--r--nixpkgs/nixos/modules/services/misc/weechat.xml66
-rw-r--r--nixpkgs/nixos/modules/services/misc/xmr-stak.nix93
-rw-r--r--nixpkgs/nixos/modules/services/misc/zoneminder.nix372
-rw-r--r--nixpkgs/nixos/modules/services/misc/zookeeper.nix156
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/alerta.nix115
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/apcupsd.nix191
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/arbtt.nix63
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/bosun.nix166
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/cadvisor.nix141
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/collectd.nix103
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix34
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix271
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix238
-rwxr-xr-xnixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults9
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix63
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix66
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/grafana.nix559
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/graphite.nix644
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/hdaps.nix23
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/heapster.nix58
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/incron.nix98
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/kapacitor.nix191
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/loki.nix112
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/longview.nix160
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/monit.nix46
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/munin.nix404
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/nagios.nix187
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/netdata.nix191
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/osquery.nix91
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix150
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix622
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix211
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml227
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bind.nix54
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix37
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix77
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix38
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix73
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix38
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/json.nix35
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix157
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/minio.nix64
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix54
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix40
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix82
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix47
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix92
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix70
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix31
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix44
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix66
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix88
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix61
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix166
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/riemann-dash.nix81
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix70
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/riemann.nix105
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/scollector.nix135
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/smartd.nix242
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/statsd.nix150
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/sysstat.nix80
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/teamviewer.nix46
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/telegraf.nix72
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/thanos.nix801
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/ups.nix280
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/uptime.nix96
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/vnstat.nix58
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix158
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix299
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix293
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/beegfs.nix357
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/cachefilesd.nix59
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/ceph.nix407
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/davfs2.nix74
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/diod.nix159
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/drbd.nix67
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix211
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix284
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/kbfs.nix66
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix150
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/nfsd.nix171
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/openafs/client.nix253
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix33
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix269
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix124
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/samba.nix253
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix368
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/u9fs.nix78
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix474
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix113
-rw-r--r--nixpkgs/nixos/modules/services/networking/amuled.nix76
-rw-r--r--nixpkgs/nixos/modules/services/networking/aria2.nix131
-rw-r--r--nixpkgs/nixos/modules/services/networking/asterisk.nix264
-rw-r--r--nixpkgs/nixos/modules/services/networking/atftpd.nix65
-rw-r--r--nixpkgs/nixos/modules/services/networking/autossh.nix113
-rw-r--r--nixpkgs/nixos/modules/services/networking/avahi-daemon.nix281
-rw-r--r--nixpkgs/nixos/modules/services/networking/babeld.nix100
-rw-r--r--nixpkgs/nixos/modules/services/networking/bind.nix207
-rw-r--r--nixpkgs/nixos/modules/services/networking/bird.nix78
-rw-r--r--nixpkgs/nixos/modules/services/networking/bitcoind.nix195
-rw-r--r--nixpkgs/nixos/modules/services/networking/bitlbee.nix194
-rw-r--r--nixpkgs/nixos/modules/services/networking/charybdis.nix109
-rw-r--r--nixpkgs/nixos/modules/services/networking/cjdns.nix294
-rw-r--r--nixpkgs/nixos/modules/services/networking/cntlm.nix126
-rw-r--r--nixpkgs/nixos/modules/services/networking/connman.nix128
-rw-r--r--nixpkgs/nixos/modules/services/networking/consul.nix254
-rw-r--r--nixpkgs/nixos/modules/services/networking/coredns.nix50
-rw-r--r--nixpkgs/nixos/modules/services/networking/coturn.nix336
-rw-r--r--nixpkgs/nixos/modules/services/networking/dante.nix62
-rw-r--r--nixpkgs/nixos/modules/services/networking/ddclient.nix201
-rw-r--r--nixpkgs/nixos/modules/services/networking/dhcpcd.nix202
-rw-r--r--nixpkgs/nixos/modules/services/networking/dhcpd.nix210
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnscache.nix106
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnschain.nix183
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.nix328
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.xml66
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix199
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnsdist.nix61
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnsmasq.nix129
-rw-r--r--nixpkgs/nixos/modules/services/networking/ejabberd.nix157
-rw-r--r--nixpkgs/nixos/modules/services/networking/epmd.nix56
-rw-r--r--nixpkgs/nixos/modules/services/networking/eternal-terminal.nix89
-rw-r--r--nixpkgs/nixos/modules/services/networking/fakeroute.nix63
-rw-r--r--nixpkgs/nixos/modules/services/networking/ferm.nix63
-rw-r--r--nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix183
-rw-r--r--nixpkgs/nixos/modules/services/networking/fireqos.nix52
-rw-r--r--nixpkgs/nixos/modules/services/networking/firewall.nix579
-rw-r--r--nixpkgs/nixos/modules/services/networking/flannel.nix191
-rw-r--r--nixpkgs/nixos/modules/services/networking/flashpolicyd.nix84
-rw-r--r--nixpkgs/nixos/modules/services/networking/freenet.nix64
-rw-r--r--nixpkgs/nixos/modules/services/networking/freeradius.nix72
-rw-r--r--nixpkgs/nixos/modules/services/networking/gale.nix182
-rw-r--r--nixpkgs/nixos/modules/services/networking/gateone.nix59
-rw-r--r--nixpkgs/nixos/modules/services/networking/gdomap.nix29
-rw-r--r--nixpkgs/nixos/modules/services/networking/git-daemon.nix130
-rw-r--r--nixpkgs/nixos/modules/services/networking/gnunet.nix158
-rw-r--r--nixpkgs/nixos/modules/services/networking/gogoclient.nix84
-rw-r--r--nixpkgs/nixos/modules/services/networking/gvpe.nix128
-rw-r--r--nixpkgs/nixos/modules/services/networking/hans.nix145
-rw-r--r--nixpkgs/nixos/modules/services/networking/haproxy.nix62
-rw-r--r--nixpkgs/nixos/modules/services/networking/heyefi.nix82
-rw-r--r--nixpkgs/nixos/modules/services/networking/hostapd.nix182
-rw-r--r--nixpkgs/nixos/modules/services/networking/htpdate.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/default.nix31
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/faxq-default.nix12
-rwxr-xr-xnixpkgs/nixos/modules/services/networking/hylafax/faxq-wait.sh29
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/hfaxd-default.nix10
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/modem-default.nix22
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/options.nix375
-rwxr-xr-xnixpkgs/nixos/modules/services/networking/hylafax/spool.sh111
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix249
-rw-r--r--nixpkgs/nixos/modules/services/networking/i2p.nix34
-rw-r--r--nixpkgs/nixos/modules/services/networking/i2pd.nix680
-rw-r--r--nixpkgs/nixos/modules/services/networking/iodine.nix150
-rw-r--r--nixpkgs/nixos/modules/services/networking/iperf3.nix97
-rw-r--r--nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh31
-rw-r--r--nixpkgs/nixos/modules/services/networking/ircd-hybrid/control.in26
-rw-r--r--nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix131
-rw-r--r--nixpkgs/nixos/modules/services/networking/ircd-hybrid/ircd.conf1051
-rw-r--r--nixpkgs/nixos/modules/services/networking/iwd.nix34
-rw-r--r--nixpkgs/nixos/modules/services/networking/jormungandr.nix102
-rw-r--r--nixpkgs/nixos/modules/services/networking/keepalived/default.nix303
-rw-r--r--nixpkgs/nixos/modules/services/networking/keepalived/virtual-ip-options.nix50
-rw-r--r--nixpkgs/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix135
-rw-r--r--nixpkgs/nixos/modules/services/networking/keepalived/vrrp-script-options.nix64
-rw-r--r--nixpkgs/nixos/modules/services/networking/keybase.nix42
-rw-r--r--nixpkgs/nixos/modules/services/networking/kippo.nix118
-rw-r--r--nixpkgs/nixos/modules/services/networking/knot.nix95
-rw-r--r--nixpkgs/nixos/modules/services/networking/kresd.nix136
-rw-r--r--nixpkgs/nixos/modules/services/networking/lambdabot.nix82
-rw-r--r--nixpkgs/nixos/modules/services/networking/libreswan.nix129
-rw-r--r--nixpkgs/nixos/modules/services/networking/lldpd.nix39
-rw-r--r--nixpkgs/nixos/modules/services/networking/logmein-hamachi.nix50
-rw-r--r--nixpkgs/nixos/modules/services/networking/mailpile.nix76
-rw-r--r--nixpkgs/nixos/modules/services/networking/matterbridge.nix118
-rw-r--r--nixpkgs/nixos/modules/services/networking/minidlna.nix176
-rw-r--r--nixpkgs/nixos/modules/services/networking/miniupnpd.nix79
-rw-r--r--nixpkgs/nixos/modules/services/networking/miredo.nix92
-rw-r--r--nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix79
-rw-r--r--nixpkgs/nixos/modules/services/networking/monero.nix238
-rw-r--r--nixpkgs/nixos/modules/services/networking/morty.nix96
-rw-r--r--nixpkgs/nixos/modules/services/networking/mosquitto.nix231
-rw-r--r--nixpkgs/nixos/modules/services/networking/mstpd.nix33
-rw-r--r--nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix110
-rw-r--r--nixpkgs/nixos/modules/services/networking/murmur.nix265
-rw-r--r--nixpkgs/nixos/modules/services/networking/mxisd.nix116
-rw-r--r--nixpkgs/nixos/modules/services/networking/namecoind.nix204
-rw-r--r--nixpkgs/nixos/modules/services/networking/nat.nix282
-rw-r--r--nixpkgs/nixos/modules/services/networking/ndppd.nix167
-rw-r--r--nixpkgs/nixos/modules/services/networking/networkmanager.nix518
-rw-r--r--nixpkgs/nixos/modules/services/networking/nftables.nix136
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix131
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/backend-submodule.nix50
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/default.nix117
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix64
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/frontend-submodule.nix36
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix142
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/server-options.nix18
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/tls-submodule.nix21
-rw-r--r--nixpkgs/nixos/modules/services/networking/ngircd.nix59
-rw-r--r--nixpkgs/nixos/modules/services/networking/nix-serve.nix81
-rw-r--r--nixpkgs/nixos/modules/services/networking/nixops-dns.nix79
-rw-r--r--nixpkgs/nixos/modules/services/networking/nntp-proxy.nix235
-rw-r--r--nixpkgs/nixos/modules/services/networking/nsd.nix984
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntopng.nix116
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/chrony.nix130
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix135
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix83
-rw-r--r--nixpkgs/nixos/modules/services/networking/nullidentdmod.nix34
-rw-r--r--nixpkgs/nixos/modules/services/networking/nylon.nix166
-rw-r--r--nixpkgs/nixos/modules/services/networking/ocserv.nix99
-rw-r--r--nixpkgs/nixos/modules/services/networking/ofono.nix44
-rw-r--r--nixpkgs/nixos/modules/services/networking/oidentd.nix44
-rw-r--r--nixpkgs/nixos/modules/services/networking/openfire.nix60
-rw-r--r--nixpkgs/nixos/modules/services/networking/openvpn.nix216
-rw-r--r--nixpkgs/nixos/modules/services/networking/ostinato.nix104
-rw-r--r--nixpkgs/nixos/modules/services/networking/owamp.nix47
-rw-r--r--nixpkgs/nixos/modules/services/networking/pdns-recursor.nix213
-rw-r--r--nixpkgs/nixos/modules/services/networking/pdnsd.nix93
-rw-r--r--nixpkgs/nixos/modules/services/networking/polipo.nix114
-rw-r--r--nixpkgs/nixos/modules/services/networking/powerdns.nix49
-rw-r--r--nixpkgs/nixos/modules/services/networking/pptpd.nix124
-rw-r--r--nixpkgs/nixos/modules/services/networking/prayer.nix97
-rw-r--r--nixpkgs/nixos/modules/services/networking/privoxy.nix112
-rw-r--r--nixpkgs/nixos/modules/services/networking/prosody.nix530
-rw-r--r--nixpkgs/nixos/modules/services/networking/quagga.nix185
-rw-r--r--nixpkgs/nixos/modules/services/networking/quassel.nix133
-rw-r--r--nixpkgs/nixos/modules/services/networking/quicktun.nix118
-rw-r--r--nixpkgs/nixos/modules/services/networking/racoon.nix45
-rw-r--r--nixpkgs/nixos/modules/services/networking/radicale.nix92
-rw-r--r--nixpkgs/nixos/modules/services/networking/radvd.nix72
-rw-r--r--nixpkgs/nixos/modules/services/networking/rdnssd.nix79
-rw-r--r--nixpkgs/nixos/modules/services/networking/redsocks.nix272
-rw-r--r--nixpkgs/nixos/modules/services/networking/resilio.nix263
-rw-r--r--nixpkgs/nixos/modules/services/networking/rpcbind.nix46
-rw-r--r--nixpkgs/nixos/modules/services/networking/rxe.nix63
-rw-r--r--nixpkgs/nixos/modules/services/networking/sabnzbd.nix69
-rw-r--r--nixpkgs/nixos/modules/services/networking/searx.nix78
-rw-r--r--nixpkgs/nixos/modules/services/networking/seeks.nix75
-rw-r--r--nixpkgs/nixos/modules/services/networking/shadowsocks.nix112
-rw-r--r--nixpkgs/nixos/modules/services/networking/shairport-sync.nix83
-rw-r--r--nixpkgs/nixos/modules/services/networking/shout.nix114
-rw-r--r--nixpkgs/nixos/modules/services/networking/skydns.nix92
-rw-r--r--nixpkgs/nixos/modules/services/networking/smokeping.nix318
-rw-r--r--nixpkgs/nixos/modules/services/networking/sniproxy.nix99
-rw-r--r--nixpkgs/nixos/modules/services/networking/softether.nix163
-rw-r--r--nixpkgs/nixos/modules/services/networking/spiped.nix220
-rw-r--r--nixpkgs/nixos/modules/services/networking/squid.nix168
-rw-r--r--nixpkgs/nixos/modules/services/networking/ssh/lshd.nix176
-rw-r--r--nixpkgs/nixos/modules/services/networking/ssh/sshd.nix512
-rw-r--r--nixpkgs/nixos/modules/services/networking/sslh.nix165
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix84
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix162
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix82
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix1300
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan.nix168
-rw-r--r--nixpkgs/nixos/modules/services/networking/stubby.nix214
-rw-r--r--nixpkgs/nixos/modules/services/networking/stunnel.nix221
-rw-r--r--nixpkgs/nixos/modules/services/networking/supplicant.nix251
-rw-r--r--nixpkgs/nixos/modules/services/networking/supybot.nix88
-rw-r--r--nixpkgs/nixos/modules/services/networking/syncplay.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/syncthing-relay.nix121
-rw-r--r--nixpkgs/nixos/modules/services/networking/syncthing.nix444
-rw-r--r--nixpkgs/nixos/modules/services/networking/tcpcrypt.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/teamspeak3.nix142
-rw-r--r--nixpkgs/nixos/modules/services/networking/tedicross.nix100
-rw-r--r--nixpkgs/nixos/modules/services/networking/tftpd.nix46
-rw-r--r--nixpkgs/nixos/modules/services/networking/thelounge.nix75
-rw-r--r--nixpkgs/nixos/modules/services/networking/tinc.nix212
-rw-r--r--nixpkgs/nixos/modules/services/networking/tinydns.nix54
-rw-r--r--nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/tox-node.nix95
-rw-r--r--nixpkgs/nixos/modules/services/networking/toxvpn.nix68
-rw-r--r--nixpkgs/nixos/modules/services/networking/tvheadend.nix61
-rw-r--r--nixpkgs/nixos/modules/services/networking/unbound.nix141
-rw-r--r--nixpkgs/nixos/modules/services/networking/unifi.nix189
-rw-r--r--nixpkgs/nixos/modules/services/networking/vsftpd.nix235
-rw-r--r--nixpkgs/nixos/modules/services/networking/wakeonlan.nix56
-rw-r--r--nixpkgs/nixos/modules/services/networking/websockify.nix54
-rw-r--r--nixpkgs/nixos/modules/services/networking/wg-quick.nix312
-rw-r--r--nixpkgs/nixos/modules/services/networking/wicd.nix39
-rw-r--r--nixpkgs/nixos/modules/services/networking/wireguard.nix408
-rw-r--r--nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix252
-rw-r--r--nixpkgs/nixos/modules/services/networking/xinetd.nix152
-rw-r--r--nixpkgs/nixos/modules/services/networking/xl2tpd.nix143
-rw-r--r--nixpkgs/nixos/modules/services/networking/xrdp.nix171
-rw-r--r--nixpkgs/nixos/modules/services/networking/zerobin.nix102
-rw-r--r--nixpkgs/nixos/modules/services/networking/zeronet.nix122
-rw-r--r--nixpkgs/nixos/modules/services/networking/zerotierone.nix67
-rw-r--r--nixpkgs/nixos/modules/services/networking/znc/default.nix306
-rw-r--r--nixpkgs/nixos/modules/services/networking/znc/options.nix270
-rw-r--r--nixpkgs/nixos/modules/services/printing/cupsd.nix442
-rw-r--r--nixpkgs/nixos/modules/services/scheduling/atd.nix115
-rw-r--r--nixpkgs/nixos/modules/services/scheduling/chronos.nix54
-rw-r--r--nixpkgs/nixos/modules/services/scheduling/cron.nix133
-rw-r--r--nixpkgs/nixos/modules/services/scheduling/fcron.nix166
-rw-r--r--nixpkgs/nixos/modules/services/scheduling/marathon.nix98
-rw-r--r--nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix94
-rw-r--r--nixpkgs/nixos/modules/services/search/elasticsearch.nix208
-rw-r--r--nixpkgs/nixos/modules/services/search/hound.nix125
-rw-r--r--nixpkgs/nixos/modules/services/search/kibana.nix209
-rw-r--r--nixpkgs/nixos/modules/services/search/solr.nix118
-rw-r--r--nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh17
-rw-r--r--nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix126
-rw-r--r--nixpkgs/nixos/modules/services/security/certmgr.nix201
-rw-r--r--nixpkgs/nixos/modules/services/security/cfssl.nix209
-rw-r--r--nixpkgs/nixos/modules/services/security/clamav.nix146
-rw-r--r--nixpkgs/nixos/modules/services/security/fail2ban.nix152
-rw-r--r--nixpkgs/nixos/modules/services/security/fprintd.nix62
-rw-r--r--nixpkgs/nixos/modules/services/security/fprot.nix88
-rw-r--r--nixpkgs/nixos/modules/services/security/haka.nix156
-rw-r--r--nixpkgs/nixos/modules/services/security/haveged.nix67
-rw-r--r--nixpkgs/nixos/modules/services/security/hologram-agent.nix58
-rw-r--r--nixpkgs/nixos/modules/services/security/hologram-server.nix130
-rw-r--r--nixpkgs/nixos/modules/services/security/munge.nix68
-rw-r--r--nixpkgs/nixos/modules/services/security/nginx-sso.nix58
-rw-r--r--nixpkgs/nixos/modules/services/security/oauth2_proxy.nix566
-rw-r--r--nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix64
-rw-r--r--nixpkgs/nixos/modules/services/security/physlock.nix128
-rw-r--r--nixpkgs/nixos/modules/services/security/shibboleth-sp.nix75
-rw-r--r--nixpkgs/nixos/modules/services/security/sks.nix146
-rw-r--r--nixpkgs/nixos/modules/services/security/sshguard.nix150
-rw-r--r--nixpkgs/nixos/modules/services/security/tor.nix783
-rw-r--r--nixpkgs/nixos/modules/services/security/torify.nix77
-rw-r--r--nixpkgs/nixos/modules/services/security/torsocks.nix121
-rw-r--r--nixpkgs/nixos/modules/services/security/usbguard.nix213
-rw-r--r--nixpkgs/nixos/modules/services/security/vault.nix156
-rw-r--r--nixpkgs/nixos/modules/services/system/cgmanager.nix26
-rw-r--r--nixpkgs/nixos/modules/services/system/cloud-init.nix180
-rw-r--r--nixpkgs/nixos/modules/services/system/dbus.nix119
-rw-r--r--nixpkgs/nixos/modules/services/system/earlyoom.nix109
-rw-r--r--nixpkgs/nixos/modules/services/system/kerberos/default.nix80
-rw-r--r--nixpkgs/nixos/modules/services/system/kerberos/heimdal.nix68
-rw-r--r--nixpkgs/nixos/modules/services/system/kerberos/mit.nix68
-rw-r--r--nixpkgs/nixos/modules/services/system/localtime.nix43
-rw-r--r--nixpkgs/nixos/modules/services/system/nscd.conf34
-rw-r--r--nixpkgs/nixos/modules/services/system/nscd.nix77
-rw-r--r--nixpkgs/nixos/modules/services/system/saslauthd.nix62
-rw-r--r--nixpkgs/nixos/modules/services/system/uptimed.nix55
-rw-r--r--nixpkgs/nixos/modules/services/torrent/deluge.nix254
-rw-r--r--nixpkgs/nixos/modules/services/torrent/flexget.nix100
-rw-r--r--nixpkgs/nixos/modules/services/torrent/magnetico.nix214
-rw-r--r--nixpkgs/nixos/modules/services/torrent/opentracker.nix45
-rw-r--r--nixpkgs/nixos/modules/services/torrent/peerflix.nix65
-rw-r--r--nixpkgs/nixos/modules/services/torrent/transmission.nix185
-rw-r--r--nixpkgs/nixos/modules/services/ttys/agetty.nix118
-rw-r--r--nixpkgs/nixos/modules/services/ttys/gpm.nix57
-rw-r--r--nixpkgs/nixos/modules/services/ttys/kmscon.nix100
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix197
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix164
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix204
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/codimd.nix915
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/cryptpad.nix54
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/documize.nix138
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/frab.nix223
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix244
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix157
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/limesurvey.nix283
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml113
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/matomo.nix310
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/mattermost.nix230
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/mediawiki.nix468
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/miniflux.nix97
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/moodle.nix315
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nextcloud.nix558
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nextcloud.xml117
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nexus.nix133
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix75
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/restya-board.nix383
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/selfoss.nix165
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/tt-rss.nix676
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/virtlyst.nix72
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/wordpress.nix373
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/youtrack.nix178
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/zabbix.nix223
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix719
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix180
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/caddy.nix105
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix77
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix72
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/hitch/default.nix108
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/hydron.nix165
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh72
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/jboss/default.nix80
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix91
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix58
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix256
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix52
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/meguca.nix174
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix132
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/minio.nix108
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/default.nix709
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix61
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix95
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix228
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix279
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/shellinabox.nix122
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/tomcat.nix425
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/traefik.nix124
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/unit/default.nix125
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/uwsgi.nix160
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/varnish/default.nix113
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/zope2.nix258
-rw-r--r--nixpkgs/nixos/modules/services/x11/clight.nix115
-rw-r--r--nixpkgs/nixos/modules/services/x11/colord.nix41
-rw-r--r--nixpkgs/nixos/modules/services/x11/compton.nix287
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix113
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/enlightenment.nix100
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix352
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix30
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/lumina.nix45
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/lxqt.nix67
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix110
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/maxx.nix31
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix7
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix218
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix266
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix127
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix123
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/xfce4-14.nix156
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/xterm.nix38
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/auto.nix68
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/default.nix377
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix294
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix140
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix173
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix95
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix42
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix290
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix298
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/slim.nix156
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/startx.nix44
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix252
-rw-r--r--nixpkgs/nixos/modules/services/x11/extra-layouts.nix168
-rw-r--r--nixpkgs/nixos/modules/services/x11/fractalart.nix36
-rw-r--r--nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix45
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/cmt.nix54
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/libinput.nix247
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/multitouch.nix94
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix213
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/wacom.nix47
-rw-r--r--nixpkgs/nixos/modules/services/x11/redshift.nix129
-rw-r--r--nixpkgs/nixos/modules/services/x11/terminal-server.nix56
-rw-r--r--nixpkgs/nixos/modules/services/x11/unclutter-xfixes.nix58
-rw-r--r--nixpkgs/nixos/modules/services/x11/unclutter.nix74
-rw-r--r--nixpkgs/nixos/modules/services/x11/urxvtd.nix48
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix37
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix66
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix77
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix24
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/default.nix79
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix37
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix54
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/fvwm.nix41
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix38
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/i3.nix78
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix27
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix30
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/none.nix12
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/notion.nix26
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix24
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix27
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix24
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/twm.nix37
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix22
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix39
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix97
-rw-r--r--nixpkgs/nixos/modules/services/x11/xautolock.nix140
-rw-r--r--nixpkgs/nixos/modules/services/x11/xbanish.nix31
-rw-r--r--nixpkgs/nixos/modules/services/x11/xfs.conf15
-rw-r--r--nixpkgs/nixos/modules/services/x11/xfs.nix46
-rw-r--r--nixpkgs/nixos/modules/services/x11/xserver.nix822
-rw-r--r--nixpkgs/nixos/modules/system/activation/activation-script.nix229
-rw-r--r--nixpkgs/nixos/modules/system/activation/no-clone.nix9
-rw-r--r--nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl489
-rw-r--r--nixpkgs/nixos/modules/system/activation/top-level.nix278
-rw-r--r--nixpkgs/nixos/modules/system/boot/binfmt.nix272
-rw-r--r--nixpkgs/nixos/modules/system/boot/emergency-mode.nix37
-rw-r--r--nixpkgs/nixos/modules/system/boot/grow-partition.nix50
-rw-r--r--nixpkgs/nixos/modules/system/boot/initrd-network.nix138
-rw-r--r--nixpkgs/nixos/modules/system/boot/initrd-ssh.nix132
-rw-r--r--nixpkgs/nixos/modules/system/boot/kernel.nix326
-rw-r--r--nixpkgs/nixos/modules/system/boot/kernel_config.nix136
-rw-r--r--nixpkgs/nixos/modules/system/boot/kexec.nix32
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/efi.nix20
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh106
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix65
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix44
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix8
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh140
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix706
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl637
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix64
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix93
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh93
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix51
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/loader.nix15
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix10
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh132
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix108
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix35
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh38
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py240
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix143
-rw-r--r--nixpkgs/nixos/modules/system/boot/luksroot.nix791
-rw-r--r--nixpkgs/nixos/modules/system/boot/modprobe.nix68
-rw-r--r--nixpkgs/nixos/modules/system/boot/networkd.nix963
-rw-r--r--nixpkgs/nixos/modules/system/boot/pbkdf2-sha512.c38
-rw-r--r--nixpkgs/nixos/modules/system/boot/plymouth.nix163
-rw-r--r--nixpkgs/nixos/modules/system/boot/resolved.nix174
-rw-r--r--nixpkgs/nixos/modules/system/boot/shutdown.nix27
-rw-r--r--nixpkgs/nixos/modules/system/boot/stage-1-init.sh572
-rw-r--r--nixpkgs/nixos/modules/system/boot/stage-1.nix570
-rw-r--r--nixpkgs/nixos/modules/system/boot/stage-2-init.sh172
-rw-r--r--nixpkgs/nixos/modules/system/boot/stage-2.nix85
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd-lib.nix217
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix123
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix518
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd.nix994
-rw-r--r--nixpkgs/nixos/modules/system/boot/timesyncd.nix54
-rw-r--r--nixpkgs/nixos/modules/system/boot/tmp.nix39
-rw-r--r--nixpkgs/nixos/modules/system/etc/etc.nix162
-rw-r--r--nixpkgs/nixos/modules/system/etc/make-etc.sh46
-rw-r--r--nixpkgs/nixos/modules/system/etc/setup-etc.pl140
-rw-r--r--nixpkgs/nixos/modules/tasks/auto-upgrade.nix114
-rw-r--r--nixpkgs/nixos/modules/tasks/bcache.nix13
-rw-r--r--nixpkgs/nixos/modules/tasks/cpu-freq.nix90
-rw-r--r--nixpkgs/nixos/modules/tasks/encrypted-devices.nix76
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems.nix326
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/bcachefs.nix65
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/btrfs.nix132
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/cifs.nix25
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/ecryptfs.nix14
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/exfat.nix11
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/ext.nix22
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/f2fs.nix25
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/glusterfs.nix11
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/jfs.nix19
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/nfs.nix107
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/ntfs.nix11
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/reiserfs.nix25
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/unionfs-fuse.nix32
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/vboxsf.nix23
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/vfat.nix25
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/xfs.nix30
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/zfs.nix562
-rw-r--r--nixpkgs/nixos/modules/tasks/kbd.nix127
-rw-r--r--nixpkgs/nixos/modules/tasks/lvm.nix17
-rw-r--r--nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix523
-rw-r--r--nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix243
-rw-r--r--nixpkgs/nixos/modules/tasks/network-interfaces.nix1183
-rw-r--r--nixpkgs/nixos/modules/tasks/powertop.nix28
-rw-r--r--nixpkgs/nixos/modules/tasks/scsi-link-power-management.nix54
-rw-r--r--nixpkgs/nixos/modules/tasks/swraid.nix17
-rw-r--r--nixpkgs/nixos/modules/tasks/trackpoint.nix107
-rw-r--r--nixpkgs/nixos/modules/tasks/tty-backgrounds-combine.sh32
-rw-r--r--nixpkgs/nixos/modules/testing/minimal-kernel.nix28
-rw-r--r--nixpkgs/nixos/modules/testing/service-runner.nix115
-rw-r--r--nixpkgs/nixos/modules/testing/test-instrumentation.nix134
-rw-r--r--nixpkgs/nixos/modules/virtualisation/amazon-image.nix153
-rw-r--r--nixpkgs/nixos/modules/virtualisation/amazon-init.nix61
-rw-r--r--nixpkgs/nixos/modules/virtualisation/amazon-options.nix21
-rw-r--r--nixpkgs/nixos/modules/virtualisation/anbox.nix139
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-agent-entropy.patch17
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-agent.nix198
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-bootstrap-blobs.nix3
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-common.nix64
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-config-user.nix12
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-config.nix5
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-image.nix59
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-images.nix5
-rw-r--r--nixpkgs/nixos/modules/virtualisation/brightbox-config.nix5
-rw-r--r--nixpkgs/nixos/modules/virtualisation/brightbox-image.nix166
-rw-r--r--nixpkgs/nixos/modules/virtualisation/cloudstack-config.nix40
-rw-r--r--nixpkgs/nixos/modules/virtualisation/container-config.nix29
-rw-r--r--nixpkgs/nixos/modules/virtualisation/containers.nix828
-rw-r--r--nixpkgs/nixos/modules/virtualisation/cri-o.nix106
-rw-r--r--nixpkgs/nixos/modules/virtualisation/docker-containers.nix230
-rw-r--r--nixpkgs/nixos/modules/virtualisation/docker-image.nix57
-rw-r--r--nixpkgs/nixos/modules/virtualisation/docker-preloader.nix134
-rw-r--r--nixpkgs/nixos/modules/virtualisation/docker.nix223
-rw-r--r--nixpkgs/nixos/modules/virtualisation/ec2-amis.nix295
-rw-r--r--nixpkgs/nixos/modules/virtualisation/ec2-data.nix87
-rw-r--r--nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.nix23
-rw-r--r--nixpkgs/nixos/modules/virtualisation/ecs-agent.nix46
-rw-r--r--nixpkgs/nixos/modules/virtualisation/gce-images.nix9
-rw-r--r--nixpkgs/nixos/modules/virtualisation/google-compute-config.nix148
-rw-r--r--nixpkgs/nixos/modules/virtualisation/google-compute-image.nix61
-rw-r--r--nixpkgs/nixos/modules/virtualisation/grow-partition.nix3
-rw-r--r--nixpkgs/nixos/modules/virtualisation/hyperv-guest.nix64
-rw-r--r--nixpkgs/nixos/modules/virtualisation/kvmgt.nix82
-rw-r--r--nixpkgs/nixos/modules/virtualisation/libvirtd.nix264
-rw-r--r--nixpkgs/nixos/modules/virtualisation/lxc-container.nix26
-rw-r--r--nixpkgs/nixos/modules/virtualisation/lxc.nix82
-rw-r--r--nixpkgs/nixos/modules/virtualisation/lxcfs.nix45
-rw-r--r--nixpkgs/nixos/modules/virtualisation/lxd.nix83
-rw-r--r--nixpkgs/nixos/modules/virtualisation/openstack-config.nix58
-rw-r--r--nixpkgs/nixos/modules/virtualisation/openvswitch.nix176
-rw-r--r--nixpkgs/nixos/modules/virtualisation/parallels-guest.nix156
-rw-r--r--nixpkgs/nixos/modules/virtualisation/qemu-guest-agent.nix36
-rw-r--r--nixpkgs/nixos/modules/virtualisation/qemu-vm.nix585
-rw-r--r--nixpkgs/nixos/modules/virtualisation/railcar.nix125
-rw-r--r--nixpkgs/nixos/modules/virtualisation/rkt.nix64
-rw-r--r--nixpkgs/nixos/modules/virtualisation/virtualbox-guest.nix93
-rw-r--r--nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix153
-rw-r--r--nixpkgs/nixos/modules/virtualisation/virtualbox-image.nix111
-rw-r--r--nixpkgs/nixos/modules/virtualisation/vmware-guest.nix56
-rw-r--r--nixpkgs/nixos/modules/virtualisation/xe-guest-utilities.nix52
-rw-r--r--nixpkgs/nixos/modules/virtualisation/xen-dom0.nix461
-rw-r--r--nixpkgs/nixos/modules/virtualisation/xen-domU.nix19
1199 files changed, 165167 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/config/appstream.nix b/nixpkgs/nixos/modules/config/appstream.nix
new file mode 100644
index 00000000000..483ac9c3cd7
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/config/debug-info.nix b/nixpkgs/nixos/modules/config/debug-info.nix
new file mode 100644
index 00000000000..2942ae5905d
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/debug-info.nix
@@ -0,0 +1,45 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+
+ options = {
+
+ environment.enableDebugInfo = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Some NixOS packages provide debug symbols. However, these are
+ not included in the system closure by default to save disk
+ space. Enabling this option causes the debug symbols to appear
+ in <filename>/run/current-system/sw/lib/debug/.build-id</filename>,
+ where tools such as <command>gdb</command> can find them.
+ If you need debug symbols for a package that doesn't
+ provide them by default, you can enable them as follows:
+ <programlisting>
+ nixpkgs.config.packageOverrides = pkgs: {
+ hello = pkgs.hello.overrideAttrs (oldAttrs: {
+ separateDebugInfo = true;
+ });
+ };
+ </programlisting>
+ '';
+ };
+
+ };
+
+
+ config = mkIf config.environment.enableDebugInfo {
+
+ # FIXME: currently disabled because /lib is already in
+ # environment.pathsToLink, and we can't have both.
+ #environment.pathsToLink = [ "/lib/debug/.build-id" ];
+
+ environment.extraOutputsToInstall = [ "debug" ];
+
+ environment.variables.NIX_DEBUG_INFO_DIRS = [ "/run/current-system/sw/lib/debug" ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/fonts/corefonts.nix b/nixpkgs/nixos/modules/config/fonts/corefonts.nix
new file mode 100644
index 00000000000..b9f69879a10
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/fonts/corefonts.nix
@@ -0,0 +1,36 @@
+# This module is deprecated, since you can just say ‘fonts.fonts = [
+# pkgs.corefonts ];’ instead.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ options = {
+
+ fonts = {
+
+ enableCoreFonts = mkOption {
+ visible = false;
+ default = false;
+ description = ''
+ Whether to include Microsoft's proprietary Core Fonts. These fonts
+ are redistributable, but only verbatim, among other restrictions.
+ See <link xlink:href="http://corefonts.sourceforge.net/eula.htm"/>
+ for details.
+ '';
+ };
+
+ };
+
+ };
+
+
+ config = mkIf config.fonts.enableCoreFonts {
+
+ fonts.fonts = [ pkgs.corefonts ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/fonts/fontconfig-penultimate.nix b/nixpkgs/nixos/modules/config/fonts/fontconfig-penultimate.nix
new file mode 100644
index 00000000000..7e311a21acf
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/fonts/fontconfig-penultimate.nix
@@ -0,0 +1,292 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.fonts.fontconfig;
+
+ fcBool = x: "<bool>" + (boolToString x) + "</bool>";
+
+ # back-supported fontconfig version and package
+ # version is used for font cache generation
+ supportVersion = "210";
+ supportPkg = pkgs."fontconfig_${supportVersion}";
+
+ # latest fontconfig version and package
+ # version is used for configuration folder name, /etc/fonts/VERSION/
+ # note: format differs from supportVersion and can not be used with makeCacheConf
+ latestVersion = pkgs.fontconfig.configVersion;
+ latestPkg = pkgs.fontconfig;
+
+ # supported version fonts.conf
+ supportFontsConf = pkgs.makeFontsConf { fontconfig = supportPkg; fontDirectories = config.fonts.fonts; };
+
+ # configuration file to read fontconfig cache
+ # version dependent
+ # priority 0
+ cacheConfSupport = makeCacheConf { version = supportVersion; };
+ cacheConfLatest = makeCacheConf {};
+
+ # generate the font cache setting file for a fontconfig version
+ # use latest when no version is passed
+ makeCacheConf = { version ? null }:
+ let
+ fcPackage = if version == null
+ then "fontconfig"
+ else "fontconfig_${version}";
+ makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
+ cache = makeCache pkgs.${fcPackage};
+ cache32 = makeCache pkgs.pkgsi686Linux.${fcPackage};
+ in
+ pkgs.writeText "fc-00-nixos-cache.conf" ''
+ <?xml version='1.0'?>
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+ <!-- Font directories -->
+ ${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.fonts)}
+ <!-- Pre-generated font caches -->
+ <cachedir>${cache}</cachedir>
+ ${optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) ''
+ <cachedir>${cache32}</cachedir>
+ ''}
+ </fontconfig>
+ '';
+
+ # local configuration file
+ localConf = pkgs.writeText "fc-local.conf" cfg.localConf;
+
+ # rendering settings configuration files
+ # priority 10
+ hintingConf = pkgs.writeText "fc-10-hinting.conf" ''
+ <?xml version='1.0'?>
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+
+ <!-- Default rendering settings -->
+ <match target="pattern">
+ <edit mode="append" name="hinting">
+ ${fcBool cfg.hinting.enable}
+ </edit>
+ <edit mode="append" name="autohint">
+ ${fcBool cfg.hinting.autohint}
+ </edit>
+ <edit mode="append" name="hintstyle">
+ <const>hintslight</const>
+ </edit>
+ </match>
+
+ </fontconfig>
+ '';
+
+ antialiasConf = pkgs.writeText "fc-10-antialias.conf" ''
+ <?xml version='1.0'?>
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+
+ <!-- Default rendering settings -->
+ <match target="pattern">
+ <edit mode="append" name="antialias">
+ ${fcBool cfg.antialias}
+ </edit>
+ </match>
+
+ </fontconfig>
+ '';
+
+ subpixelConf = pkgs.writeText "fc-10-subpixel.conf" ''
+ <?xml version='1.0'?>
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+
+ <!-- Default rendering settings -->
+ <match target="pattern">
+ <edit mode="append" name="rgba">
+ <const>${cfg.subpixel.rgba}</const>
+ </edit>
+ <edit mode="append" name="lcdfilter">
+ <const>lcd${cfg.subpixel.lcdfilter}</const>
+ </edit>
+ </match>
+
+ </fontconfig>
+ '';
+
+ dpiConf = pkgs.writeText "fc-11-dpi.conf" ''
+ <?xml version='1.0'?>
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+
+ <match target="pattern">
+ <edit name="dpi" mode="assign">
+ <double>${toString cfg.dpi}</double>
+ </edit>
+ </match>
+
+ </fontconfig>
+ '';
+
+ # default fonts configuration file
+ # priority 52
+ defaultFontsConf =
+ let genDefault = fonts: name:
+ optionalString (fonts != []) ''
+ <alias>
+ <family>${name}</family>
+ <prefer>
+ ${concatStringsSep ""
+ (map (font: ''
+ <family>${font}</family>
+ '') fonts)}
+ </prefer>
+ </alias>
+ '';
+ in
+ pkgs.writeText "fc-52-nixos-default-fonts.conf" ''
+ <?xml version='1.0'?>
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+
+ <!-- Default fonts -->
+ ${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
+
+ ${genDefault cfg.defaultFonts.serif "serif"}
+
+ ${genDefault cfg.defaultFonts.monospace "monospace"}
+
+ </fontconfig>
+ '';
+
+ # reject Type 1 fonts
+ # priority 53
+ rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" ''
+ <?xml version="1.0"?>
+ <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+ <fontconfig>
+
+ <!-- Reject Type 1 fonts -->
+ <selectfont>
+ <rejectfont>
+ <pattern>
+ <patelt name="fontformat"><string>Type 1</string></patelt>
+ </pattern>
+ </rejectfont>
+ </selectfont>
+
+ </fontconfig>
+ '';
+
+ # The configuration to be included in /etc/font/
+ penultimateConf = pkgs.runCommand "fontconfig-penultimate-conf" {
+ preferLocalBuild = true;
+ } ''
+ support_folder=$out/etc/fonts/conf.d
+ latest_folder=$out/etc/fonts/${latestVersion}/conf.d
+
+ mkdir -p $support_folder
+ mkdir -p $latest_folder
+
+ # fonts.conf
+ ln -s ${supportFontsConf} $support_folder/../fonts.conf
+ ln -s ${latestPkg.out}/etc/fonts/fonts.conf \
+ $latest_folder/../fonts.conf
+
+ # fontconfig-penultimate various configuration files
+ ln -s ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/*.conf \
+ $support_folder
+ ln -s ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/*.conf \
+ $latest_folder
+
+ ln -s ${cacheConfSupport} $support_folder/00-nixos-cache.conf
+ ln -s ${cacheConfLatest} $latest_folder/00-nixos-cache.conf
+
+ rm $support_folder/10-antialias.conf $latest_folder/10-antialias.conf
+ ln -s ${antialiasConf} $support_folder/10-antialias.conf
+ ln -s ${antialiasConf} $latest_folder/10-antialias.conf
+
+ rm $support_folder/10-hinting.conf $latest_folder/10-hinting.conf
+ ln -s ${hintingConf} $support_folder/10-hinting.conf
+ ln -s ${hintingConf} $latest_folder/10-hinting.conf
+
+ ${optionalString cfg.useEmbeddedBitmaps ''
+ rm $support_folder/10-no-embedded-bitmaps.conf
+ rm $latest_folder/10-no-embedded-bitmaps.conf
+ ''}
+
+ rm $support_folder/10-subpixel.conf $latest_folder/10-subpixel.conf
+ ln -s ${subpixelConf} $support_folder/10-subpixel.conf
+ ln -s ${subpixelConf} $latest_folder/10-subpixel.conf
+
+ ${optionalString (cfg.dpi != 0) ''
+ ln -s ${dpiConf} $support_folder/11-dpi.conf
+ ln -s ${dpiConf} $latest_folder/11-dpi.conf
+ ''}
+
+ # 50-user.conf
+ ${optionalString (!cfg.includeUserConf) ''
+ rm $support_folder/50-user.conf
+ rm $latest_folder/50-user.conf
+ ''}
+
+ # 51-local.conf
+ rm $latest_folder/51-local.conf
+ substitute \
+ ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/51-local.conf \
+ $latest_folder/51-local.conf \
+ --replace local.conf /etc/fonts/${latestVersion}/local.conf
+
+ # local.conf (indirect priority 51)
+ ${optionalString (cfg.localConf != "") ''
+ ln -s ${localConf} $support_folder/../local.conf
+ ln -s ${localConf} $latest_folder/../local.conf
+ ''}
+
+ # 52-nixos-default-fonts.conf
+ ln -s ${defaultFontsConf} $support_folder/52-nixos-default-fonts.conf
+ ln -s ${defaultFontsConf} $latest_folder/52-nixos-default-fonts.conf
+
+ # 53-no-bitmaps.conf
+ ${optionalString cfg.allowBitmaps ''
+ rm $support_folder/53-no-bitmaps.conf
+ rm $latest_folder/53-no-bitmaps.conf
+ ''}
+
+ ${optionalString (!cfg.allowType1) ''
+ # 53-nixos-reject-type1.conf
+ ln -s ${rejectType1} $support_folder/53-nixos-reject-type1.conf
+ ln -s ${rejectType1} $latest_folder/53-nixos-reject-type1.conf
+ ''}
+ '';
+
+in
+{
+
+ options = {
+
+ fonts = {
+
+ fontconfig = {
+
+ penultimate = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable fontconfig-penultimate settings to supplement the
+ NixOS defaults by providing per-font rendering defaults and
+ metric aliases.
+ '';
+ };
+ };
+
+ };
+ };
+
+ };
+
+ config = mkIf (config.fonts.fontconfig.enable && config.fonts.fontconfig.penultimate.enable) {
+
+ fonts.fontconfig.confPackages = [ penultimateConf ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/fonts/fontconfig-ultimate.nix b/nixpkgs/nixos/modules/config/fonts/fontconfig-ultimate.nix
new file mode 100644
index 00000000000..84d90899dff
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/fonts/fontconfig-ultimate.nix
@@ -0,0 +1,86 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let cfg = config.fonts.fontconfig.ultimate;
+
+ latestVersion = pkgs.fontconfig.configVersion;
+
+ # The configuration to be included in /etc/font/
+ confPkg = pkgs.runCommand "font-ultimate-conf" { preferLocalBuild = true; } ''
+ support_folder=$out/etc/fonts/conf.d
+ latest_folder=$out/etc/fonts/${latestVersion}/conf.d
+
+ mkdir -p $support_folder
+ mkdir -p $latest_folder
+
+ # fontconfig ultimate substitutions
+ ${optionalString (cfg.substitutions != "none") ''
+ ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/presets/${cfg.substitutions}/*.conf \
+ $support_folder
+ ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/presets/${cfg.substitutions}/*.conf \
+ $latest_folder
+ ''}
+
+ # fontconfig ultimate various configuration files
+ ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/conf.d/*.conf \
+ $support_folder
+ ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/conf.d/*.conf \
+ $latest_folder
+ '';
+
+in
+{
+
+ options = {
+
+ fonts = {
+
+ fontconfig = {
+
+ ultimate = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable fontconfig-ultimate settings (formerly known as
+ Infinality). Besides the customizable settings in this NixOS
+ module, fontconfig-ultimate also provides many font-specific
+ rendering tweaks.
+ '';
+ };
+
+ substitutions = mkOption {
+ type = types.enum ["free" "combi" "ms" "none"];
+ default = "free";
+ description = ''
+ Font substitutions to replace common Type 1 fonts with nicer
+ TrueType fonts. <literal>free</literal> uses free fonts,
+ <literal>ms</literal> uses Microsoft fonts,
+ <literal>combi</literal> uses a combination, and
+ <literal>none</literal> disables the substitutions.
+ '';
+ };
+
+ preset = mkOption {
+ type = types.enum ["ultimate1" "ultimate2" "ultimate3" "ultimate4" "ultimate5" "osx" "windowsxp"];
+ default = "ultimate3";
+ description = ''
+ FreeType rendering settings preset. Any of the presets may be
+ customized by setting environment variables.
+ '';
+ };
+ };
+ };
+ };
+
+ };
+
+ config = mkIf (config.fonts.fontconfig.enable && cfg.enable) {
+
+ fonts.fontconfig.confPackages = [ confPkg ];
+ environment.variables.INFINALITY_FT = cfg.preset;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/fonts/fontconfig.nix b/nixpkgs/nixos/modules/config/fonts/fontconfig.nix
new file mode 100644
index 00000000000..8f227c42326
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/fonts/fontconfig.nix
@@ -0,0 +1,476 @@
+/*
+
+NixOS support 2 fontconfig versions, "support" and "latest".
+
+- "latest" refers to default fontconfig package (pkgs.fontconfig).
+ configuration files are linked to /etc/fonts/VERSION/conf.d/
+- "support" refers to supportPkg (pkgs."fontconfig_${supportVersion}").
+ configuration files are linked to /etc/fonts/conf.d/
+
+This module generates a package containing configuration files and link it in /etc/fonts.
+
+Fontconfig reads files in folder name / file name order, so the number prepended to the configuration file name decide the order of parsing.
+Low number means high priority.
+
+*/
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.fonts.fontconfig;
+
+ fcBool = x: "<bool>" + (boolToString x) + "</bool>";
+
+ # back-supported fontconfig version and package
+ # version is used for font cache generation
+ supportVersion = "210";
+ supportPkg = pkgs."fontconfig_${supportVersion}";
+
+ # latest fontconfig version and package
+ # version is used for configuration folder name, /etc/fonts/VERSION/
+ # note: format differs from supportVersion and can not be used with makeCacheConf
+ latestVersion = pkgs.fontconfig.configVersion;
+ latestPkg = pkgs.fontconfig;
+
+ # supported version fonts.conf
+ supportFontsConf = pkgs.makeFontsConf { fontconfig = supportPkg; fontDirectories = config.fonts.fonts; };
+
+ # configuration file to read fontconfig cache
+ # version dependent
+ # priority 0
+ cacheConfSupport = makeCacheConf { version = supportVersion; };
+ cacheConfLatest = makeCacheConf {};
+
+ # generate the font cache setting file for a fontconfig version
+ # use latest when no version is passed
+ makeCacheConf = { version ? null }:
+ let
+ fcPackage = if version == null
+ then "fontconfig"
+ else "fontconfig_${version}";
+ makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
+ cache = makeCache pkgs.${fcPackage};
+ cache32 = makeCache pkgs.pkgsi686Linux.${fcPackage};
+ in
+ pkgs.writeText "fc-00-nixos-cache.conf" ''
+ <?xml version='1.0'?>
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+ <!-- Font directories -->
+ ${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.fonts)}
+ <!-- Pre-generated font caches -->
+ <cachedir>${cache}</cachedir>
+ ${optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) ''
+ <cachedir>${cache32}</cachedir>
+ ''}
+ </fontconfig>
+ '';
+
+ # rendering settings configuration file
+ # priority 10
+ renderConf = pkgs.writeText "fc-10-nixos-rendering.conf" ''
+ <?xml version='1.0'?>
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+
+ <!-- Default rendering settings -->
+ <match target="pattern">
+ <edit mode="append" name="hinting">
+ ${fcBool cfg.hinting.enable}
+ </edit>
+ <edit mode="append" name="autohint">
+ ${fcBool cfg.hinting.autohint}
+ </edit>
+ <edit mode="append" name="hintstyle">
+ <const>hintslight</const>
+ </edit>
+ <edit mode="append" name="antialias">
+ ${fcBool cfg.antialias}
+ </edit>
+ <edit mode="append" name="rgba">
+ <const>${cfg.subpixel.rgba}</const>
+ </edit>
+ <edit mode="append" name="lcdfilter">
+ <const>lcd${cfg.subpixel.lcdfilter}</const>
+ </edit>
+ </match>
+
+ ${optionalString (cfg.dpi != 0) ''
+ <match target="pattern">
+ <edit name="dpi" mode="assign">
+ <double>${toString cfg.dpi}</double>
+ </edit>
+ </match>
+ ''}
+
+ </fontconfig>
+ '';
+
+ # local configuration file
+ localConf = pkgs.writeText "fc-local.conf" cfg.localConf;
+
+ # default fonts configuration file
+ # priority 52
+ defaultFontsConf =
+ let genDefault = fonts: name:
+ optionalString (fonts != []) ''
+ <alias binding="same">
+ <family>${name}</family>
+ <prefer>
+ ${concatStringsSep ""
+ (map (font: ''
+ <family>${font}</family>
+ '') fonts)}
+ </prefer>
+ </alias>
+ '';
+ in
+ pkgs.writeText "fc-52-nixos-default-fonts.conf" ''
+ <?xml version='1.0'?>
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+
+ <!-- Default fonts -->
+ ${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
+
+ ${genDefault cfg.defaultFonts.serif "serif"}
+
+ ${genDefault cfg.defaultFonts.monospace "monospace"}
+
+ ${genDefault cfg.defaultFonts.emoji "emoji"}
+
+ </fontconfig>
+ '';
+
+ # bitmap font options
+ # priority 53
+ rejectBitmaps = pkgs.writeText "fc-53-no-bitmaps.conf" ''
+ <?xml version="1.0"?>
+ <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+ <fontconfig>
+
+ ${optionalString (!cfg.allowBitmaps) ''
+ <!-- Reject bitmap fonts -->
+ <selectfont>
+ <rejectfont>
+ <pattern>
+ <patelt name="scalable"><bool>false</bool></patelt>
+ </pattern>
+ </rejectfont>
+ </selectfont>
+ ''}
+
+ <!-- Use embedded bitmaps in fonts like Calibri? -->
+ <match target="font">
+ <edit name="embeddedbitmap" mode="assign">
+ ${fcBool cfg.useEmbeddedBitmaps}
+ </edit>
+ </match>
+
+ </fontconfig>
+ '';
+
+ # reject Type 1 fonts
+ # priority 53
+ rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" ''
+ <?xml version="1.0"?>
+ <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+ <fontconfig>
+
+ <!-- Reject Type 1 fonts -->
+ <selectfont>
+ <rejectfont>
+ <pattern>
+ <patelt name="fontformat"><string>Type 1</string></patelt>
+ </pattern>
+ </rejectfont>
+ </selectfont>
+
+ </fontconfig>
+ '';
+
+ # fontconfig configuration package
+ confPkg = pkgs.runCommand "fontconfig-conf" {
+ preferLocalBuild = true;
+ } ''
+ support_folder=$out/etc/fonts/conf.d
+ latest_folder=$out/etc/fonts/${latestVersion}/conf.d
+
+ mkdir -p $support_folder
+ mkdir -p $latest_folder
+
+ # fonts.conf
+ ln -s ${supportFontsConf} $support_folder/../fonts.conf
+ ln -s ${latestPkg.out}/etc/fonts/fonts.conf \
+ $latest_folder/../fonts.conf
+
+ # fontconfig default config files
+ ln -s ${supportPkg.out}/etc/fonts/conf.d/*.conf \
+ $support_folder/
+ ln -s ${latestPkg.out}/etc/fonts/conf.d/*.conf \
+ $latest_folder/
+
+ # update latest 51-local.conf path to look at the latest local.conf
+ rm $latest_folder/51-local.conf
+
+ substitute ${latestPkg.out}/etc/fonts/conf.d/51-local.conf \
+ $latest_folder/51-local.conf \
+ --replace local.conf /etc/fonts/${latestVersion}/local.conf
+
+ # 00-nixos-cache.conf
+ ln -s ${cacheConfSupport} \
+ $support_folder/00-nixos-cache.conf
+ ln -s ${cacheConfLatest} $latest_folder/00-nixos-cache.conf
+
+ # 10-nixos-rendering.conf
+ ln -s ${renderConf} $support_folder/10-nixos-rendering.conf
+ ln -s ${renderConf} $latest_folder/10-nixos-rendering.conf
+
+ # 50-user.conf
+ ${optionalString (!cfg.includeUserConf) ''
+ rm $support_folder/50-user.conf
+ rm $latest_folder/50-user.conf
+ ''}
+
+ # local.conf (indirect priority 51)
+ ${optionalString (cfg.localConf != "") ''
+ ln -s ${localConf} $support_folder/../local.conf
+ ln -s ${localConf} $latest_folder/../local.conf
+ ''}
+
+ # 52-nixos-default-fonts.conf
+ ln -s ${defaultFontsConf} $support_folder/52-nixos-default-fonts.conf
+ ln -s ${defaultFontsConf} $latest_folder/52-nixos-default-fonts.conf
+
+ # 53-no-bitmaps.conf
+ ln -s ${rejectBitmaps} $support_folder/53-no-bitmaps.conf
+ ln -s ${rejectBitmaps} $latest_folder/53-no-bitmaps.conf
+
+ ${optionalString (!cfg.allowType1) ''
+ # 53-nixos-reject-type1.conf
+ ln -s ${rejectType1} $support_folder/53-nixos-reject-type1.conf
+ ln -s ${rejectType1} $latest_folder/53-nixos-reject-type1.conf
+ ''}
+ '';
+
+ # Package with configuration files
+ # this merge all the packages in the fonts.fontconfig.confPackages list
+ fontconfigEtc = pkgs.buildEnv {
+ name = "fontconfig-etc";
+ paths = cfg.confPackages;
+ ignoreCollisions = true;
+ };
+in
+{
+
+ options = {
+
+ fonts = {
+
+ fontconfig = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If enabled, a Fontconfig configuration file will be built
+ pointing to a set of default fonts. If you don't care about
+ running X11 applications or any other program that uses
+ Fontconfig, you can turn this option off and prevent a
+ dependency on all those fonts.
+ '';
+ };
+
+ confPackages = mkOption {
+ internal = true;
+ type = with types; listOf path;
+ default = [ ];
+ description = ''
+ Fontconfig configuration packages.
+ '';
+ };
+
+ antialias = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable font antialiasing. At high resolution (> 200 DPI),
+ antialiasing has no visible effect; users of such displays may want
+ to disable this option.
+ '';
+ };
+
+ dpi = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Force DPI setting. Setting to <literal>0</literal> disables DPI
+ forcing; the DPI detected for the display will be used.
+ '';
+ };
+
+ localConf = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ System-wide customization file contents, has higher priority than
+ <literal>defaultFonts</literal> settings.
+ '';
+ };
+
+ defaultFonts = {
+ monospace = mkOption {
+ type = types.listOf types.str;
+ default = ["DejaVu Sans Mono"];
+ description = ''
+ System-wide default monospace font(s). Multiple fonts may be
+ listed in case multiple languages must be supported.
+ '';
+ };
+
+ sansSerif = mkOption {
+ type = types.listOf types.str;
+ default = ["DejaVu Sans"];
+ description = ''
+ System-wide default sans serif font(s). Multiple fonts may be
+ listed in case multiple languages must be supported.
+ '';
+ };
+
+ serif = mkOption {
+ type = types.listOf types.str;
+ default = ["DejaVu Serif"];
+ description = ''
+ System-wide default serif font(s). Multiple fonts may be listed
+ in case multiple languages must be supported.
+ '';
+ };
+
+ emoji = mkOption {
+ type = types.listOf types.str;
+ default = ["Noto Color Emoji"];
+ description = ''
+ System-wide default emoji font(s). Multiple fonts may be listed
+ in case a font does not support all emoji.
+
+ Note that fontconfig matches color emoji fonts preferentially,
+ so if you want to use a black and white font while having
+ a color font installed (eg. Noto Color Emoji installed alongside
+ Noto Emoji), fontconfig will still choose the color font even
+ when it is later in the list.
+ '';
+ };
+ };
+
+ hinting = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable font hinting. Hinting aligns glyphs to pixel boundaries to
+ improve rendering sharpness at low resolution. At high resolution
+ (> 200 dpi) hinting will do nothing (at best); users of such
+ displays may want to disable this option.
+ '';
+ };
+
+ autohint = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the autohinter in place of the default interpreter.
+ The results are usually lower quality than correctly-hinted
+ fonts, but better than unhinted fonts.
+ '';
+ };
+ };
+
+ includeUserConf = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Include the user configuration from
+ <filename>~/.config/fontconfig/fonts.conf</filename> or
+ <filename>~/.config/fontconfig/conf.d</filename>.
+ '';
+ };
+
+ subpixel = {
+
+ rgba = mkOption {
+ default = "rgb";
+ type = types.enum ["rgb" "bgr" "vrgb" "vbgr" "none"];
+ description = ''
+ Subpixel order. The overwhelming majority of displays are
+ <literal>rgb</literal> in their normal orientation. Select
+ <literal>vrgb</literal> for mounting such a display 90 degrees
+ clockwise from its normal orientation or <literal>vbgr</literal>
+ for mounting 90 degrees counter-clockwise. Select
+ <literal>bgr</literal> in the unlikely event of mounting 180
+ degrees from the normal orientation. Reverse these directions in
+ the improbable event that the display's native subpixel order is
+ <literal>bgr</literal>.
+ '';
+ };
+
+ lcdfilter = mkOption {
+ default = "default";
+ type = types.enum ["none" "default" "light" "legacy"];
+ description = ''
+ FreeType LCD filter. At high resolution (> 200 DPI), LCD filtering
+ has no visible effect; users of such displays may want to select
+ <literal>none</literal>.
+ '';
+ };
+
+ };
+
+ cache32Bit = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Generate system fonts cache for 32-bit applications.
+ '';
+ };
+
+ allowBitmaps = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Allow bitmap fonts. Set to <literal>false</literal> to ban all
+ bitmap fonts.
+ '';
+ };
+
+ allowType1 = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow Type-1 fonts. Default is <literal>false</literal> because of
+ poor rendering.
+ '';
+ };
+
+ useEmbeddedBitmaps = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''Use embedded bitmaps in fonts like Calibri.'';
+ };
+
+ };
+
+ };
+
+ };
+ config = mkMerge [
+ (mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.fontconfig ];
+ environment.etc.fonts.source = "${fontconfigEtc}/etc/fonts/";
+ })
+ (mkIf (cfg.enable && !cfg.penultimate.enable) {
+ fonts.fontconfig.confPackages = [ confPkg ];
+ })
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/config/fonts/fontdir.nix b/nixpkgs/nixos/modules/config/fonts/fontdir.nix
new file mode 100644
index 00000000000..cc70fbf8744
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/fonts/fontdir.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ x11Fonts = pkgs.runCommand "X11-fonts" { preferLocalBuild = true; } ''
+ mkdir -p "$out/share/X11-fonts"
+ find ${toString config.fonts.fonts} \
+ \( -name fonts.dir -o -name '*.ttf' -o -name '*.otf' \) \
+ -exec ln -sf -t "$out/share/X11-fonts" '{}' \;
+ cd "$out/share/X11-fonts"
+ rm -f fonts.dir fonts.scale fonts.alias
+ ${pkgs.xorg.mkfontdir}/bin/mkfontdir
+ ${pkgs.xorg.mkfontscale}/bin/mkfontscale
+ cat $(find ${pkgs.xorg.fontalias}/ -name fonts.alias) >fonts.alias
+ '';
+
+in
+
+{
+
+ options = {
+
+ fonts = {
+
+ enableFontDir = mkOption {
+ default = false;
+ description = ''
+ Whether to create a directory with links to all fonts in
+ <filename>/run/current-system/sw/share/X11-fonts</filename>.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf config.fonts.enableFontDir {
+
+ environment.systemPackages = [ x11Fonts ];
+
+ environment.pathsToLink = [ "/share/X11-fonts" ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/fonts/fonts.nix b/nixpkgs/nixos/modules/config/fonts/fonts.nix
new file mode 100644
index 00000000000..abb806b601a
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/fonts/fonts.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ options = {
+
+ fonts = {
+
+ # TODO: find another name for it.
+ fonts = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ example = literalExample "[ pkgs.dejavu_fonts ]";
+ description = "List of primary font paths.";
+ };
+
+ enableDefaultFonts = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable a basic set of fonts providing several font styles
+ and families and reasonable coverage of Unicode.
+ '';
+ };
+
+ };
+
+ };
+
+ config = {
+
+ fonts.fonts = mkIf config.fonts.enableDefaultFonts
+ [
+ pkgs.xorg.fontbhlucidatypewriter100dpi
+ pkgs.xorg.fontbhlucidatypewriter75dpi
+ pkgs.dejavu_fonts
+ pkgs.freefont_ttf
+ pkgs.gyre-fonts # TrueType substitutes for standard PostScript fonts
+ pkgs.liberation_ttf
+ pkgs.xorg.fontbh100dpi
+ pkgs.xorg.fontmiscmisc
+ pkgs.xorg.fontcursormisc
+ pkgs.unifont
+ pkgs.noto-fonts-emoji
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/fonts/ghostscript.nix b/nixpkgs/nixos/modules/config/fonts/ghostscript.nix
new file mode 100644
index 00000000000..1c62a525de9
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/fonts/ghostscript.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ options = {
+
+ fonts = {
+
+ enableGhostscriptFonts = mkOption {
+ default = false;
+ description = ''
+ Whether to add the fonts provided by Ghostscript (such as
+ various URW fonts and the “Base-14” Postscript fonts) to the
+ list of system fonts, making them available to X11
+ applications.
+ '';
+ };
+
+ };
+
+ };
+
+
+ config = mkIf config.fonts.enableGhostscriptFonts {
+
+ fonts.fonts = [ "${pkgs.ghostscript}/share/ghostscript/fonts" ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/gnu.nix b/nixpkgs/nixos/modules/config/gnu.nix
new file mode 100644
index 00000000000..93d13097019
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/gnu.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options = {
+ gnu = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ When enabled, GNU software is chosen by default whenever a there is
+ a choice between GNU and non-GNU software (e.g., GNU lsh
+ vs. OpenSSH).
+ '';
+ };
+ };
+
+ config = mkIf config.gnu {
+
+ environment.systemPackages = with pkgs;
+ # TODO: Adjust `requiredPackages' from `system-path.nix'.
+ # TODO: Add Inetutils once it has the new `ifconfig'.
+ [ parted
+ #fdisk # XXX: GNU fdisk currently fails to build and it's redundant
+ # with the `parted' command.
+ nano zile
+ texinfo # for the stand-alone Info reader
+ ]
+ ++ stdenv.lib.optional (!stdenv.isAarch32) grub2;
+
+
+ # GNU GRUB, where available.
+ boot.loader.grub.enable = !pkgs.stdenv.isAarch32;
+ boot.loader.grub.version = 2;
+
+ # GNU lsh.
+ services.openssh.enable = false;
+ services.lshd.enable = true;
+ programs.ssh.startAgent = false;
+ services.xserver.startGnuPGAgent = true;
+
+ # TODO: GNU dico.
+ # TODO: GNU Inetutils' inetd.
+ # TODO: GNU Pies.
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix b/nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix
new file mode 100644
index 00000000000..86a6bfb5af4
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/config/i18n.nix b/nixpkgs/nixos/modules/config/i18n.nix
new file mode 100644
index 00000000000..dc7305b1ba2
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/i18n.nix
@@ -0,0 +1,160 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+
+ options = {
+
+ i18n = {
+ glibcLocales = mkOption {
+ type = types.path;
+ default = pkgs.buildPackages.glibcLocales.override {
+ allLocales = any (x: x == "all") config.i18n.supportedLocales;
+ locales = config.i18n.supportedLocales;
+ };
+ example = literalExample "pkgs.glibcLocales";
+ description = ''
+ Customized pkg.glibcLocales package.
+
+ Changing this option can disable handling of i18n.defaultLocale
+ and supportedLocale.
+ '';
+ };
+
+ defaultLocale = mkOption {
+ type = types.str;
+ default = "en_US.UTF-8";
+ example = "nl_NL.UTF-8";
+ description = ''
+ The default locale. It determines the language for program
+ messages, the format for dates and times, sort order, and so on.
+ It also determines the character set, such as UTF-8.
+ '';
+ };
+
+ 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"];
+ example = ["en_US.UTF-8/UTF-8" "nl_NL.UTF-8/UTF-8" "nl_NL/ISO-8859-1"];
+ description = ''
+ List of locales that the system should support. The value
+ <literal>"all"</literal> means that all locales supported by
+ Glibc will be installed. A full list of supported locales
+ can be found at <link
+ xlink:href="https://sourceware.org/git/?p=glibc.git;a=blob;f=localedata/SUPPORTED"/>.
+ '';
+ };
+
+ consolePackages = mkOption {
+ type = types.listOf types.package;
+ default = with pkgs.kbdKeymaps; [ dvp neo ];
+ defaultText = ''with pkgs.kbdKeymaps; [ dvp neo ]'';
+ description = ''
+ List of additional packages that provide console fonts, keymaps and
+ other resources.
+ '';
+ };
+
+ consoleFont = mkOption {
+ type = types.str;
+ default = "Lat2-Terminus16";
+ example = "LatArCyrHeb-16";
+ description = ''
+ The font used for the virtual consoles. Leave empty to use
+ whatever the <command>setfont</command> program considers the
+ default font.
+ '';
+ };
+
+ consoleUseXkbConfig = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If set, configure the console keymap from the xserver keyboard
+ settings.
+ '';
+ };
+
+ consoleKeyMap = mkOption {
+ type = mkOptionType {
+ name = "string or path";
+ check = t: (isString t || types.path.check t);
+ };
+
+ default = "us";
+ example = "fr";
+ description = ''
+ The keyboard mapping table for the virtual consoles.
+ '';
+ };
+
+ consoleColors = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [
+ "002b36" "dc322f" "859900" "b58900"
+ "268bd2" "d33682" "2aa198" "eee8d5"
+ "002b36" "cb4b16" "586e75" "657b83"
+ "839496" "6c71c4" "93a1a1" "fdf6e3"
+ ];
+ description = ''
+ The 16 colors palette used by the virtual consoles.
+ Leave empty to use the default colors.
+ Colors must be in hexadecimal format and listed in
+ order from color 0 to color 15.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ i18n.consoleKeyMap = with config.services.xserver;
+ mkIf config.i18n.consoleUseXkbConfig
+ (pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } ''
+ '${pkgs.ckbcomp}/bin/ckbcomp' -model '${xkbModel}' -layout '${layout}' \
+ -option '${xkbOptions}' -variant '${xkbVariant}' > "$out"
+ '');
+
+ environment.systemPackages =
+ optional (config.i18n.supportedLocales != []) config.i18n.glibcLocales;
+
+ 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";
+ };
+
+ # ‘/etc/locale.conf’ is used by systemd.
+ environment.etc = singleton
+ { target = "locale.conf";
+ source = pkgs.writeText "locale.conf"
+ ''
+ LANG=${config.i18n.defaultLocale}
+ ${concatStringsSep "\n" (mapAttrsToList (n: v: ''${n}=${v}'') config.i18n.extraLocaleSettings)}
+ '';
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/iproute2.nix b/nixpkgs/nixos/modules/config/iproute2.nix
new file mode 100644
index 00000000000..a1d9ebcec66
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/iproute2.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.networking.iproute2;
+in
+{
+ 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/nixpkgs/nixos/modules/config/krb5/default.nix b/nixpkgs/nixos/modules/config/krb5/default.nix
new file mode 100644
index 00000000000..ff16ffcf9c6
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/krb5/default.nix
@@ -0,0 +1,367 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.krb5;
+
+ # This is to provide support for old configuration options (as much as is
+ # reasonable). This can be removed after 18.03 was released.
+ defaultConfig = {
+ libdefaults = optionalAttrs (cfg.defaultRealm != null)
+ { default_realm = cfg.defaultRealm; };
+
+ realms = optionalAttrs (lib.all (value: value != null) [
+ cfg.defaultRealm cfg.kdc cfg.kerberosAdminServer
+ ]) {
+ ${cfg.defaultRealm} = {
+ kdc = cfg.kdc;
+ admin_server = cfg.kerberosAdminServer;
+ };
+ };
+
+ domain_realm = optionalAttrs (lib.all (value: value != null) [
+ cfg.domainRealm cfg.defaultRealm
+ ]) {
+ ".${cfg.domainRealm}" = cfg.defaultRealm;
+ ${cfg.domainRealm} = cfg.defaultRealm;
+ };
+ };
+
+ mergedConfig = (recursiveUpdate defaultConfig {
+ inherit (config.krb5)
+ kerberos libdefaults realms domain_realm capaths appdefaults plugins
+ extraConfig config;
+ });
+
+ filterEmbeddedMetadata = value: if isAttrs value then
+ (filterAttrs
+ (attrName: attrValue: attrName != "_module" && attrValue != null)
+ value)
+ else value;
+
+ mkIndent = depth: concatStrings (builtins.genList (_: " ") (2 * depth));
+
+ mkRelation = name: value: "${name} = ${mkVal { inherit value; }}";
+
+ mkVal = { value, depth ? 0 }:
+ if (value == true) then "true"
+ else if (value == false) then "false"
+ else if (isInt value) then (toString value)
+ else if (isList value) then
+ concatMapStringsSep " " mkVal { inherit value depth; }
+ else if (isAttrs value) then
+ (concatStringsSep "\n${mkIndent (depth + 1)}"
+ ([ "{" ] ++ (mapAttrsToList
+ (attrName: attrValue: let
+ mappedAttrValue = mkVal {
+ value = attrValue;
+ depth = depth + 1;
+ };
+ in "${attrName} = ${mappedAttrValue}")
+ value))) + "\n${mkIndent depth}}"
+ else value;
+
+ mkMappedAttrsOrString = value: concatMapStringsSep "\n"
+ (line: if builtins.stringLength line > 0
+ then "${mkIndent 1}${line}"
+ else line)
+ (splitString "\n"
+ (if isAttrs value then
+ concatStringsSep "\n"
+ (mapAttrsToList mkRelation value)
+ else value));
+
+in {
+
+ ###### interface
+
+ options = {
+ krb5 = {
+ enable = mkEnableOption "building krb5.conf, configuration file for Kerberos V";
+
+ kerberos = mkOption {
+ type = types.package;
+ default = pkgs.krb5Full;
+ defaultText = "pkgs.krb5Full";
+ example = literalExample "pkgs.heimdalFull";
+ description = ''
+ The Kerberos implementation that will be present in
+ <literal>environment.systemPackages</literal> after enabling this
+ service.
+ '';
+ };
+
+ libdefaults = mkOption {
+ type = with types; either attrs lines;
+ default = {};
+ apply = attrs: filterEmbeddedMetadata attrs;
+ example = literalExample ''
+ {
+ default_realm = "ATHENA.MIT.EDU";
+ };
+ '';
+ description = ''
+ Settings used by the Kerberos V5 library.
+ '';
+ };
+
+ realms = mkOption {
+ type = with types; either attrs lines;
+ default = {};
+ example = literalExample ''
+ {
+ "ATHENA.MIT.EDU" = {
+ admin_server = "athena.mit.edu";
+ kdc = "athena.mit.edu";
+ };
+ };
+ '';
+ apply = attrs: filterEmbeddedMetadata attrs;
+ description = "Realm-specific contact information and settings.";
+ };
+
+ domain_realm = mkOption {
+ type = with types; either attrs lines;
+ default = {};
+ example = literalExample ''
+ {
+ "example.com" = "EXAMPLE.COM";
+ ".example.com" = "EXAMPLE.COM";
+ };
+ '';
+ apply = attrs: filterEmbeddedMetadata attrs;
+ description = ''
+ Map of server hostnames to Kerberos realms.
+ '';
+ };
+
+ capaths = mkOption {
+ type = with types; either attrs lines;
+ default = {};
+ example = literalExample ''
+ {
+ "ATHENA.MIT.EDU" = {
+ "EXAMPLE.COM" = ".";
+ };
+ "EXAMPLE.COM" = {
+ "ATHENA.MIT.EDU" = ".";
+ };
+ };
+ '';
+ apply = attrs: filterEmbeddedMetadata attrs;
+ description = ''
+ Authentication paths for non-hierarchical cross-realm authentication.
+ '';
+ };
+
+ appdefaults = mkOption {
+ type = with types; either attrs lines;
+ default = {};
+ example = literalExample ''
+ {
+ pam = {
+ debug = false;
+ ticket_lifetime = 36000;
+ renew_lifetime = 36000;
+ max_timeout = 30;
+ timeout_shift = 2;
+ initial_timeout = 1;
+ };
+ };
+ '';
+ apply = attrs: filterEmbeddedMetadata attrs;
+ description = ''
+ Settings used by some Kerberos V5 applications.
+ '';
+ };
+
+ plugins = mkOption {
+ type = with types; either attrs lines;
+ default = {};
+ example = literalExample ''
+ {
+ ccselect = {
+ disable = "k5identity";
+ };
+ };
+ '';
+ apply = attrs: filterEmbeddedMetadata attrs;
+ description = ''
+ Controls plugin module registration.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = with types; nullOr lines;
+ default = null;
+ example = ''
+ [logging]
+ kdc = SYSLOG:NOTICE
+ admin_server = SYSLOG:NOTICE
+ default = SYSLOG:NOTICE
+ '';
+ description = ''
+ These lines go to the end of <literal>krb5.conf</literal> verbatim.
+ <literal>krb5.conf</literal> may include any of the relations that are
+ valid for <literal>kdc.conf</literal> (see <literal>man
+ kdc.conf</literal>), but it is not a recommended practice.
+ '';
+ };
+
+ config = mkOption {
+ type = with types; nullOr lines;
+ default = null;
+ example = ''
+ [libdefaults]
+ default_realm = EXAMPLE.COM
+
+ [realms]
+ EXAMPLE.COM = {
+ admin_server = kerberos.example.com
+ kdc = kerberos.example.com
+ default_principal_flags = +preauth
+ }
+
+ [domain_realm]
+ example.com = EXAMPLE.COM
+ .example.com = EXAMPLE.COM
+
+ [logging]
+ kdc = SYSLOG:NOTICE
+ admin_server = SYSLOG:NOTICE
+ default = SYSLOG:NOTICE
+ '';
+ description = ''
+ Verbatim <literal>krb5.conf</literal> configuration. Note that this
+ is mutually exclusive with configuration via
+ <literal>libdefaults</literal>, <literal>realms</literal>,
+ <literal>domain_realm</literal>, <literal>capaths</literal>,
+ <literal>appdefaults</literal>, <literal>plugins</literal> and
+ <literal>extraConfig</literal> configuration options. Consult
+ <literal>man krb5.conf</literal> for documentation.
+ '';
+ };
+
+ defaultRealm = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "ATHENA.MIT.EDU";
+ description = ''
+ DEPRECATED, please use
+ <literal>krb5.libdefaults.default_realm</literal>.
+ '';
+ };
+
+ domainRealm = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "athena.mit.edu";
+ description = ''
+ DEPRECATED, please create a map of server hostnames to Kerberos realms
+ in <literal>krb5.domain_realm</literal>.
+ '';
+ };
+
+ kdc = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "kerberos.mit.edu";
+ description = ''
+ DEPRECATED, please pass a <literal>kdc</literal> attribute to a realm
+ in <literal>krb5.realms</literal>.
+ '';
+ };
+
+ kerberosAdminServer = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "kerberos.mit.edu";
+ description = ''
+ DEPRECATED, please pass an <literal>admin_server</literal> attribute
+ to a realm in <literal>krb5.realms</literal>.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.kerberos ];
+
+ environment.etc."krb5.conf".text = if isString cfg.config
+ then cfg.config
+ else (''
+ [libdefaults]
+ ${mkMappedAttrsOrString mergedConfig.libdefaults}
+
+ [realms]
+ ${mkMappedAttrsOrString mergedConfig.realms}
+
+ [domain_realm]
+ ${mkMappedAttrsOrString mergedConfig.domain_realm}
+
+ [capaths]
+ ${mkMappedAttrsOrString mergedConfig.capaths}
+
+ [appdefaults]
+ ${mkMappedAttrsOrString mergedConfig.appdefaults}
+
+ [plugins]
+ ${mkMappedAttrsOrString mergedConfig.plugins}
+ '' + optionalString (mergedConfig.extraConfig != null)
+ ("\n" + mergedConfig.extraConfig));
+
+ warnings = flatten [
+ (optional (cfg.defaultRealm != null) ''
+ The option krb5.defaultRealm is deprecated, please use
+ krb5.libdefaults.default_realm.
+ '')
+ (optional (cfg.domainRealm != null) ''
+ The option krb5.domainRealm is deprecated, please use krb5.domain_realm.
+ '')
+ (optional (cfg.kdc != null) ''
+ The option krb5.kdc is deprecated, please pass a kdc attribute to a
+ realm in krb5.realms.
+ '')
+ (optional (cfg.kerberosAdminServer != null) ''
+ The option krb5.kerberosAdminServer is deprecated, please pass an
+ admin_server attribute to a realm in krb5.realms.
+ '')
+ ];
+
+ assertions = [
+ { assertion = !((builtins.any (value: value != null) [
+ cfg.defaultRealm cfg.domainRealm cfg.kdc cfg.kerberosAdminServer
+ ]) && ((builtins.any (value: value != {}) [
+ cfg.libdefaults cfg.realms cfg.domain_realm cfg.capaths
+ cfg.appdefaults cfg.plugins
+ ]) || (builtins.any (value: value != null) [
+ cfg.config cfg.extraConfig
+ ])));
+ message = ''
+ Configuration of krb5.conf by deprecated options is mutually exclusive
+ with configuration by section. Please migrate your config using the
+ attributes suggested in the warnings.
+ '';
+ }
+ { assertion = !(cfg.config != null
+ && ((builtins.any (value: value != {}) [
+ cfg.libdefaults cfg.realms cfg.domain_realm cfg.capaths
+ cfg.appdefaults cfg.plugins
+ ]) || (builtins.any (value: value != null) [
+ cfg.extraConfig cfg.defaultRealm cfg.domainRealm cfg.kdc
+ cfg.kerberosAdminServer
+ ])));
+ message = ''
+ Configuration of krb5.conf using krb.config is mutually exclusive with
+ configuration by section. If you want to mix the two, you can pass
+ lines to any configuration section or lines to krb5.extraConfig.
+ '';
+ }
+ ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/ldap.nix b/nixpkgs/nixos/modules/config/ldap.nix
new file mode 100644
index 00000000000..e008497a2a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/ldap.nix
@@ -0,0 +1,293 @@
+{ config, lib, pkgs, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+ cfg = config.users.ldap;
+
+ # Careful: OpenLDAP seems to be very picky about the indentation of
+ # this file. Directives HAVE to start in the first column!
+ ldapConfig = {
+ target = "ldap.conf";
+ source = writeText "ldap.conf" ''
+ uri ${config.users.ldap.server}
+ base ${config.users.ldap.base}
+ timelimit ${toString config.users.ldap.timeLimit}
+ bind_timelimit ${toString config.users.ldap.bind.timeLimit}
+ bind_policy ${config.users.ldap.bind.policy}
+ ${optionalString config.users.ldap.useTLS ''
+ ssl start_tls
+ ''}
+ ${optionalString (config.users.ldap.bind.distinguishedName != "") ''
+ binddn ${config.users.ldap.bind.distinguishedName}
+ ''}
+ ${optionalString (cfg.extraConfig != "") cfg.extraConfig }
+ '';
+ };
+
+ nslcdConfig = writeText "nslcd.conf" ''
+ uid nslcd
+ gid nslcd
+ uri ${cfg.server}
+ base ${cfg.base}
+ timelimit ${toString cfg.timeLimit}
+ 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 }
+ '';
+
+ # nslcd normally reads configuration from /etc/nslcd.conf.
+ # this file might contain secrets. We append those at runtime,
+ # so redirect its location to something more temporary.
+ nslcdWrapped = runCommandNoCC "nslcd-wrapped" { nativeBuildInputs = [ makeWrapper ]; } ''
+ mkdir -p $out/bin
+ makeWrapper ${nss_pam_ldapd}/sbin/nslcd $out/bin/nslcd \
+ --set LD_PRELOAD "${pkgs.libredirect}/lib/libredirect.so" \
+ --set NIX_REDIRECTS "/etc/nslcd.conf=/run/nslcd/nslcd.conf"
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ users.ldap = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable authentication against an LDAP server.";
+ };
+
+ loginPam = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to include authentication against LDAP in login PAM";
+ };
+
+ nsswitch = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to include lookup against LDAP in NSS";
+ };
+
+ server = mkOption {
+ example = "ldap://ldap.example.org/";
+ description = "The URL of the LDAP server.";
+ };
+
+ base = mkOption {
+ example = "dc=example,dc=org";
+ description = "The distinguished name of the search base.";
+ };
+
+ useTLS = mkOption {
+ default = false;
+ description = ''
+ If enabled, use TLS (encryption) over an LDAP (port 389)
+ connection. The alternative is to specify an LDAPS server (port
+ 636) in <option>users.ldap.server</option> or to forego
+ security.
+ '';
+ };
+
+ timeLimit = mkOption {
+ default = 0;
+ type = types.int;
+ description = ''
+ Specifies the time limit (in seconds) to use when performing
+ searches. A value of zero (0), which is the default, is to
+ wait indefinitely for searches to be completed.
+ '';
+ };
+
+ daemon = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to let the nslcd daemon (nss-pam-ldapd) handle the
+ LDAP lookups for NSS and PAM. This can improve performance,
+ and if you need to bind to the LDAP server with a password,
+ it increases security, since only the nslcd user needs to
+ have access to the bindpw file, not everyone that uses NSS
+ and/or PAM. If this option is enabled, a local nscd user is
+ created automatically, and the nslcd service is started
+ automatically when the network get up.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra configuration options that will be added verbatim at
+ 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.
+ '';
+ };
+
+ rootpwmodpwFile = 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 = {
+ distinguishedName = mkOption {
+ default = "";
+ example = "cn=admin,dc=example,dc=com";
+ type = types.str;
+ description = ''
+ The distinguished name to bind to the LDAP server with. If this
+ is not specified, an anonymous bind will be done.
+ '';
+ };
+
+ passwordFile = mkOption {
+ default = "/etc/ldap/bind.password";
+ type = types.str;
+ description = ''
+ The path to a file containing the credentials to use when binding
+ to the LDAP server (if not binding anonymously).
+ '';
+ };
+
+ timeLimit = mkOption {
+ default = 30;
+ type = types.int;
+ description = ''
+ Specifies the time limit (in seconds) to use when connecting
+ to the directory server. This is distinct from the time limit
+ specified in <literal>users.ldap.timeLimit</literal> and affects
+ the initial server connection only.
+ '';
+ };
+
+ policy = mkOption {
+ default = "hard_open";
+ type = types.enum [ "hard_open" "hard_init" "soft" ];
+ description = ''
+ Specifies the policy to use for reconnecting to an unavailable
+ LDAP server. The default is <literal>hard_open</literal>, which
+ reconnects if opening the connection to the directory server
+ failed. By contrast, <literal>hard_init</literal> reconnects if
+ initializing the connection failed. Initializing may not
+ actually contact the directory server, and it is possible that
+ a malformed configuration file will trigger reconnection. If
+ <literal>soft</literal> is specified, then
+ <literal>nss_ldap</literal> will return immediately on server
+ failure. All hard reconnect policies block with exponential
+ backoff before retrying.
+ '';
+ };
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra configuration options that will be added verbatim at
+ the end of the ldap configuration file (ldap.conf).
+ If <literal>users.ldap.daemon</literal> is enabled, this
+ configuration will not be used. In that case, use
+ <literal>users.ldap.daemon.extraConfig</literal> instead.
+ '' ;
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.etc = optional (!cfg.daemon.enable) ldapConfig;
+
+ system.activationScripts = mkIf (!cfg.daemon.enable) {
+ ldap = stringAfter [ "etc" "groups" "users" ] ''
+ if test -f "${cfg.bind.passwordFile}" ; then
+ umask 0077
+ conf="$(mktemp)"
+ printf 'bindpw %s\n' "$(cat ${cfg.bind.passwordFile})" |
+ cat ${ldapConfig.source} - >"$conf"
+ mv -fT "$conf" /etc/ldap.conf
+ fi
+ '';
+ };
+
+ system.nssModules = singleton (
+ if cfg.daemon.enable then nss_pam_ldapd else nss_ldap
+ );
+
+ users = mkIf cfg.daemon.enable {
+ groups.nslcd = {
+ gid = config.ids.gids.nslcd;
+ };
+
+ users.nslcd = {
+ uid = config.ids.uids.nslcd;
+ description = "nslcd user.";
+ group = "nslcd";
+ };
+ };
+
+ systemd.services = mkIf cfg.daemon.enable {
+ nslcd = {
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ umask 0077
+ conf="$(mktemp)"
+ {
+ cat ${nslcdConfig}
+ test -z '${cfg.bind.distinguishedName}' -o ! -f '${cfg.bind.passwordFile}' ||
+ printf 'bindpw %s\n' "$(cat '${cfg.bind.passwordFile}')"
+ test -z '${cfg.daemon.rootpwmoddn}' -o ! -f '${cfg.daemon.rootpwmodpwFile}' ||
+ printf 'rootpwmodpw %s\n' "$(cat '${cfg.daemon.rootpwmodpwFile}')"
+ } >"$conf"
+ mv -fT "$conf" /run/nslcd/nslcd.conf
+ '';
+ restartTriggers = [ "/run/nslcd/nslcd.conf" ];
+
+ serviceConfig = {
+ ExecStart = "${nslcdWrapped}/bin/nslcd";
+ Type = "forking";
+ Restart = "always";
+ User = "nslcd";
+ Group = "nslcd";
+ RuntimeDirectory = [ "nslcd" ];
+ PIDFile = "/run/nslcd/nslcd.pid";
+ };
+ };
+
+ };
+
+ };
+
+ imports =
+ [ (mkRenamedOptionModule [ "users" "ldap" "bind" "password"] [ "users" "ldap" "bind" "passwordFile"])
+ ];
+}
diff --git a/nixpkgs/nixos/modules/config/locale.nix b/nixpkgs/nixos/modules/config/locale.nix
new file mode 100644
index 00000000000..6f056588187
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/locale.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ tzdir = "${pkgs.tzdata}/share/zoneinfo";
+ nospace = str: filter (c: c == " ") (stringToCharacters str) == [];
+ timezone = types.nullOr (types.addCheck types.str nospace)
+ // { description = "null or string without spaces"; };
+
+ lcfg = config.location;
+
+in
+
+{
+ options = {
+
+ time = {
+
+ timeZone = mkOption {
+ default = null;
+ type = timezone;
+ example = "America/New_York";
+ description = ''
+ The time zone used when displaying times and dates. See <link
+ xlink:href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"/>
+ for a comprehensive list of possible values for this setting.
+
+ If null, the timezone will default to UTC and can be set imperatively
+ using timedatectl.
+ '';
+ };
+
+ hardwareClockInLocalTime = mkOption {
+ default = false;
+ type = types.bool;
+ description = "If set, keep the hardware clock in local time instead of UTC.";
+ };
+
+ };
+
+ location = {
+
+ latitude = mkOption {
+ type = types.float;
+ description = ''
+ Your current latitude, between
+ <literal>-90.0</literal> and <literal>90.0</literal>. Must be provided
+ along with longitude.
+ '';
+ };
+
+ longitude = mkOption {
+ type = types.float;
+ description = ''
+ Your current longitude, between
+ between <literal>-180.0</literal> and <literal>180.0</literal>. Must be
+ provided along with latitude.
+ '';
+ };
+
+ provider = mkOption {
+ type = types.enum [ "manual" "geoclue2" ];
+ default = "manual";
+ description = ''
+ The location provider to use for determining your location. If set to
+ <literal>manual</literal> you must also provide latitude/longitude.
+ '';
+ };
+
+ };
+ };
+
+ config = {
+
+ environment.sessionVariables.TZDIR = "/etc/zoneinfo";
+
+ services.geoclue2.enable = mkIf (lcfg.provider == "geoclue2") true;
+
+ # This way services are restarted when tzdata changes.
+ systemd.globalEnvironment.TZDIR = tzdir;
+
+ systemd.services.systemd-timedated.environment = lib.optionalAttrs (config.time.timeZone != null) { NIXOS_STATIC_TIMEZONE = "1"; };
+
+ environment.etc = {
+ zoneinfo.source = tzdir;
+ } // lib.optionalAttrs (config.time.timeZone != null) {
+ localtime.source = "/etc/zoneinfo/${config.time.timeZone}";
+ localtime.mode = "direct-symlink";
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/malloc.nix b/nixpkgs/nixos/modules/config/malloc.nix
new file mode 100644
index 00000000000..31a659ee83f
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/malloc.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+ cfg = config.environment.memoryAllocator;
+
+ # The set of alternative malloc(3) providers.
+ providers = {
+ graphene-hardened = {
+ libPath = "${pkgs.graphene-hardened-malloc}/lib/libhardened_malloc.so";
+ description = ''
+ An allocator designed to mitigate memory corruption attacks, such as
+ those caused by use-after-free bugs.
+ '';
+ };
+
+ jemalloc = {
+ libPath = "${pkgs.jemalloc}/lib/libjemalloc.so";
+ description = ''
+ A general purpose allocator that emphasizes fragmentation avoidance
+ and scalable concurrency support.
+ '';
+ };
+
+ scudo = {
+ libPath = "${pkgs.llvmPackages.compiler-rt}/lib/linux/libclang_rt.scudo-x86_64.so";
+ description = ''
+ A user-mode allocator based on LLVM Sanitizer’s CombinedAllocator,
+ which aims at providing additional mitigations against heap based
+ vulnerabilities, while maintaining good performance.
+ '';
+ };
+ };
+
+ providerConf = providers.${cfg.provider};
+
+ # An output that contains only the shared library, to avoid
+ # needlessly bloating the system closure
+ mallocLib = pkgs.runCommand "malloc-provider-${cfg.provider}"
+ rec {
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ origLibPath = providerConf.libPath;
+ libName = baseNameOf origLibPath;
+ }
+ ''
+ mkdir -p $out/lib
+ cp -L $origLibPath $out/lib/$libName
+ '';
+
+ # The full path to the selected provider shlib.
+ providerLibPath = "${mallocLib}/lib/${mallocLib.libName}";
+in
+
+{
+ meta = {
+ maintainers = [ maintainers.joachifm ];
+ };
+
+ options = {
+ environment.memoryAllocator.provider = mkOption {
+ type = types.enum ([ "libc" ] ++ attrNames providers);
+ default = "libc";
+ description = ''
+ The system-wide memory allocator.
+
+ Briefly, the system-wide memory allocator providers are:
+ <itemizedlist>
+ <listitem><para><literal>libc</literal>: the standard allocator provided by libc</para></listitem>
+ ${toString (mapAttrsToList
+ (name: value: "<listitem><para><literal>${name}</literal>: ${value.description}</para></listitem>")
+ providers)}
+ </itemizedlist>
+
+ <warning>
+ <para>
+ Selecting an alternative allocator (i.e., anything other than
+ <literal>libc</literal>) may result in instability, data loss,
+ and/or service failure.
+ </para>
+ </warning>
+ '';
+ };
+ };
+
+ config = mkIf (cfg.provider != "libc") {
+ environment.etc."ld-nix.so.preload".text = ''
+ ${providerLibPath}
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/networking.nix b/nixpkgs/nixos/modules/config/networking.nix
new file mode 100644
index 00000000000..a89667ea221
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/networking.nix
@@ -0,0 +1,219 @@
+# /etc files related to networking, such as /etc/services.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.networking;
+
+ 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
+
+{
+
+ options = {
+
+ networking.hosts = lib.mkOption {
+ type = types.attrsOf (types.listOf types.str);
+ example = literalExample ''
+ {
+ "127.0.0.1" = [ "foo.bar.baz" ];
+ "192.168.0.2" = [ "fileserver.local" "nameserver.local" ];
+ };
+ '';
+ description = ''
+ Locally defined maps of hostnames to IP addresses.
+ '';
+ };
+
+ networking.extraHosts = lib.mkOption {
+ type = types.lines;
+ default = "";
+ example = "192.168.0.1 lanlocalhost";
+ description = ''
+ Additional verbatim entries to be appended to <filename>/etc/hosts</filename>.
+ '';
+ };
+
+ networking.hostConf = lib.mkOption {
+ type = types.lines;
+ default = "multi on";
+ example = ''
+ multi on
+ reorder on
+ trim lan
+ '';
+ description = ''
+ The contents of <filename>/etc/host.conf</filename>. See also <citerefentry><refentrytitle>host.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ '';
+ };
+
+ networking.timeServers = mkOption {
+ default = [
+ "0.nixos.pool.ntp.org"
+ "1.nixos.pool.ntp.org"
+ "2.nixos.pool.ntp.org"
+ "3.nixos.pool.ntp.org"
+ ];
+ description = ''
+ The set of NTP servers from which to synchronise.
+ '';
+ };
+
+ networking.proxy = {
+
+ default = lib.mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ This option specifies the default value for httpProxy, httpsProxy, ftpProxy and rsyncProxy.
+ '';
+ example = "http://127.0.0.1:3128";
+ };
+
+ httpProxy = lib.mkOption {
+ type = types.nullOr types.str;
+ default = cfg.proxy.default;
+ description = ''
+ This option specifies the http_proxy environment variable.
+ '';
+ example = "http://127.0.0.1:3128";
+ };
+
+ httpsProxy = lib.mkOption {
+ type = types.nullOr types.str;
+ default = cfg.proxy.default;
+ description = ''
+ This option specifies the https_proxy environment variable.
+ '';
+ example = "http://127.0.0.1:3128";
+ };
+
+ ftpProxy = lib.mkOption {
+ type = types.nullOr types.str;
+ default = cfg.proxy.default;
+ description = ''
+ This option specifies the ftp_proxy environment variable.
+ '';
+ example = "http://127.0.0.1:3128";
+ };
+
+ rsyncProxy = lib.mkOption {
+ type = types.nullOr types.str;
+ default = cfg.proxy.default;
+ description = ''
+ This option specifies the rsync_proxy environment variable.
+ '';
+ example = "http://127.0.0.1:3128";
+ };
+
+ allProxy = lib.mkOption {
+ type = types.nullOr types.str;
+ default = cfg.proxy.default;
+ description = ''
+ This option specifies the all_proxy environment variable.
+ '';
+ example = "http://127.0.0.1:3128";
+ };
+
+ noProxy = lib.mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ This option specifies the no_proxy environment variable.
+ If a default proxy is used and noProxy is null,
+ then noProxy will be set to 127.0.0.1,localhost.
+ '';
+ example = "127.0.0.1,localhost,.localdomain";
+ };
+
+ envVars = lib.mkOption {
+ type = types.attrs;
+ internal = true;
+ default = {};
+ description = ''
+ Environment variables used for the network proxy.
+ '';
+ };
+ };
+ };
+
+ 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";
+
+ # /etc/protocols: IP protocol numbers.
+ protocols.source = pkgs.iana-etc + "/etc/protocols";
+
+ # /etc/hosts: Hostname-to-IP mappings.
+ hosts.text = let
+ oneToString = set: ip: ip + " " + concatStringsSep " " set.${ip};
+ allToString = set: concatMapStringsSep "\n" (oneToString set) (attrNames set);
+ in ''
+ ${allToString (filterAttrs (_: v: v != []) cfg.hosts)}
+ ${cfg.extraHosts}
+ '';
+
+ # /etc/host.conf: resolver configuration file
+ "host.conf".text = cfg.hostConf;
+
+ } // optionalAttrs (pkgs.stdenv.hostPlatform.libc == "glibc") {
+ # /etc/rpc: RPC program numbers.
+ rpc.source = pkgs.glibc.out + "/etc/rpc";
+ };
+
+ networking.proxy.envVars =
+ optionalAttrs (cfg.proxy.default != null) {
+ # other options already fallback to proxy.default
+ no_proxy = "127.0.0.1,localhost";
+ } // optionalAttrs (cfg.proxy.httpProxy != null) {
+ http_proxy = cfg.proxy.httpProxy;
+ } // optionalAttrs (cfg.proxy.httpsProxy != null) {
+ https_proxy = cfg.proxy.httpsProxy;
+ } // optionalAttrs (cfg.proxy.rsyncProxy != null) {
+ rsync_proxy = cfg.proxy.rsyncProxy;
+ } // optionalAttrs (cfg.proxy.ftpProxy != null) {
+ ftp_proxy = cfg.proxy.ftpProxy;
+ } // optionalAttrs (cfg.proxy.allProxy != null) {
+ all_proxy = cfg.proxy.allProxy;
+ } // optionalAttrs (cfg.proxy.noProxy != null) {
+ no_proxy = cfg.proxy.noProxy;
+ };
+
+ # Install the proxy environment variables
+ environment.sessionVariables = cfg.proxy.envVars;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/no-x-libs.nix b/nixpkgs/nixos/modules/config/no-x-libs.nix
new file mode 100644
index 00000000000..74cf74d7418
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/no-x-libs.nix
@@ -0,0 +1,41 @@
+# This module gets rid of all dependencies on X11 client libraries
+# (including fontconfig).
+
+{ config, lib, ... }:
+
+with lib;
+
+{
+ options = {
+ environment.noXlibs = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Switch off the options in the default configuration that
+ require X11 libraries. This includes client-side font
+ configuration and SSH forwarding of X11 authentication
+ in. Thus, you probably do not want to enable this option if
+ you want to run X11 programs on this machine via SSH.
+ '';
+ };
+ };
+
+ config = mkIf config.environment.noXlibs {
+ programs.ssh.setXAuthLocation = false;
+ security.pam.services.su.forwardXAuth = lib.mkForce false;
+
+ fonts.fontconfig.enable = false;
+
+ nixpkgs.overlays = singleton (const (super: {
+ dbus = super.dbus.override { x11Support = false; };
+ networkmanager-fortisslvpn = super.networkmanager-fortisslvpn.override { withGnome = false; };
+ networkmanager-l2tp = super.networkmanager-l2tp.override { withGnome = false; };
+ networkmanager-openconnect = super.networkmanager-openconnect.override { withGnome = false; };
+ networkmanager-openvpn = super.networkmanager-openvpn.override { withGnome = false; };
+ networkmanager-vpnc = super.networkmanager-vpnc.override { withGnome = false; };
+ networkmanager-iodine = super.networkmanager-iodine.override { withGnome = false; };
+ pinentry = super.pinentry.override { gtk2 = null; gcr = null; qt4 = null; qt5 = null; };
+ gobject-introspection = super.gobject-introspection.override { x11Support = false; };
+ }));
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/nsswitch.nix b/nixpkgs/nixos/modules/config/nsswitch.nix
new file mode 100644
index 00000000000..13277fe56e4
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/nsswitch.nix
@@ -0,0 +1,116 @@
+# Configuration for the Name Service Switch (/etc/nsswitch.conf).
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ # only with nscd up and running we can load NSS modules that are not integrated in NSS
+ canLoadExternalModules = config.services.nscd.enable;
+ myhostname = canLoadExternalModules;
+ mymachines = canLoadExternalModules;
+ nssmdns = canLoadExternalModules && config.services.avahi.nssmdns;
+ nsswins = canLoadExternalModules && config.services.samba.nsswins;
+ 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"
+ ++ optional nssmdns "mdns_minimal [NOTFOUND=return]"
+ ++ optional nsswins "wins"
+ ++ optional resolved "resolve [!UNAVAIL=return]"
+ ++ [ "dns" ]
+ ++ optional nssmdns "mdns"
+ ++ optional myhostname "myhostname";
+
+ passwdArray = [ "files" ]
+ ++ optional sssd "sss"
+ ++ optional ldap "ldap"
+ ++ optional mymachines "mymachines"
+ ++ optional googleOsLogin "cache_oslogin oslogin"
+ ++ [ "systemd" ];
+
+ shadowArray = [ "files" ]
+ ++ optional sssd "sss"
+ ++ optional ldap "ldap";
+
+ servicesArray = [ "files" ]
+ ++ optional sssd "sss";
+
+in {
+ options = {
+
+ # NSS modules. Hacky!
+ # Only works with nscd!
+ system.nssModules = mkOption {
+ type = types.listOf types.path;
+ internal = true;
+ default = [];
+ description = ''
+ Search path for NSS (Name Service Switch) modules. This allows
+ several DNS resolution methods to be specified via
+ <filename>/etc/nsswitch.conf</filename>.
+ '';
+ apply = list:
+ {
+ inherit list;
+ path = makeLibraryPath list;
+ };
+ };
+
+ system.nssHosts = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "mdns" ];
+ description = ''
+ List of host entries to configure in <filename>/etc/nsswitch.conf</filename>.
+ '';
+ };
+
+ };
+
+ config = {
+ assertions = [
+ {
+ # generic catch if the NixOS module adding to nssModules does not prevent it with specific message.
+ assertion = config.system.nssModules.path != "" -> canLoadExternalModules;
+ message = "Loading NSS modules from path ${config.system.nssModules.path} requires nscd being enabled.";
+ }
+ {
+ # resolved does not need to add to nssModules, therefore needs an extra assertion
+ assertion = resolved -> canLoadExternalModules;
+ message = "Loading systemd-resolved's nss-resolve NSS module requires nscd being enabled.";
+ }
+ ];
+
+ # Name Service Switch configuration file. Required by the C
+ # library. !!! Factor out the mdns stuff. The avahi module
+ # should define an option used by this module.
+ environment.etc."nsswitch.conf".text = ''
+ passwd: ${concatStringsSep " " passwdArray}
+ group: ${concatStringsSep " " passwdArray}
+ shadow: ${concatStringsSep " " shadowArray}
+
+ hosts: ${concatStringsSep " " config.system.nssHosts}
+ networks: files
+
+ ethers: files
+ services: ${concatStringsSep " " servicesArray}
+ protocols: files
+ rpc: files
+ '';
+
+ system.nssHosts = hostArray;
+
+ # Systemd provides nss-myhostname to ensure that our hostname
+ # always resolves to a valid IP address. It returns all locally
+ # 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 ])
+ ++ optional googleOsLogin pkgs.google-compute-engine-oslogin.out;
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/power-management.nix b/nixpkgs/nixos/modules/config/power-management.nix
new file mode 100644
index 00000000000..64cdf50f141
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/power-management.nix
@@ -0,0 +1,106 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.powerManagement;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ powerManagement = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Whether to enable power management. This includes support
+ for suspend-to-RAM and powersave features on laptops.
+ '';
+ };
+
+ resumeCommands = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Commands executed after the system resumes from suspend-to-RAM.";
+ };
+
+ powerUpCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example = literalExample ''
+ "''${pkgs.hdparm}/sbin/hdparm -B 255 /dev/sda"
+ '';
+ description =
+ ''
+ Commands executed when the machine powers up. That is,
+ they're executed both when the system first boots and when
+ it resumes from suspend or hibernation.
+ '';
+ };
+
+ powerDownCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example = literalExample ''
+ "''${pkgs.hdparm}/sbin/hdparm -B 255 /dev/sda"
+ '';
+ description =
+ ''
+ Commands executed when the machine powers down. That is,
+ they're executed both when the system shuts down and when
+ it goes to suspend or hibernation.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.targets.post-resume = {
+ description = "Post-Resume Actions";
+ requires = [ "post-resume.service" ];
+ after = [ "post-resume.service" ];
+ wantedBy = [ "sleep.target" ];
+ unitConfig.StopWhenUnneeded = true;
+ };
+
+ # Service executed before suspending/hibernating.
+ systemd.services.pre-sleep =
+ { description = "Pre-Sleep Actions";
+ wantedBy = [ "sleep.target" ];
+ before = [ "sleep.target" ];
+ script =
+ ''
+ ${cfg.powerDownCommands}
+ '';
+ serviceConfig.Type = "oneshot";
+ };
+
+ systemd.services.post-resume =
+ { description = "Post-Resume Actions";
+ after = [ "suspend.target" "hibernate.target" "hybrid-sleep.target" ];
+ script =
+ ''
+ ${config.systemd.package}/bin/systemctl try-restart post-resume.target
+ ${cfg.resumeCommands}
+ ${cfg.powerUpCommands}
+ '';
+ serviceConfig.Type = "oneshot";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/pulseaudio.nix b/nixpkgs/nixos/modules/config/pulseaudio.nix
new file mode 100644
index 00000000000..5c3e3930258
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/pulseaudio.nix
@@ -0,0 +1,326 @@
+{ config, lib, pkgs, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+ cfg = config.hardware.pulseaudio;
+ alsaCfg = config.sound;
+
+ systemWide = cfg.enable && cfg.systemWide;
+ nonSystemWide = cfg.enable && !cfg.systemWide;
+ hasZeroconf = let z = cfg.zeroconf; in z.publish.enable || z.discovery.enable;
+
+ overriddenPackage = cfg.package.override
+ (optionalAttrs hasZeroconf { zeroconfSupport = true; });
+ binary = "${getBin overriddenPackage}/bin/pulseaudio";
+ binaryNoDaemon = "${binary} --daemonize=no";
+
+ # Forces 32bit pulseaudio and alsaPlugins to be built/supported for apps
+ # using 32bit alsa on 64bit linux.
+ enable32BitAlsaPlugins = cfg.support32Bit && stdenv.isx86_64 && (pkgs.pkgsi686Linux.alsaLib != null && pkgs.pkgsi686Linux.libpulseaudio != null);
+
+
+ myConfigFile =
+ let
+ addModuleIf = cond: mod: optionalString cond "load-module ${mod}";
+ allAnon = optional cfg.tcp.anonymousClients.allowAll "auth-anonymous=1";
+ ipAnon = let a = cfg.tcp.anonymousClients.allowedIpRanges;
+ in optional (a != []) ''auth-ip-acl=${concatStringsSep ";" a}'';
+ in writeTextFile {
+ name = "default.pa";
+ text = ''
+ .include ${cfg.configFile}
+ ${addModuleIf cfg.zeroconf.publish.enable "module-zeroconf-publish"}
+ ${addModuleIf cfg.zeroconf.discovery.enable "module-zeroconf-discover"}
+ ${addModuleIf cfg.tcp.enable (concatStringsSep " "
+ ([ "module-native-protocol-tcp" ] ++ allAnon ++ ipAnon))}
+ ${cfg.extraConfig}
+ '';
+ };
+
+ ids = config.ids;
+
+ uid = ids.uids.pulseaudio;
+ gid = ids.gids.pulseaudio;
+
+ stateDir = "/run/pulse";
+
+ # Create pulse/client.conf even if PulseAudio is disabled so
+ # that we can disable the autospawn feature in programs that
+ # are built with PulseAudio support (like KDE).
+ clientConf = writeText "client.conf" ''
+ autospawn=${if nonSystemWide then "yes" else "no"}
+ ${optionalString nonSystemWide "daemon-binary=${binary}"}
+ ${cfg.extraClientConf}
+ '';
+
+ # Write an /etc/asound.conf that causes all ALSA applications to
+ # be re-routed to the PulseAudio server through ALSA's Pulse
+ # plugin.
+ alsaConf = writeText "asound.conf" (''
+ pcm_type.pulse {
+ libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
+ ${lib.optionalString enable32BitAlsaPlugins
+ "libs.32Bit = ${pkgs.pkgsi686Linux.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
+ }
+ pcm.!default {
+ type pulse
+ hint.description "Default Audio Device (via PulseAudio)"
+ }
+ ctl_type.pulse {
+ libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
+ ${lib.optionalString enable32BitAlsaPlugins
+ "libs.32Bit = ${pkgs.pkgsi686Linux.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
+ }
+ ctl.!default {
+ type pulse
+ }
+ ${alsaCfg.extraConfig}
+ '');
+
+in {
+
+ options = {
+
+ hardware.pulseaudio = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the PulseAudio sound server.
+ '';
+ };
+
+ systemWide = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If false, a PulseAudio server is launched automatically for
+ each user that tries to use the sound system. The server runs
+ with user privileges. This is the recommended and most secure
+ way to use PulseAudio. If true, one system-wide PulseAudio
+ server is launched on boot, running as the user "pulse", and
+ only users in the "audio" group will have access to the server.
+ Please read the PulseAudio documentation for more details.
+ '';
+ };
+
+ support32Bit = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to include the 32-bit pulseaudio libraries in the system or not.
+ This is only useful on 64-bit systems and currently limited to x86_64-linux.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ description = ''
+ The path to the default configuration options the PulseAudio server
+ should use. By default, the "default.pa" configuration
+ from the PulseAudio distribution is used.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Literal string to append to <literal>configFile</literal>
+ and the config file generated by the pulseaudio module.
+ '';
+ };
+
+ extraClientConf = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration appended to pulse/client.conf file.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.pulseaudio;
+ defaultText = "pkgs.pulseaudio";
+ example = literalExample "pkgs.pulseaudioFull";
+ description = ''
+ The PulseAudio derivation to use. This can be used to enable
+ features (such as JACK support, Bluetooth) via the
+ <literal>pulseaudioFull</literal> package.
+ '';
+ };
+
+ 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;
+ default = "notice";
+ description = ''
+ The log level that the system-wide pulseaudio daemon should use,
+ if activated.
+ '';
+ };
+
+ config = mkOption {
+ type = types.attrsOf types.unspecified;
+ default = {};
+ description = ''Config of the pulse daemon. See <literal>man pulse-daemon.conf</literal>.'';
+ example = literalExample ''{ realtime-scheduling = "yes"; }'';
+ };
+ };
+
+ zeroconf = {
+ discovery.enable =
+ mkEnableOption "discovery of pulseaudio sinks in the local network";
+ publish.enable =
+ mkEnableOption "publishing the pulseaudio sink in the local network";
+ };
+
+ # TODO: enable by default?
+ tcp = {
+ enable = mkEnableOption "tcp streaming support";
+
+ anonymousClients = {
+ allowAll = mkEnableOption "all anonymous clients to stream to the server";
+ allowedIpRanges = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = literalExample ''[ "127.0.0.1" "192.168.1.0/24" ]'';
+ description = ''
+ A list of IP subnets that are allowed to stream to the server.
+ '';
+ };
+ };
+ };
+
+ };
+
+ };
+
+
+ config = mkMerge [
+ {
+ environment.etc = singleton {
+ target = "pulse/client.conf";
+ source = clientConf;
+ };
+
+ hardware.pulseaudio.configFile = mkDefault "${getBin overriddenPackage}/etc/pulse/default.pa";
+ }
+
+ (mkIf cfg.enable {
+ environment.systemPackages = [ overriddenPackage ];
+
+ sound.enable = true;
+
+ environment.etc = [
+ { target = "asound.conf";
+ source = alsaConf; }
+
+ { target = "pulse/daemon.conf";
+ source = writeText "daemon.conf" (lib.generators.toKeyValue {} cfg.daemon.config); }
+
+ { target = "openal/alsoft.conf";
+ source = writeText "alsoft.conf" "drivers=pulse"; }
+
+ { target = "libao.conf";
+ source = writeText "libao.conf" "default_driver=pulse"; }
+ ];
+
+ # Disable flat volumes to enable relative ones
+ hardware.pulseaudio.daemon.config.flat-volumes = mkDefault "no";
+
+ # Upstream defaults to speex-float-1 which results in audible artifacts
+ hardware.pulseaudio.daemon.config.resample-method = mkDefault "speex-float-5";
+
+ # Allow PulseAudio to get realtime priority using rtkit.
+ security.rtkit.enable = true;
+
+ 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;
+ })
+ (mkIf cfg.zeroconf.publish.enable {
+ services.avahi.publish.enable = true;
+ services.avahi.publish.userServices = true;
+ })
+
+ (mkIf nonSystemWide {
+ environment.etc = singleton {
+ target = "pulse/default.pa";
+ source = myConfigFile;
+ };
+ systemd.user = {
+ services.pulseaudio = {
+ restartIfChanged = true;
+ serviceConfig = {
+ RestartSec = "500ms";
+ PassEnvironment = "DISPLAY";
+ };
+ };
+ sockets.pulseaudio = {
+ wantedBy = [ "sockets.target" ];
+ };
+ };
+ })
+
+ (mkIf systemWide {
+ users.users.pulse = {
+ # For some reason, PulseAudio wants UID == GID.
+ uid = assert uid == gid; uid;
+ group = "pulse";
+ extraGroups = [ "audio" ];
+ description = "PulseAudio system service user";
+ home = stateDir;
+ createHome = true;
+ };
+
+ users.groups.pulse.gid = gid;
+
+ systemd.services.pulseaudio = {
+ description = "PulseAudio System-Wide Server";
+ wantedBy = [ "sound.target" ];
+ before = [ "sound.target" ];
+ environment.PULSE_RUNTIME_PATH = stateDir;
+ serviceConfig = {
+ Type = "notify";
+ ExecStart = "${binaryNoDaemon} --log-level=${cfg.daemon.logLevel} --system -n --file=${myConfigFile}";
+ Restart = "on-failure";
+ RestartSec = "500ms";
+ };
+ };
+
+ environment.variables.PULSE_COOKIE = "${stateDir}/.config/pulse/cookie";
+ })
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/config/qt5.nix b/nixpkgs/nixos/modules/config/qt5.nix
new file mode 100644
index 00000000000..7de1c0f5d55
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/qt5.nix
@@ -0,0 +1,102 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.qt5;
+
+ isQGnome = cfg.platformTheme == "gnome" && cfg.style == "adwaita";
+ isQtStyle = cfg.platformTheme == "gtk2" && cfg.style != "adwaita";
+
+ packages = if isQGnome then [ pkgs.qgnomeplatform pkgs.adwaita-qt ]
+ else if isQtStyle then [ pkgs.qtstyleplugins ]
+ else throw "`qt5.platformTheme` ${cfg.platformTheme} and `qt5.style` ${cfg.style} are not compatible.";
+
+in
+
+{
+
+ options = {
+ qt5 = {
+
+ enable = mkEnableOption "Qt5 theming configuration";
+
+ platformTheme = mkOption {
+ type = types.enum [
+ "gtk2"
+ "gnome"
+ ];
+ example = "gnome";
+ relatedPackages = [
+ "qgnomeplatform"
+ ["libsForQt5" "qtstyleplugins"]
+ ];
+ description = ''
+ Selects the platform theme to use for Qt5 applications.</para>
+ <para>The options are
+ <variablelist>
+ <varlistentry>
+ <term><literal>gtk</literal></term>
+ <listitem><para>Use GTK theme with
+ <link xlink:href="https://github.com/qt/qtstyleplugins">qtstyleplugins</link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>gnome</literal></term>
+ <listitem><para>Use GNOME theme with
+ <link xlink:href="https://github.com/FedoraQt/QGnomePlatform">qgnomeplatform</link>
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+
+ style = mkOption {
+ type = types.enum [
+ "adwaita"
+ "cleanlooks"
+ "gtk2"
+ "motif"
+ "plastique"
+ ];
+ example = "adwaita";
+ relatedPackages = [
+ "adwaita-qt"
+ ["libsForQt5" "qtstyleplugins"]
+ ];
+ description = ''
+ Selects the style to use for Qt5 applications.</para>
+ <para>The options are
+ <variablelist>
+ <varlistentry>
+ <term><literal>adwaita</literal></term>
+ <listitem><para>Use Adwaita Qt style with
+ <link xlink:href="https://github.com/FedoraQt/adwaita-qt">adwaita</link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>cleanlooks</literal></term>
+ <term><literal>gtk2</literal></term>
+ <term><literal>motif</literal></term>
+ <term><literal>plastique</literal></term>
+ <listitem><para>Use styles from
+ <link xlink:href="https://github.com/qt/qtstyleplugins">qtstyleplugins</link>
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.variables.QT_QPA_PLATFORMTHEME = cfg.platformTheme;
+
+ environment.variables.QT_STYLE_OVERRIDE = cfg.style;
+
+ environment.systemPackages = packages;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/resolvconf.nix b/nixpkgs/nixos/modules/config/resolvconf.nix
new file mode 100644
index 00000000000..406c6a7ac32
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/resolvconf.nix
@@ -0,0 +1,149 @@
+# /etc files related to networking, such as /etc/services.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.networking.resolvconf;
+
+ resolvconfOptions = cfg.extraOptions
+ ++ optional cfg.dnsSingleRequest "single-request"
+ ++ optional cfg.dnsExtensionMechanism "edns0";
+
+ configText =
+ ''
+ # This is the default, but we must set it here to prevent
+ # a collision with an apparently unrelated environment
+ # variable with the same name exported by dhcpcd.
+ interface_order='lo lo[0-9]*'
+ '' + optionalString config.services.nscd.enable ''
+ # Invalidate the nscd cache whenever resolv.conf is
+ # regenerated.
+ libc_restart='${pkgs.systemd}/bin/systemctl try-restart --no-block nscd.service 2> /dev/null'
+ '' + optionalString (length resolvconfOptions > 0) ''
+ # Options as described in resolv.conf(5)
+ resolv_conf_options='${concatStringsSep " " resolvconfOptions}'
+ '' + optionalString cfg.useLocalResolver ''
+ # This hosts runs a full-blown DNS resolver.
+ name_servers='127.0.0.1'
+ '' + cfg.extraConfig;
+
+in
+
+{
+
+ options = {
+
+ networking.resolvconf = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ internal = true;
+ description = ''
+ DNS configuration is managed by resolvconf.
+ '';
+ };
+
+ useHostResolvConf = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ In containers, whether to use the
+ <filename>resolv.conf</filename> supplied by the host.
+ '';
+ };
+
+ dnsSingleRequest = lib.mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Recent versions of glibc will issue both ipv4 (A) and ipv6 (AAAA)
+ address queries at the same time, from the same port. Sometimes upstream
+ routers will systemically drop the ipv4 queries. The symptom of this problem is
+ that 'getent hosts example.com' only returns ipv6 (or perhaps only ipv4) addresses. The
+ workaround for this is to specify the option 'single-request' in
+ /etc/resolv.conf. This option enables that.
+ '';
+ };
+
+ dnsExtensionMechanism = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable the <code>edns0</code> option in <filename>resolv.conf</filename>. With
+ that option set, <code>glibc</code> supports use of the extension mechanisms for
+ DNS (EDNS) specified in RFC 2671. The most popular user of that feature is DNSSEC,
+ which does not work without it.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "libc=NO";
+ description = ''
+ Extra configuration to append to <filename>resolvconf.conf</filename>.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "ndots:1" "rotate" ];
+ description = ''
+ Set the options in <filename>/etc/resolv.conf</filename>.
+ '';
+ };
+
+ useLocalResolver = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Use local DNS server for resolving.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkMerge [
+ {
+ networking.resolvconf.enable = !(config.environment.etc ? "resolv.conf");
+
+ environment.etc."resolvconf.conf".text =
+ if !cfg.enable then
+ # Force-stop any attempts to use resolvconf
+ ''
+ echo "resolvconf is disabled on this system but was used anyway:" >&2
+ echo "$0 $*" >&2
+ exit 1
+ ''
+ else configText;
+ }
+
+ (mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.openresolv ];
+
+ systemd.services.resolvconf = {
+ description = "resolvconf update";
+
+ before = [ "network-pre.target" ];
+ wants = [ "network-pre.target" ];
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ config.environment.etc."resolvconf.conf".source ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.openresolv}/bin/resolvconf -u";
+ RemainAfterExit = true;
+ };
+ };
+
+ })
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/config/shells-environment.nix b/nixpkgs/nixos/modules/config/shells-environment.nix
new file mode 100644
index 00000000000..d939cbb393e
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/shells-environment.nix
@@ -0,0 +1,204 @@
+# This module defines a global environment configuration and
+# a common configuration for all shells.
+
+{ config, lib, utils, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.environment;
+
+ exportedEnvVars =
+ let
+ absoluteVariables =
+ mapAttrs (n: toList) cfg.variables;
+
+ suffixedVariables =
+ flip mapAttrs cfg.profileRelativeEnvVars (envVar: listSuffixes:
+ concatMap (profile: map (suffix: "${profile}${suffix}") listSuffixes) cfg.profiles
+ );
+
+ allVariables =
+ zipAttrsWith (n: concatLists) [ absoluteVariables suffixedVariables ];
+
+ exportVariables =
+ mapAttrsToList (n: v: ''export ${n}="${concatStringsSep ":" v}"'') allVariables;
+ in
+ concatStringsSep "\n" exportVariables;
+in
+
+{
+
+ options = {
+
+ environment.variables = mkOption {
+ default = {};
+ example = { EDITOR = "nvim"; VISUAL = "nvim"; };
+ description = ''
+ A set of environment variables used in the global environment.
+ These variables will be set on shell initialisation (e.g. in /etc/profile).
+ The value of each variable can be either a string or a list of
+ strings. The latter is concatenated, interspersed with colon
+ characters.
+ '';
+ type = with types; attrsOf (either str (listOf str));
+ apply = mapAttrs (n: v: if isList v then concatStringsSep ":" v else v);
+ };
+
+ environment.profiles = mkOption {
+ default = [];
+ description = ''
+ A list of profiles used to setup the global environment.
+ '';
+ type = types.listOf types.str;
+ };
+
+ environment.profileRelativeEnvVars = mkOption {
+ type = types.attrsOf (types.listOf types.str);
+ example = { PATH = [ "/bin" ]; MANPATH = [ "/man" "/share/man" ]; };
+ description = ''
+ Attribute set of environment variable. Each attribute maps to a list
+ of relative paths. Each relative path is appended to the each profile
+ of <option>environment.profiles</option> to form the content of the
+ corresponding environment variable.
+ '';
+ };
+
+ # !!! isn't there a better way?
+ environment.extraInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during global environment initialisation
+ after all variables and profileVariables have been set.
+ This code is assumed to be shell-independent, which means you should
+ stick to pure sh without sh word split.
+ '';
+ type = types.lines;
+ };
+
+ environment.shellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during shell initialisation.
+ This code is assumed to be shell-independent, which means you should
+ stick to pure sh without sh word split.
+ '';
+ type = types.lines;
+ };
+
+ environment.loginShellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during login shell initialisation.
+ This code is assumed to be shell-independent, which means you should
+ stick to pure sh without sh word split.
+ '';
+ type = types.lines;
+ };
+
+ environment.interactiveShellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during interactive shell initialisation.
+ This code is assumed to be shell-independent, which means you should
+ stick to pure sh without sh word split.
+ '';
+ type = types.lines;
+ };
+
+ environment.shellAliases = mkOption {
+ 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 = with types; attrsOf (nullOr (either str path));
+ };
+
+ environment.binsh = mkOption {
+ default = "${config.system.build.binsh}/bin/sh";
+ defaultText = "\${config.system.build.binsh}/bin/sh";
+ example = literalExample ''
+ "''${pkgs.dash}/bin/dash"
+ '';
+ type = types.path;
+ visible = false;
+ description = ''
+ The shell executable that is linked system-wide to
+ <literal>/bin/sh</literal>. Please note that NixOS assumes all
+ over the place that shell to be Bash, so override the default
+ setting only if you know exactly what you're doing.
+ '';
+ };
+
+ environment.shells = mkOption {
+ default = [];
+ example = literalExample "[ pkgs.bashInteractive pkgs.zsh ]";
+ description = ''
+ A list of permissible login shells for user accounts.
+ No need to mention <literal>/bin/sh</literal>
+ here, it is placed into this list implicitly.
+ '';
+ type = types.listOf (types.either types.shellPackage types.path);
+ };
+
+ };
+
+ config = {
+
+ system.build.binsh = pkgs.bashInteractive;
+
+ # Set session variables in the shell as well. This is usually
+ # unnecessary, but it allows changes to session variables to take
+ # effect without restarting the session (e.g. by opening a new
+ # terminal instead of logging out of X11).
+ environment.variables = config.environment.sessionVariables;
+
+ environment.profileRelativeEnvVars = config.environment.profileRelativeSessionVariables;
+
+ environment.shellAliases = mapAttrs (name: mkDefault) {
+ ls = "ls --color=tty";
+ ll = "ls -l";
+ l = "ls -alh";
+ };
+
+ environment.etc.shells.text =
+ ''
+ ${concatStringsSep "\n" (map utils.toShellPath cfg.shells)}
+ /bin/sh
+ '';
+
+ # For resetting environment with `. /etc/set-environment` when needed
+ # and discoverability (see motivation of #30418).
+ environment.etc.set-environment.source = config.system.build.setEnvironment;
+
+ system.build.setEnvironment = pkgs.writeText "set-environment"
+ ''
+ # DO NOT EDIT -- this file has been generated automatically.
+
+ # Prevent this file from being sourced by child shells.
+ export __NIXOS_SET_ENVIRONMENT_DONE=1
+
+ ${exportedEnvVars}
+
+ ${cfg.extraInit}
+
+ # ~/bin if it exists overrides other bin directories.
+ export PATH="$HOME/bin:$PATH"
+ '';
+
+ system.activationScripts.binsh = stringAfter [ "stdio" ]
+ ''
+ # Create the required /bin/sh symlink; otherwise lots of things
+ # (notably the system() function) won't work.
+ mkdir -m 0755 -p /bin
+ ln -sfn "${cfg.binsh}" /bin/.sh.tmp
+ mv /bin/.sh.tmp /bin/sh # atomically replace /bin/sh
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/swap.nix b/nixpkgs/nixos/modules/config/swap.nix
new file mode 100644
index 00000000000..fed3fa3bc7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/swap.nix
@@ -0,0 +1,222 @@
+{ config, lib, pkgs, utils, ... }:
+
+with utils;
+with lib;
+
+let
+
+ randomEncryptionCoerce = enable: { inherit enable; };
+
+ randomEncryptionOpts = { ... }: {
+
+ options = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Encrypt swap device with a random key. This way you won't have a persistent swap device.
+
+ WARNING: Don't try to hibernate when you have at least one swap partition with
+ this option enabled! We have no way to set the partition into which hibernation image
+ is saved, so if your image ends up on an encrypted one you would lose it!
+
+ WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device
+ when using randomEncryption as the UUIDs and labels will get erased on every boot when
+ the partition is encrypted. Best to use /dev/disk/by-partuuid/…
+ '';
+ };
+
+ cipher = mkOption {
+ default = "aes-xts-plain64";
+ example = "serpent-xts-plain64";
+ type = types.str;
+ description = ''
+ Use specified cipher for randomEncryption.
+
+ Hint: Run "cryptsetup benchmark" to see which one is fastest on your machine.
+ '';
+ };
+
+ source = mkOption {
+ default = "/dev/urandom";
+ example = "/dev/random";
+ type = types.str;
+ description = ''
+ Define the source of randomness to obtain a random key for encryption.
+ '';
+ };
+
+ };
+
+ };
+
+ swapCfg = {config, options, ...}: {
+
+ options = {
+
+ device = mkOption {
+ example = "/dev/sda3";
+ type = types.str;
+ description = "Path of the device.";
+ };
+
+ label = mkOption {
+ example = "swap";
+ type = types.str;
+ description = ''
+ Label of the device. Can be used instead of <varname>device</varname>.
+ '';
+ };
+
+ size = mkOption {
+ default = null;
+ example = 2048;
+ type = types.nullOr types.int;
+ description = ''
+ If this option is set, ‘device’ is interpreted as the
+ path of a swapfile that will be created automatically
+ with the indicated size (in megabytes).
+ '';
+ };
+
+ priority = mkOption {
+ default = null;
+ example = 2048;
+ type = types.nullOr types.int;
+ description = ''
+ Specify the priority of the swap device. Priority is a value between 0 and 32767.
+ Higher numbers indicate higher priority.
+ null lets the kernel choose a priority, which will show up as a negative value.
+ '';
+ };
+
+ randomEncryption = mkOption {
+ default = false;
+ example = {
+ enable = true;
+ cipher = "serpent-xts-plain64";
+ source = "/dev/random";
+ };
+ type = types.coercedTo types.bool randomEncryptionCoerce (types.submodule randomEncryptionOpts);
+ description = ''
+ Encrypt swap device with a random key. This way you won't have a persistent swap device.
+
+ HINT: run "cryptsetup benchmark" to test cipher performance on your machine.
+
+ WARNING: Don't try to hibernate when you have at least one swap partition with
+ this option enabled! We have no way to set the partition into which hibernation image
+ is saved, so if your image ends up on an encrypted one you would lose it!
+
+ WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device
+ when using randomEncryption as the UUIDs and labels will get erased on every boot when
+ the partition is encrypted. Best to use /dev/disk/by-partuuid/…
+ '';
+ };
+
+ deviceName = mkOption {
+ type = types.str;
+ internal = true;
+ };
+
+ realDevice = mkOption {
+ type = types.path;
+ internal = true;
+ };
+
+ };
+
+ config = rec {
+ device = mkIf options.label.isDefined
+ "/dev/disk/by-label/${config.label}";
+ deviceName = lib.replaceChars ["\\"] [""] (escapeSystemdPath config.device);
+ realDevice = if config.randomEncryption.enable then "/dev/mapper/${deviceName}" else config.device;
+ };
+
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ swapDevices = mkOption {
+ default = [];
+ example = [
+ { device = "/dev/hda7"; }
+ { device = "/var/swapfile"; }
+ { label = "bigswap"; }
+ ];
+ description = ''
+ The swap devices and swap files. These must have been
+ initialised using <command>mkswap</command>. Each element
+ should be an attribute set specifying either the path of the
+ swap device or file (<literal>device</literal>) or the label
+ of the swap device (<literal>label</literal>, see
+ <command>mkswap -L</command>). Using a label is
+ recommended.
+ '';
+
+ type = types.listOf (types.submodule swapCfg);
+ };
+
+ };
+
+ config = mkIf ((length config.swapDevices) != 0) {
+
+ system.requiredKernelConfig = with config.lib.kernelConfig; [
+ (isYes "SWAP")
+ ];
+
+ # Create missing swapfiles.
+ # FIXME: support changing the size of existing swapfiles.
+ systemd.services =
+ let
+
+ createSwapDevice = sw:
+ assert sw.device != "";
+ assert !(sw.randomEncryption.enable && lib.hasPrefix "/dev/disk/by-uuid" sw.device);
+ assert !(sw.randomEncryption.enable && lib.hasPrefix "/dev/disk/by-label" sw.device);
+ let realDevice' = escapeSystemdPath sw.realDevice;
+ in nameValuePair "mkswap-${sw.deviceName}"
+ { description = "Initialisation of swap device ${sw.device}";
+ wantedBy = [ "${realDevice'}.swap" ];
+ before = [ "${realDevice'}.swap" ];
+ path = [ pkgs.utillinux ] ++ optional sw.randomEncryption.enable pkgs.cryptsetup;
+
+ script =
+ ''
+ ${optionalString (sw.size != null) ''
+ currentSize=$(( $(stat -c "%s" "${sw.device}" 2>/dev/null || echo 0) / 1024 / 1024 ))
+ if [ "${toString sw.size}" != "$currentSize" ]; then
+ fallocate -l ${toString sw.size}M "${sw.device}" ||
+ dd if=/dev/zero of="${sw.device}" bs=1M count=${toString sw.size}
+ if [ "${toString sw.size}" -lt "$currentSize" ]; then
+ truncate --size "${toString sw.size}M" "${sw.device}"
+ fi
+ chmod 0600 ${sw.device}
+ ${optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
+ fi
+ ''}
+ ${optionalString sw.randomEncryption.enable ''
+ cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} ${sw.device} ${sw.deviceName}
+ mkswap ${sw.realDevice}
+ ''}
+ '';
+
+ unitConfig.RequiresMountsFor = [ "${dirOf sw.device}" ];
+ unitConfig.DefaultDependencies = false; # needed to prevent a cycle
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = sw.randomEncryption.enable;
+ serviceConfig.ExecStop = optionalString sw.randomEncryption.enable "${pkgs.cryptsetup}/bin/cryptsetup luksClose ${sw.deviceName}";
+ restartIfChanged = false;
+ };
+
+ in listToAttrs (map createSwapDevice (filter (sw: sw.size != null || sw.randomEncryption.enable) config.swapDevices));
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/sysctl.nix b/nixpkgs/nixos/modules/config/sysctl.nix
new file mode 100644
index 00000000000..fb2b58eed72
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/sysctl.nix
@@ -0,0 +1,63 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+
+ sysctlOption = mkOptionType {
+ name = "sysctl option value";
+ check = val:
+ let
+ checkType = x: isBool x || isString x || isInt x || x == null;
+ in
+ checkType val || (val._type or "" == "override" && checkType val.content);
+ merge = loc: defs: mergeOneOption loc (filterOverrides defs);
+ };
+
+in
+
+{
+
+ options = {
+
+ boot.kernel.sysctl = mkOption {
+ default = {};
+ example = literalExample ''
+ { "net.ipv4.tcp_syncookies" = false; "vm.swappiness" = 60; }
+ '';
+ type = types.attrsOf sysctlOption;
+ description = ''
+ Runtime parameters of the Linux kernel, as set by
+ <citerefentry><refentrytitle>sysctl</refentrytitle>
+ <manvolnum>8</manvolnum></citerefentry>. Note that sysctl
+ parameters names must be enclosed in quotes
+ (e.g. <literal>"vm.swappiness"</literal> instead of
+ <literal>vm.swappiness</literal>). The value of each
+ parameter may be a string, integer, boolean, or null
+ (signifying the option will not appear at all).
+ '';
+ };
+
+ };
+
+ config = {
+
+ environment.etc."sysctl.d/60-nixos.conf".text =
+ concatStrings (mapAttrsToList (n: v:
+ optionalString (v != null) "${n}=${if v == false then "0" else toString v}\n"
+ ) config.boot.kernel.sysctl);
+
+ systemd.services.systemd-sysctl =
+ { wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ config.environment.etc."sysctl.d/60-nixos.conf".source ];
+ };
+
+ # Hide kernel pointers (e.g. in /proc/modules) for unprivileged
+ # users as these make it easier to exploit kernel vulnerabilities.
+ boot.kernel.sysctl."kernel.kptr_restrict" = 1;
+
+ # Disable YAMA by default to allow easy debugging.
+ boot.kernel.sysctl."kernel.yama.ptrace_scope" = mkDefault 0;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/system-environment.nix b/nixpkgs/nixos/modules/config/system-environment.nix
new file mode 100644
index 00000000000..792d1dbb38f
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/system-environment.nix
@@ -0,0 +1,99 @@
+# This module defines a system-wide environment that will be
+# initialised by pam_env (that is, not only in shells).
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.environment;
+
+ pamProfiles =
+ map
+ (replaceStrings ["$HOME" "$USER"] ["@{HOME}" "@{PAM_USER}"])
+ cfg.profiles;
+
+in
+
+{
+
+ options = {
+
+ environment.sessionVariables = mkOption {
+ default = {};
+ description = ''
+ A set of environment variables used in the global environment.
+ These variables will be set by PAM early in the login process.
+
+ The value of each session variable can be either a string or a
+ list of strings. The latter is concatenated, interspersed with
+ colon characters.
+
+ Note, due to limitations in the PAM format values may not
+ contain the <literal>"</literal> character.
+
+ Also, these variables are merged into
+ <xref linkend="opt-environment.variables"/> and it is
+ therefore not possible to use PAM style variables such as
+ <code>@{HOME}</code>.
+ '';
+ type = with types; attrsOf (either str (listOf str));
+ apply = mapAttrs (n: v: if isList v then concatStringsSep ":" v else v);
+ };
+
+ environment.profileRelativeSessionVariables = mkOption {
+ type = types.attrsOf (types.listOf types.str);
+ example = { PATH = [ "/bin" ]; MANPATH = [ "/man" "/share/man" ]; };
+ description = ''
+ Attribute set of environment variable used in the global
+ environment. These variables will be set by PAM early in the
+ login process.
+
+ Variable substitution is available as described in
+ <citerefentry>
+ <refentrytitle>pam_env.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>.
+
+ Each attribute maps to a list of relative paths. Each relative
+ path is appended to the each profile of
+ <option>environment.profiles</option> to form the content of
+ the corresponding environment variable.
+
+ Also, these variables are merged into
+ <xref linkend="opt-environment.profileRelativeEnvVars"/> and it is
+ therefore not possible to use PAM style variables such as
+ <code>@{HOME}</code>.
+ '';
+ };
+
+ };
+
+ config = {
+
+ system.build.pamEnvironment =
+ let
+ suffixedVariables =
+ flip mapAttrs cfg.profileRelativeSessionVariables (envVar: suffixes:
+ flip concatMap pamProfiles (profile:
+ map (suffix: "${profile}${suffix}") suffixes
+ )
+ );
+
+ pamVariable = n: v:
+ ''${n} DEFAULT="${concatStringsSep ":" (toList v)}"'';
+
+ pamVariables =
+ concatStringsSep "\n"
+ (mapAttrsToList pamVariable
+ (zipAttrsWith (n: concatLists)
+ [
+ (mapAttrs (n: toList) cfg.sessionVariables)
+ suffixedVariables
+ ]));
+ in
+ pkgs.writeText "pam-environment" "${pamVariables}\n";
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/system-path.nix b/nixpkgs/nixos/modules/config/system-path.nix
new file mode 100644
index 00000000000..aba9bc0945b
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/system-path.nix
@@ -0,0 +1,150 @@
+# This module defines the packages that appear in
+# /run/current-system/sw.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ requiredPackages = map (pkg: setPrio ((pkg.meta.priority or 5) + 3) pkg)
+ [ config.nix.package
+ pkgs.acl
+ pkgs.attr
+ pkgs.bashInteractive # bash with ncurses support
+ pkgs.bzip2
+ pkgs.coreutils-full
+ pkgs.cpio
+ pkgs.curl
+ pkgs.diffutils
+ pkgs.findutils
+ pkgs.gawk
+ pkgs.stdenv.cc.libc
+ pkgs.getent
+ pkgs.getconf
+ pkgs.gnugrep
+ pkgs.gnupatch
+ pkgs.gnused
+ pkgs.gnutar
+ pkgs.gzip
+ pkgs.xz
+ pkgs.less
+ pkgs.libcap
+ pkgs.nano
+ pkgs.ncurses
+ pkgs.netcat
+ pkgs.nix-info
+ config.programs.ssh.package
+ pkgs.perl
+ pkgs.procps
+ pkgs.rsync
+ pkgs.strace
+ pkgs.su
+ pkgs.time
+ pkgs.utillinux
+ pkgs.which # 88K size
+ ];
+
+in
+
+{
+ options = {
+
+ environment = {
+
+ systemPackages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "[ pkgs.firefox pkgs.thunderbird ]";
+ description = ''
+ The set of packages that appear in
+ /run/current-system/sw. These packages are
+ automatically available to all users, and are
+ automatically updated every time you rebuild the system
+ configuration. (The latter is the main difference with
+ installing them in the default profile,
+ <filename>/nix/var/nix/profiles/default</filename>.
+ '';
+ };
+
+ pathsToLink = mkOption {
+ type = types.listOf types.str;
+ # Note: We need `/lib' to be among `pathsToLink' for NSS modules
+ # to work.
+ default = [];
+ example = ["/"];
+ description = "List of directories to be symlinked in <filename>/run/current-system/sw</filename>.";
+ };
+
+ extraOutputsToInstall = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "doc" "info" "devdoc" ];
+ description = "List of additional package outputs to be symlinked into <filename>/run/current-system/sw</filename>.";
+ };
+
+ extraSetup = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Shell fragments to be run after the system environment has been created. This should only be used for things that need to modify the internals of the environment, e.g. generating MIME caches. The environment being built can be accessed at $out.";
+ };
+
+ };
+
+ system = {
+
+ path = mkOption {
+ internal = true;
+ description = ''
+ The packages you want in the boot environment.
+ '';
+ };
+
+ };
+
+ };
+
+ config = {
+
+ environment.systemPackages = requiredPackages;
+
+ environment.pathsToLink =
+ [ "/bin"
+ "/etc/xdg"
+ "/etc/gtk-2.0"
+ "/etc/gtk-3.0"
+ "/lib" # FIXME: remove and update debug-info.nix
+ "/sbin"
+ "/share/emacs"
+ "/share/nano"
+ "/share/org"
+ "/share/themes"
+ "/share/vim-plugins"
+ "/share/vulkan"
+ "/share/kservices5"
+ "/share/kservicetypes5"
+ "/share/kxmlgui5"
+ ];
+
+ system.path = pkgs.buildEnv {
+ name = "system-path";
+ paths = config.environment.systemPackages;
+ inherit (config.environment) pathsToLink extraOutputsToInstall;
+ ignoreCollisions = true;
+ # !!! Hacky, should modularise.
+ # outputs TODO: note that the tools will often not be linked by default
+ postBuild =
+ ''
+ # Remove wrapped binaries, they shouldn't be accessible via PATH.
+ find $out/bin -maxdepth 1 -name ".*-wrapped" -type l -delete
+
+ if [ -x $out/bin/glib-compile-schemas -a -w $out/share/glib-2.0/schemas ]; then
+ $out/bin/glib-compile-schemas $out/share/glib-2.0/schemas
+ fi
+
+ ${config.environment.extraSetup}
+ '';
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/terminfo.nix b/nixpkgs/nixos/modules/config/terminfo.nix
new file mode 100644
index 00000000000..1396640af67
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/terminfo.nix
@@ -0,0 +1,33 @@
+# This module manages the terminfo database
+# and its integration in the system.
+{ config, ... }:
+{
+ config = {
+
+ environment.pathsToLink = [
+ "/share/terminfo"
+ ];
+
+ environment.etc.terminfo = {
+ source = "${config.system.path}/share/terminfo";
+ };
+
+ environment.profileRelativeSessionVariables = {
+ TERMINFO_DIRS = [ "/share/terminfo" ];
+ };
+
+ environment.extraInit = ''
+
+ # reset TERM with new TERMINFO available (if any)
+ export TERM=$TERM
+ '';
+
+ security.sudo.extraConfig = ''
+
+ # Keep terminfo database for root and %wheel.
+ Defaults:root,%wheel env_keep+=TERMINFO_DIRS
+ Defaults:root,%wheel env_keep+=TERMINFO
+ '';
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/unix-odbc-drivers.nix b/nixpkgs/nixos/modules/config/unix-odbc-drivers.nix
new file mode 100644
index 00000000000..8dd81172738
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/unix-odbc-drivers.nix
@@ -0,0 +1,38 @@
+{ config, lib, ... }:
+
+with lib;
+
+# unixODBC drivers (this solution is not perfect.. Because the user has to
+# ask the admin to add a driver.. but it's simple and works
+
+let
+ iniDescription = pkg: ''
+ [${pkg.fancyName}]
+ Description = ${pkg.meta.description}
+ Driver = ${pkg}/${pkg.driver}
+ '';
+
+in {
+ ###### interface
+
+ options = {
+ environment.unixODBCDrivers = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "with pkgs.unixODBCDrivers; [ sqlite psql ]";
+ description = ''
+ Specifies Unix ODBC drivers to be registered in
+ <filename>/etc/odbcinst.ini</filename>. You may also want to
+ add <literal>pkgs.unixODBC</literal> to the system path to get
+ a command line client to connnect to ODBC databases.
+ '';
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf (config.environment.unixODBCDrivers != []) {
+ environment.etc."odbcinst.ini".text = concatMapStringsSep "\n" iniDescription config.environment.unixODBCDrivers;
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/update-users-groups.pl b/nixpkgs/nixos/modules/config/update-users-groups.pl
new file mode 100644
index 00000000000..59cea51c611
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/update-users-groups.pl
@@ -0,0 +1,283 @@
+use strict;
+use File::Path qw(make_path);
+use File::Slurp;
+use JSON;
+
+make_path("/var/lib/nixos", { mode => 0755 });
+
+
+# Keep track of deleted uids and gids.
+my $uidMapFile = "/var/lib/nixos/uid-map";
+my $uidMap = -e $uidMapFile ? decode_json(read_file($uidMapFile)) : {};
+
+my $gidMapFile = "/var/lib/nixos/gid-map";
+my $gidMap = -e $gidMapFile ? decode_json(read_file($gidMapFile)) : {};
+
+
+sub updateFile {
+ my ($path, $contents, $perms) = @_;
+ write_file("$path.tmp", { binmode => ':utf8', perms => $perms // 0644 }, $contents);
+ rename("$path.tmp", $path) or die;
+}
+
+
+sub hashPassword {
+ my ($password) = @_;
+ my $salt = "";
+ my @chars = ('.', '/', 0..9, 'A'..'Z', 'a'..'z');
+ $salt .= $chars[rand 64] for (1..8);
+ return crypt($password, '$6$' . $salt . '$');
+}
+
+
+# Functions for allocating free GIDs/UIDs. FIXME: respect ID ranges in
+# /etc/login.defs.
+sub allocId {
+ my ($used, $prevUsed, $idMin, $idMax, $up, $getid) = @_;
+ my $id = $up ? $idMin : $idMax;
+ while ($id >= $idMin && $id <= $idMax) {
+ if (!$used->{$id} && !$prevUsed->{$id} && !defined &$getid($id)) {
+ $used->{$id} = 1;
+ return $id;
+ }
+ $used->{$id} = 1;
+ if ($up) { $id++; } else { $id--; }
+ }
+ die "$0: out of free UIDs or GIDs\n";
+}
+
+my (%gidsUsed, %uidsUsed, %gidsPrevUsed, %uidsPrevUsed);
+
+sub allocGid {
+ my ($name) = @_;
+ my $prevGid = $gidMap->{$name};
+ if (defined $prevGid && !defined $gidsUsed{$prevGid}) {
+ print STDERR "reviving group '$name' with GID $prevGid\n";
+ $gidsUsed{$prevGid} = 1;
+ return $prevGid;
+ }
+ return allocId(\%gidsUsed, \%gidsPrevUsed, 400, 499, 0, sub { my ($gid) = @_; getgrgid($gid) });
+}
+
+sub allocUid {
+ my ($name, $isSystemUser) = @_;
+ my ($min, $max, $up) = $isSystemUser ? (400, 499, 0) : (1000, 29999, 1);
+ my $prevUid = $uidMap->{$name};
+ if (defined $prevUid && $prevUid >= $min && $prevUid <= $max && !defined $uidsUsed{$prevUid}) {
+ print STDERR "reviving user '$name' with UID $prevUid\n";
+ $uidsUsed{$prevUid} = 1;
+ return $prevUid;
+ }
+ return allocId(\%uidsUsed, \%uidsPrevUsed, $min, $max, $up, sub { my ($uid) = @_; getpwuid($uid) });
+}
+
+
+# Read the declared users/groups.
+my $spec = decode_json(read_file($ARGV[0]));
+
+# Don't allocate UIDs/GIDs that are manually assigned.
+foreach my $g (@{$spec->{groups}}) {
+ $gidsUsed{$g->{gid}} = 1 if defined $g->{gid};
+}
+
+foreach my $u (@{$spec->{users}}) {
+ $uidsUsed{$u->{uid}} = 1 if defined $u->{uid};
+}
+
+# Likewise for previously used but deleted UIDs/GIDs.
+$uidsPrevUsed{$_} = 1 foreach values %{$uidMap};
+$gidsPrevUsed{$_} = 1 foreach values %{$gidMap};
+
+
+# Read the current /etc/group.
+sub parseGroup {
+ chomp;
+ my @f = split(':', $_, -4);
+ my $gid = $f[2] eq "" ? undef : int($f[2]);
+ $gidsUsed{$gid} = 1 if defined $gid;
+ return ($f[0], { name => $f[0], password => $f[1], gid => $gid, members => $f[3] });
+}
+
+my %groupsCur = -f "/etc/group" ? map { parseGroup } read_file("/etc/group") : ();
+
+# Read the current /etc/passwd.
+sub parseUser {
+ chomp;
+ my @f = split(':', $_, -7);
+ my $uid = $f[2] eq "" ? undef : int($f[2]);
+ $uidsUsed{$uid} = 1 if defined $uid;
+ return ($f[0], { name => $f[0], fakePassword => $f[1], uid => $uid,
+ gid => $f[3], description => $f[4], home => $f[5], shell => $f[6] });
+}
+
+my %usersCur = -f "/etc/passwd" ? map { parseUser } read_file("/etc/passwd") : ();
+
+# Read the groups that were created declaratively (i.e. not by groups)
+# in the past. These must be removed if they are no longer in the
+# current spec.
+my $declGroupsFile = "/var/lib/nixos/declarative-groups";
+my %declGroups;
+$declGroups{$_} = 1 foreach split / /, -e $declGroupsFile ? read_file($declGroupsFile) : "";
+
+# Idem for the users.
+my $declUsersFile = "/var/lib/nixos/declarative-users";
+my %declUsers;
+$declUsers{$_} = 1 foreach split / /, -e $declUsersFile ? read_file($declUsersFile) : "";
+
+
+# Generate a new /etc/group containing the declared groups.
+my %groupsOut;
+foreach my $g (@{$spec->{groups}}) {
+ my $name = $g->{name};
+ my $existing = $groupsCur{$name};
+
+ my %members = map { ($_, 1) } @{$g->{members}};
+
+ if (defined $existing) {
+ $g->{gid} = $existing->{gid} if !defined $g->{gid};
+ if ($g->{gid} != $existing->{gid}) {
+ warn "warning: not applying GID change of group ‘$name’ ($existing->{gid} -> $g->{gid})\n";
+ $g->{gid} = $existing->{gid};
+ }
+ $g->{password} = $existing->{password}; # do we want this?
+ if ($spec->{mutableUsers}) {
+ # Merge in non-declarative group members.
+ foreach my $uname (split /,/, $existing->{members} // "") {
+ $members{$uname} = 1 if !defined $declUsers{$uname};
+ }
+ }
+ } else {
+ $g->{gid} = allocGid($name) if !defined $g->{gid};
+ $g->{password} = "x";
+ }
+
+ $g->{members} = join ",", sort(keys(%members));
+ $groupsOut{$name} = $g;
+
+ $gidMap->{$name} = $g->{gid};
+}
+
+# Update the persistent list of declarative groups.
+updateFile($declGroupsFile, join(" ", sort(keys %groupsOut)));
+
+# Merge in the existing /etc/group.
+foreach my $name (keys %groupsCur) {
+ my $g = $groupsCur{$name};
+ next if defined $groupsOut{$name};
+ if (!$spec->{mutableUsers} || defined $declGroups{$name}) {
+ print STDERR "removing group ‘$name’\n";
+ } else {
+ $groupsOut{$name} = $g;
+ }
+}
+
+
+# Rewrite /etc/group. FIXME: acquire lock.
+my @lines = map { join(":", $_->{name}, $_->{password}, $_->{gid}, $_->{members}) . "\n" }
+ (sort { $a->{gid} <=> $b->{gid} } values(%groupsOut));
+updateFile($gidMapFile, encode_json($gidMap));
+updateFile("/etc/group", \@lines);
+system("nscd --invalidate group");
+
+# Generate a new /etc/passwd containing the declared users.
+my %usersOut;
+foreach my $u (@{$spec->{users}}) {
+ my $name = $u->{name};
+
+ # Resolve the gid of the user.
+ if ($u->{group} =~ /^[0-9]$/) {
+ $u->{gid} = $u->{group};
+ } elsif (defined $groupsOut{$u->{group}}) {
+ $u->{gid} = $groupsOut{$u->{group}}->{gid} // die;
+ } else {
+ warn "warning: user ‘$name’ has unknown group ‘$u->{group}’\n";
+ $u->{gid} = 65534;
+ }
+
+ my $existing = $usersCur{$name};
+ if (defined $existing) {
+ $u->{uid} = $existing->{uid} if !defined $u->{uid};
+ if ($u->{uid} != $existing->{uid}) {
+ warn "warning: not applying UID change of user ‘$name’ ($existing->{uid} -> $u->{uid})\n";
+ $u->{uid} = $existing->{uid};
+ }
+ } else {
+ $u->{uid} = allocUid($name, $u->{isSystemUser}) if !defined $u->{uid};
+
+ if (defined $u->{initialPassword}) {
+ $u->{hashedPassword} = hashPassword($u->{initialPassword});
+ } elsif (defined $u->{initialHashedPassword}) {
+ $u->{hashedPassword} = $u->{initialHashedPassword};
+ }
+ }
+
+ # Create a home directory.
+ if ($u->{createHome}) {
+ make_path($u->{home}, { mode => 0700 }) if ! -e $u->{home};
+ chown $u->{uid}, $u->{gid}, $u->{home};
+ }
+
+ if (defined $u->{passwordFile}) {
+ if (-e $u->{passwordFile}) {
+ $u->{hashedPassword} = read_file($u->{passwordFile});
+ chomp $u->{hashedPassword};
+ } else {
+ warn "warning: password file ‘$u->{passwordFile}’ does not exist\n";
+ }
+ } elsif (defined $u->{password}) {
+ $u->{hashedPassword} = hashPassword($u->{password});
+ }
+
+ $u->{fakePassword} = $existing->{fakePassword} // "x";
+ $usersOut{$name} = $u;
+
+ $uidMap->{$name} = $u->{uid};
+}
+
+# Update the persistent list of declarative users.
+updateFile($declUsersFile, join(" ", sort(keys %usersOut)));
+
+# Merge in the existing /etc/passwd.
+foreach my $name (keys %usersCur) {
+ my $u = $usersCur{$name};
+ next if defined $usersOut{$name};
+ if (!$spec->{mutableUsers} || defined $declUsers{$name}) {
+ print STDERR "removing user ‘$name’\n";
+ } else {
+ $usersOut{$name} = $u;
+ }
+}
+
+# Rewrite /etc/passwd. FIXME: acquire lock.
+@lines = map { join(":", $_->{name}, $_->{fakePassword}, $_->{uid}, $_->{gid}, $_->{description}, $_->{home}, $_->{shell}) . "\n" }
+ (sort { $a->{uid} <=> $b->{uid} } (values %usersOut));
+updateFile($uidMapFile, encode_json($uidMap));
+updateFile("/etc/passwd", \@lines);
+system("nscd --invalidate passwd");
+
+
+# Rewrite /etc/shadow to add new accounts or remove dead ones.
+my @shadowNew;
+my %shadowSeen;
+
+foreach my $line (-f "/etc/shadow" ? read_file("/etc/shadow") : ()) {
+ chomp $line;
+ my ($name, $hashedPassword, @rest) = split(':', $line, -9);
+ my $u = $usersOut{$name};;
+ next if !defined $u;
+ $hashedPassword = "!" if !$spec->{mutableUsers};
+ $hashedPassword = $u->{hashedPassword} if defined $u->{hashedPassword} && !$spec->{mutableUsers}; # FIXME
+ chomp $hashedPassword;
+ push @shadowNew, join(":", $name, $hashedPassword, @rest) . "\n";
+ $shadowSeen{$name} = 1;
+}
+
+foreach my $u (values %usersOut) {
+ next if defined $shadowSeen{$u->{name}};
+ my $hashedPassword = "!";
+ $hashedPassword = $u->{hashedPassword} if defined $u->{hashedPassword};
+ # FIXME: set correct value for sp_lstchg.
+ push @shadowNew, join(":", $u->{name}, $hashedPassword, "1::::::") . "\n";
+}
+
+updateFile("/etc/shadow", \@shadowNew, 0600);
diff --git a/nixpkgs/nixos/modules/config/users-groups.nix b/nixpkgs/nixos/modules/config/users-groups.nix
new file mode 100644
index 00000000000..ba79bd3d6ec
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/users-groups.nix
@@ -0,0 +1,601 @@
+{ config, lib, utils, pkgs, ... }:
+
+with lib;
+
+let
+ ids = config.ids;
+ cfg = config.users;
+
+ passwordDescription = ''
+ The options <option>hashedPassword</option>,
+ <option>password</option> and <option>passwordFile</option>
+ controls what password is set for the user.
+ <option>hashedPassword</option> overrides both
+ <option>password</option> and <option>passwordFile</option>.
+ <option>password</option> overrides <option>passwordFile</option>.
+ If none of these three options are set, no password is assigned to
+ the user, and the user will not be able to do password logins.
+ If the option <option>users.mutableUsers</option> is true, the
+ password defined in one of the three options will only be set when
+ the user is created for the first time. After that, you are free to
+ change the password with the ordinary user management commands. If
+ <option>users.mutableUsers</option> is false, you cannot change
+ user passwords, they will always be set according to the password
+ options.
+ '';
+
+ hashedPasswordDescription = ''
+ To generate hashed password install <literal>mkpasswd</literal>
+ package and run <literal>mkpasswd -m sha-512</literal>.
+ '';
+
+ userOpts = { name, config, ... }: {
+
+ options = {
+
+ name = mkOption {
+ type = types.str;
+ apply = x: assert (builtins.stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x;
+ description = ''
+ The name of the user account. If undefined, the name of the
+ attribute set will be used.
+ '';
+ };
+
+ description = mkOption {
+ type = types.str;
+ default = "";
+ example = "Alice Q. User";
+ description = ''
+ A short description of the user account, typically the
+ user's full name. This is actually the “GECOS” or “comment”
+ field in <filename>/etc/passwd</filename>.
+ '';
+ };
+
+ uid = mkOption {
+ type = with types; nullOr int;
+ default = null;
+ description = ''
+ The account UID. If the UID is null, a free UID is picked on
+ activation.
+ '';
+ };
+
+ isSystemUser = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Indicates if the user is a system user or not. This option
+ only has an effect if <option>uid</option> is
+ <option>null</option>, in which case it determines whether
+ the user's UID is allocated in the range for system users
+ (below 500) or in the range for normal users (starting at
+ 1000).
+ '';
+ };
+
+ isNormalUser = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Indicates whether this is an account for a “real” user. This
+ automatically sets <option>group</option> to
+ <literal>users</literal>, <option>createHome</option> to
+ <literal>true</literal>, <option>home</option> to
+ <filename>/home/<replaceable>username</replaceable></filename>,
+ <option>useDefaultShell</option> to <literal>true</literal>,
+ and <option>isSystemUser</option> to
+ <literal>false</literal>.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ apply = x: assert (builtins.stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x;
+ default = "nogroup";
+ description = "The user's primary group.";
+ };
+
+ extraGroups = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "The user's auxiliary groups.";
+ };
+
+ home = mkOption {
+ type = types.path;
+ default = "/var/empty";
+ description = "The user's home directory.";
+ };
+
+ cryptHomeLuks = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Path to encrypted luks device that contains
+ the user's home directory.
+ '';
+ };
+
+ shell = mkOption {
+ type = types.either types.shellPackage types.path;
+ default = pkgs.shadow;
+ defaultText = "pkgs.shadow";
+ example = literalExample "pkgs.bashInteractive";
+ description = ''
+ The path to the user's shell. Can use shell derivations,
+ like <literal>pkgs.bashInteractive</literal>. Don’t
+ forget to enable your shell in
+ <literal>programs</literal> if necessary,
+ like <code>programs.zsh.enable = true;</code>.
+ '';
+ };
+
+ subUidRanges = mkOption {
+ type = with types; listOf (submodule subordinateUidRange);
+ default = [];
+ example = [
+ { startUid = 1000; count = 1; }
+ { startUid = 100001; count = 65534; }
+ ];
+ description = ''
+ Subordinate user ids that user is allowed to use.
+ They are set into <filename>/etc/subuid</filename> and are used
+ by <literal>newuidmap</literal> for user namespaces.
+ '';
+ };
+
+ subGidRanges = mkOption {
+ type = with types; listOf (submodule subordinateGidRange);
+ default = [];
+ example = [
+ { startGid = 100; count = 1; }
+ { startGid = 1001; count = 999; }
+ ];
+ description = ''
+ Subordinate group ids that user is allowed to use.
+ They are set into <filename>/etc/subgid</filename> and are used
+ by <literal>newgidmap</literal> for user namespaces.
+ '';
+ };
+
+ createHome = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If true, the home directory will be created automatically. If this
+ option is true and the home directory already exists but is not
+ owned by the user, directory owner and group will be changed to
+ match the user.
+ '';
+ };
+
+ useDefaultShell = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If true, the user's shell will be set to
+ <option>users.defaultUserShell</option>.
+ '';
+ };
+
+ hashedPassword = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Specifies the hashed password for the user.
+ ${passwordDescription}
+ ${hashedPasswordDescription}
+ '';
+ };
+
+ password = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Specifies the (clear text) password for the user.
+ Warning: do not set confidential information here
+ because it is world-readable in the Nix store. This option
+ should only be used for public accounts.
+ ${passwordDescription}
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ The full path to a file that contains the user's password. The password
+ file is read on each system activation. The file should contain
+ exactly one line, which should be the password in an encrypted form
+ that is suitable for the <literal>chpasswd -e</literal> command.
+ ${passwordDescription}
+ '';
+ };
+
+ initialHashedPassword = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Specifies the initial hashed password for the user, i.e. the
+ hashed password assigned if the user does not already
+ exist. If <option>users.mutableUsers</option> is true, the
+ password can be changed subsequently using the
+ <command>passwd</command> command. Otherwise, it's
+ equivalent to setting the <option>hashedPassword</option> option.
+
+ ${hashedPasswordDescription}
+ '';
+ };
+
+ initialPassword = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Specifies the initial password for the user, i.e. the
+ password assigned if the user does not already exist. If
+ <option>users.mutableUsers</option> is true, the password
+ can be changed subsequently using the
+ <command>passwd</command> command. Otherwise, it's
+ equivalent to setting the <option>password</option>
+ option. The same caveat applies: the password specified here
+ is world-readable in the Nix store, so it should only be
+ used for guest accounts or passwords that will be changed
+ promptly.
+ '';
+ };
+
+ packages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "[ pkgs.firefox pkgs.thunderbird ]";
+ description = ''
+ The set of packages that should be made availabe to the user.
+ This is in contrast to <option>environment.systemPackages</option>,
+ which adds packages to all users.
+ '';
+ };
+
+ };
+
+ config = mkMerge
+ [ { name = mkDefault name;
+ shell = mkIf config.useDefaultShell (mkDefault cfg.defaultUserShell);
+ }
+ (mkIf config.isNormalUser {
+ group = mkDefault "users";
+ createHome = mkDefault true;
+ home = mkDefault "/home/${config.name}";
+ useDefaultShell = mkDefault true;
+ isSystemUser = mkDefault false;
+ })
+ # If !mutableUsers, setting ‘initialPassword’ is equivalent to
+ # setting ‘password’ (and similarly for hashed passwords).
+ (mkIf (!cfg.mutableUsers && config.initialPassword != null) {
+ password = mkDefault config.initialPassword;
+ })
+ (mkIf (!cfg.mutableUsers && config.initialHashedPassword != null) {
+ hashedPassword = mkDefault config.initialHashedPassword;
+ })
+ ];
+
+ };
+
+ groupOpts = { name, ... }: {
+
+ options = {
+
+ name = mkOption {
+ type = types.str;
+ description = ''
+ The name of the group. If undefined, the name of the attribute set
+ will be used.
+ '';
+ };
+
+ gid = mkOption {
+ type = with types; nullOr int;
+ default = null;
+ description = ''
+ The group GID. If the GID is null, a free GID is picked on
+ activation.
+ '';
+ };
+
+ members = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = ''
+ The user names of the group members, added to the
+ <literal>/etc/group</literal> file.
+ '';
+ };
+
+ };
+
+ config = {
+ name = mkDefault name;
+ };
+
+ };
+
+ subordinateUidRange = {
+ options = {
+ startUid = mkOption {
+ type = types.int;
+ description = ''
+ Start of the range of subordinate user ids that user is
+ allowed to use.
+ '';
+ };
+ count = mkOption {
+ type = types.int;
+ default = 1;
+ description = ''Count of subordinate user ids'';
+ };
+ };
+ };
+
+ subordinateGidRange = {
+ options = {
+ startGid = mkOption {
+ type = types.int;
+ description = ''
+ Start of the range of subordinate group ids that user is
+ allowed to use.
+ '';
+ };
+ count = mkOption {
+ type = types.int;
+ default = 1;
+ description = ''Count of subordinate group ids'';
+ };
+ };
+ };
+
+ mkSubuidEntry = user: concatStrings (
+ map (range: "${user.name}:${toString range.startUid}:${toString range.count}\n")
+ user.subUidRanges);
+
+ subuidFile = concatStrings (map mkSubuidEntry (attrValues cfg.users));
+
+ mkSubgidEntry = user: concatStrings (
+ map (range: "${user.name}:${toString range.startGid}:${toString range.count}\n")
+ user.subGidRanges);
+
+ subgidFile = concatStrings (map mkSubgidEntry (attrValues cfg.users));
+
+ idsAreUnique = set: idAttr: !(fold (name: args@{ dup, acc }:
+ let
+ id = builtins.toString (builtins.getAttr idAttr (builtins.getAttr name set));
+ exists = builtins.hasAttr id acc;
+ newAcc = acc // (builtins.listToAttrs [ { name = id; value = true; } ]);
+ in if dup then args else if exists
+ then builtins.trace "Duplicate ${idAttr} ${id}" { dup = true; acc = null; }
+ else { dup = false; acc = newAcc; }
+ ) { dup = false; acc = {}; } (builtins.attrNames set)).dup;
+
+ uidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) cfg.users) "uid";
+ gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid";
+
+ spec = pkgs.writeText "users-groups.json" (builtins.toJSON {
+ inherit (cfg) mutableUsers;
+ users = mapAttrsToList (_: u:
+ { inherit (u)
+ name uid group description home createHome isSystemUser
+ password passwordFile hashedPassword
+ initialPassword initialHashedPassword;
+ shell = utils.toShellPath u.shell;
+ }) cfg.users;
+ groups = mapAttrsToList (n: g:
+ { inherit (g) name gid;
+ members = g.members ++ (mapAttrsToList (n: u: u.name) (
+ filterAttrs (n: u: elem g.name u.extraGroups) cfg.users
+ ));
+ }) cfg.groups;
+ });
+
+ systemShells =
+ let
+ shells = mapAttrsToList (_: u: u.shell) cfg.users;
+ in
+ filter types.shellPackage.check shells;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ users.mutableUsers = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If set to <literal>true</literal>, you are free to add new users and groups to the system
+ with the ordinary <literal>useradd</literal> and
+ <literal>groupadd</literal> commands. On system activation, the
+ existing contents of the <literal>/etc/passwd</literal> and
+ <literal>/etc/group</literal> files will be merged with the
+ contents generated from the <literal>users.users</literal> and
+ <literal>users.groups</literal> options.
+ The initial password for a user will be set
+ according to <literal>users.users</literal>, but existing passwords
+ will not be changed.
+
+ <warning><para>
+ If set to <literal>false</literal>, the contents of the user and
+ group files will simply be replaced on system activation. This also
+ holds for the user passwords; all changed
+ passwords will be reset according to the
+ <literal>users.users</literal> configuration on activation.
+ </para></warning>
+ '';
+ };
+
+ users.enforceIdUniqueness = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to require that no two users/groups share the same uid/gid.
+ '';
+ };
+
+ users.users = mkOption {
+ default = {};
+ type = with types; loaOf (submodule userOpts);
+ example = {
+ alice = {
+ uid = 1234;
+ description = "Alice Q. User";
+ home = "/home/alice";
+ createHome = true;
+ group = "users";
+ extraGroups = ["wheel"];
+ shell = "/bin/sh";
+ };
+ };
+ description = ''
+ Additional user accounts to be created automatically by the system.
+ This can also be used to set options for root.
+ '';
+ };
+
+ users.groups = mkOption {
+ default = {};
+ example =
+ { students.gid = 1001;
+ hackers = { };
+ };
+ type = with types; loaOf (submodule groupOpts);
+ description = ''
+ Additional groups to be created automatically by the system.
+ '';
+ };
+
+ # FIXME: obsolete - will remove.
+ security.initialRootPassword = mkOption {
+ type = types.str;
+ default = "!";
+ example = "";
+ visible = false;
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ users.users = {
+ root = {
+ uid = ids.uids.root;
+ description = "System administrator";
+ home = "/root";
+ shell = mkDefault cfg.defaultUserShell;
+ group = "root";
+ initialHashedPassword = mkDefault config.security.initialRootPassword;
+ };
+ nobody = {
+ uid = ids.uids.nobody;
+ description = "Unprivileged account (don't use!)";
+ group = "nogroup";
+ };
+ };
+
+ users.groups = {
+ root.gid = ids.gids.root;
+ wheel.gid = ids.gids.wheel;
+ disk.gid = ids.gids.disk;
+ kmem.gid = ids.gids.kmem;
+ tty.gid = ids.gids.tty;
+ floppy.gid = ids.gids.floppy;
+ uucp.gid = ids.gids.uucp;
+ lp.gid = ids.gids.lp;
+ cdrom.gid = ids.gids.cdrom;
+ tape.gid = ids.gids.tape;
+ audio.gid = ids.gids.audio;
+ video.gid = ids.gids.video;
+ dialout.gid = ids.gids.dialout;
+ nogroup.gid = ids.gids.nogroup;
+ users.gid = ids.gids.users;
+ nixbld.gid = ids.gids.nixbld;
+ utmp.gid = ids.gids.utmp;
+ adm.gid = ids.gids.adm;
+ input.gid = ids.gids.input;
+ kvm.gid = ids.gids.kvm;
+ render.gid = ids.gids.render;
+ };
+
+ system.activationScripts.users = stringAfter [ "stdio" ]
+ ''
+ install -m 0700 -d /root
+ install -m 0755 -d /home
+
+ ${pkgs.perl}/bin/perl -w \
+ -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix} \
+ -I${pkgs.perlPackages.JSON}/${pkgs.perl.libPrefix} \
+ ${./update-users-groups.pl} ${spec}
+ '';
+
+ # for backwards compatibility
+ system.activationScripts.groups = stringAfter [ "users" ] "";
+
+ # Install all the user shells
+ environment.systemPackages = systemShells;
+
+ environment.etc = {
+ subuid = {
+ text = subuidFile;
+ mode = "0644";
+ };
+ subgid = {
+ text = subgidFile;
+ mode = "0644";
+ };
+ } // (mapAttrs' (name: { packages, ... }: {
+ name = "profiles/per-user/${name}";
+ value.source = pkgs.buildEnv {
+ name = "user-environment";
+ paths = packages;
+ inherit (config.environment) pathsToLink extraOutputsToInstall;
+ inherit (config.system.path) ignoreCollisions postBuild;
+ };
+ }) (filterAttrs (_: u: u.packages != []) cfg.users));
+
+ environment.profiles = [
+ "$HOME/.nix-profile"
+ "/etc/profiles/per-user/$USER"
+ ];
+
+ assertions = [
+ { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique);
+ message = "UIDs and GIDs must be unique!";
+ }
+ { # If mutableUsers is false, to prevent users creating a
+ # configuration that locks them out of the system, ensure that
+ # there is at least one "privileged" account that has a
+ # password or an SSH authorized key. Privileged accounts are
+ # root and users in the wheel group.
+ assertion = !cfg.mutableUsers ->
+ any id (mapAttrsToList (name: cfg:
+ (name == "root"
+ || cfg.group == "wheel"
+ || elem "wheel" cfg.extraGroups)
+ &&
+ ((cfg.hashedPassword != null && cfg.hashedPassword != "!")
+ || cfg.password != null
+ || cfg.passwordFile != null
+ || cfg.openssh.authorizedKeys.keys != []
+ || cfg.openssh.authorizedKeys.keyFiles != [])
+ ) cfg.users);
+ message = ''
+ Neither the root account nor any wheel user has a password or SSH authorized key.
+ You must set one to prevent being locked out of your system.'';
+ }
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/vpnc.nix b/nixpkgs/nixos/modules/config/vpnc.nix
new file mode 100644
index 00000000000..356e007c0a3
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/vpnc.nix
@@ -0,0 +1,41 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.networking.vpnc;
+ mkServiceDef = name: value:
+ {
+ name = "vpnc/${name}.conf";
+ value = { text = value; };
+ };
+
+in
+{
+ options = {
+ networking.vpnc = {
+ services = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = literalExample ''
+ { test = '''
+ IPSec gateway 192.168.1.1
+ IPSec ID someID
+ IPSec secret secretKey
+ Xauth username name
+ Xauth password pass
+ ''';
+ }
+ '';
+ description =
+ ''
+ The names of cisco VPNs and their associated definitions
+ '';
+ };
+ };
+ };
+
+ config.environment.etc = mapAttrs' mkServiceDef cfg.services;
+}
+
+
diff --git a/nixpkgs/nixos/modules/config/vte.nix b/nixpkgs/nixos/modules/config/vte.nix
new file mode 100644
index 00000000000..d4a8c926fef
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/vte.nix
@@ -0,0 +1,52 @@
+# VTE
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ vteInitSnippet = ''
+ # Show current working directory in VTE terminals window title.
+ # Supports both bash and zsh, requires interactive shell.
+ . ${pkgs.vte}/etc/profile.d/vte.sh
+ '';
+
+in
+
+{
+
+ options = {
+
+ programs.bash.vteIntegration = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration for VTE terminals.
+ This allows it to preserve the current directory of the shell
+ across terminals.
+ '';
+ };
+
+ programs.zsh.vteIntegration = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration for VTE terminals.
+ This allows it to preserve the current directory of the shell
+ across terminals.
+ '';
+ };
+
+ };
+
+ config = mkMerge [
+ (mkIf config.programs.bash.vteIntegration {
+ programs.bash.interactiveShellInit = mkBefore vteInitSnippet;
+ })
+
+ (mkIf config.programs.zsh.vteIntegration {
+ programs.zsh.interactiveShellInit = vteInitSnippet;
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/config/xdg/autostart.nix b/nixpkgs/nixos/modules/config/xdg/autostart.nix
new file mode 100644
index 00000000000..0ee94fed818
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/xdg/autostart.nix
@@ -0,0 +1,22 @@
+{ config, lib, ... }:
+
+with lib;
+{
+ options = {
+ xdg.autostart.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to install files to support the
+ <link xlink:href="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html">XDG Autostart specification</link>.
+ '';
+ };
+ };
+
+ config = mkIf config.xdg.autostart.enable {
+ environment.pathsToLink = [
+ "/etc/xdg/autostart"
+ ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/xdg/icons.nix b/nixpkgs/nixos/modules/config/xdg/icons.nix
new file mode 100644
index 00000000000..4677ce090b0
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/xdg/icons.nix
@@ -0,0 +1,38 @@
+{ config, lib, ... }:
+
+with lib;
+{
+ options = {
+ xdg.icons.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to install files to support the
+ <link xlink:href="https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html">XDG Icon Theme specification</link>.
+ '';
+ };
+ };
+
+ config = mkIf config.xdg.icons.enable {
+ environment.pathsToLink = [
+ "/share/icons"
+ "/share/pixmaps"
+ ];
+
+ # libXcursor looks for cursors in XCURSOR_PATH
+ # it mostly follows the spec for icons
+ # See: https://www.x.org/releases/current/doc/man/man3/Xcursor.3.xhtml Themes
+
+ # These are preferred so they come first in the list
+ environment.sessionVariables.XCURSOR_PATH = [
+ "$HOME/.icons"
+ "$HOME/.local/share/icons"
+ ];
+
+ environment.profileRelativeSessionVariables.XCURSOR_PATH = [
+ "/share/icons"
+ "/share/pixmaps"
+ ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/xdg/menus.nix b/nixpkgs/nixos/modules/config/xdg/menus.nix
new file mode 100644
index 00000000000..c172692df5d
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/xdg/menus.nix
@@ -0,0 +1,25 @@
+{ config, lib, ... }:
+
+with lib;
+{
+ options = {
+ xdg.menus.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to install files to support the
+ <link xlink:href="https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html">XDG Desktop Menu specification</link>.
+ '';
+ };
+ };
+
+ config = mkIf config.xdg.menus.enable {
+ environment.pathsToLink = [
+ "/share/applications"
+ "/share/desktop-directories"
+ "/etc/xdg/menus"
+ "/etc/xdg/menus/applications-merged"
+ ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/xdg/mime.nix b/nixpkgs/nixos/modules/config/xdg/mime.nix
new file mode 100644
index 00000000000..a5374c2b468
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/xdg/mime.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+{
+ options = {
+ xdg.mime.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to install files to support the
+ <link xlink:href="https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html">XDG Shared MIME-info specification</link> and the
+ <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html">XDG MIME Applications specification</link>.
+ '';
+ };
+ };
+
+ config = mkIf config.xdg.mime.enable {
+ environment.pathsToLink = [ "/share/mime" ];
+
+ environment.systemPackages = [
+ # this package also installs some useful data, as well as its utilities
+ pkgs.shared-mime-info
+ ];
+
+ environment.extraSetup = ''
+ if [ -w $out/share/mime ] && [ -d $out/share/mime/packages ]; then
+ XDG_DATA_DIRS=$out/share PKGSYSTEM_ENABLE_FSYNC=0 ${pkgs.buildPackages.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
+ fi
+
+ if [ -w $out/share/applications ]; then
+ ${pkgs.buildPackages.desktop-file-utils}/bin/update-desktop-database $out/share/applications
+ fi
+ '';
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/config/xdg/portal.nix b/nixpkgs/nixos/modules/config/xdg/portal.nix
new file mode 100644
index 00000000000..bdbbfda2bb4
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/xdg/portal.nix
@@ -0,0 +1,58 @@
+{ config, pkgs ,lib ,... }:
+
+with lib;
+
+{
+ options.xdg.portal = {
+ enable =
+ mkEnableOption "<link xlink:href='https://github.com/flatpak/xdg-desktop-portal'>xdg desktop integration</link>"//{
+ default = false;
+ };
+
+ extraPortals = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = ''
+ List of additional portals to add to path. Portals allow interaction
+ with system, like choosing files or taking screenshots. At minimum,
+ a desktop portal implementation should be listed. GNOME and KDE already
+ adds <package>xdg-desktop-portal-gtk</package>; and
+ <package>xdg-desktop-portal-kde</package> respectively. On other desktop
+ environments you probably want to add them yourself.
+ '';
+ };
+
+ gtkUsePortal = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Sets environment variable <literal>GTK_USE_PORTAL</literal> to <literal>1</literal>.
+ This is needed for packages ran outside Flatpak to respect and use XDG Desktop Portals.
+ For example, you'd need to set this for non-flatpak Firefox to use native filechoosers.
+ Defaults to <literal>false</literal> to respect its opt-in nature.
+ '';
+ };
+ };
+
+ config =
+ let
+ cfg = config.xdg.portal;
+ packages = [ pkgs.xdg-desktop-portal ] ++ cfg.extraPortals;
+
+ in mkIf cfg.enable {
+
+ assertions = [
+ { assertion = (cfg.gtkUsePortal -> cfg.extraPortals != []);
+ message = "Setting xdg.portal.gtkUsePortal to true requires a portal implementation in xdg.portal.extraPortals such as xdg-desktop-portal-gtk or xdg-desktop-portal-kde.";
+ }
+ ];
+
+ services.dbus.packages = packages;
+ systemd.packages = packages;
+
+ environment.variables = {
+ GTK_USE_PORTAL = mkIf cfg.gtkUsePortal "1";
+ XDG_DESKTOP_PORTAL_PATH = map (p: "${p}/share/xdg-desktop-portal/portals") cfg.extraPortals;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/config/xdg/sounds.nix b/nixpkgs/nixos/modules/config/xdg/sounds.nix
new file mode 100644
index 00000000000..148240d631c
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/config/zram.nix b/nixpkgs/nixos/modules/config/zram.nix
new file mode 100644
index 00000000000..5d411c73a56
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/zram.nix
@@ -0,0 +1,189 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.zramSwap;
+
+ # 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
+
+{
+
+ ###### interface
+
+ options = {
+
+ zramSwap = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ 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>.
+ '';
+ };
+
+ numDevices = mkOption {
+ default = 1;
+ type = types.int;
+ description = ''
+ 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.
+ '';
+ };
+
+ memoryPercent = mkOption {
+ default = 50;
+ type = types.int;
+ 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. Run <literal>zramctl</literal> to check how good memory is
+ compressed.
+ '';
+ };
+
+ priority = mkOption {
+ default = 5;
+ type = types.int;
+ description = ''
+ Priority of the zram swap devices. It should be a number higher than
+ the priority of your disk-based swap devices (so that the system will
+ fill the zram swap devices before falling back to disk swap).
+ '';
+ };
+
+ algorithm = mkOption {
+ default = "lzo";
+ example = "lz4";
+ 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, but requires newer kernel.
+ 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")
+ ];
+
+ # Disabling this for the moment, as it would create and mkswap devices twice,
+ # once in stage 2 boot, and again when the zram-reloader service starts.
+ # boot.kernelModules = [ "zram" ];
+
+ boot.extraModprobeConfig = ''
+ options zram num_devices=${toString cfg.numDevices}
+ '';
+
+ services.udev.extraRules = ''
+ KERNEL=="zram[0-9]*", ENV{SYSTEMD_WANTS}="zram-init-%k.service", TAG+="systemd"
+ '';
+
+ systemd.services =
+ let
+ createZramInitService = dev:
+ nameValuePair "zram-init-${dev}" {
+ description = "Init swap on zram-based device ${dev}";
+ 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 -euo pipefail
+
+ # Calculate memory to use for zram
+ mem=$(${pkgs.gawk}/bin/awk '/MemTotal: / {
+ print int($2*${toString cfg.memoryPercent}/100.0/${toString devicesCount}*1024)
+ }' /proc/meminfo)
+
+ ${pkgs.utillinux}/sbin/zramctl --size $mem --algorithm ${cfg.algorithm} /dev/${dev}
+ ${pkgs.utillinux}/sbin/mkswap /dev/${dev}
+ '';
+ restartIfChanged = false;
+ };
+ 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;
+ ExecStartPre = "${modprobe} -r zram";
+ ExecStart = "${modprobe} zram";
+ ExecStop = "${modprobe} -r zram";
+ };
+ restartTriggers = [
+ cfg.numDevices
+ cfg.algorithm
+ cfg.memoryPercent
+ ];
+ restartIfChanged = true;
+ })]);
+
+ swapDevices =
+ let
+ useZramSwap = dev:
+ {
+ device = "/dev/${dev}";
+ priority = cfg.priority;
+ };
+ in map useZramSwap devices;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/acpilight.nix b/nixpkgs/nixos/modules/hardware/acpilight.nix
new file mode 100644
index 00000000000..34e8a222096
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/acpilight.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.hardware.acpilight;
+in
+{
+ options = {
+ hardware.acpilight = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable acpilight.
+ This will allow brightness control via xbacklight from users in the video group
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.udev.packages = with pkgs; [ acpilight ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/all-firmware.nix b/nixpkgs/nixos/modules/hardware/all-firmware.nix
new file mode 100644
index 00000000000..534fcc34276
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/all-firmware.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.hardware;
+in {
+
+ ###### interface
+
+ options = {
+
+ hardware.enableAllFirmware = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Turn on this option if you want to enable all the firmware.
+ '';
+ };
+
+ hardware.enableRedistributableFirmware = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Turn on this option if you want to enable all the firmware with a license allowing redistribution.
+ (i.e. free firmware and <literal>firmware-linux-nonfree</literal>)
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkMerge [
+ (mkIf (cfg.enableAllFirmware || cfg.enableRedistributableFirmware) {
+ hardware.firmware = with pkgs; [
+ firmwareLinuxNonfree
+ intel2200BGFirmware
+ rtl8192su-firmware
+ rt5677-firmware
+ rtl8723bs-firmware
+ rtlwifi_new-firmware
+ zd1211fw
+ alsa-firmware
+ openelec-dvb-firmware
+ ] ++ optional (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) raspberrypiWirelessFirmware
+ ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "4.13") [
+ rtl8723bs-firmware
+ ];
+ })
+ (mkIf cfg.enableAllFirmware {
+ assertions = [{
+ assertion = !cfg.enableAllFirmware || (config.nixpkgs.config.allowUnfree or false);
+ message = ''
+ the list of hardware.enableAllFirmware contains non-redistributable licensed firmware files.
+ This requires nixpkgs.config.allowUnfree to be true.
+ An alternative is to use the hardware.enableRedistributableFirmware option.
+ '';
+ }];
+ hardware.firmware = with pkgs; [
+ broadcom-bt-firmware
+ b43Firmware_5_1_138
+ b43Firmware_6_30_163_46
+ b43FirmwareCutter
+ ] ++ optional (pkgs.stdenv.hostPlatform.isi686 || pkgs.stdenv.hostPlatform.isx86_64) facetimehd-firmware;
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/hardware/bladeRF.nix b/nixpkgs/nixos/modules/hardware/bladeRF.nix
new file mode 100644
index 00000000000..92544347714
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/hardware/brightnessctl.nix b/nixpkgs/nixos/modules/hardware/brightnessctl.nix
new file mode 100644
index 00000000000..2d54398d10d
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/brightnessctl.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.hardware.brightnessctl;
+in
+{
+
+ options = {
+
+ hardware.brightnessctl = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable brightnessctl in userspace.
+ This will allow brightness control from users in the video group.
+ '';
+
+ };
+ };
+ };
+
+
+ config = mkIf cfg.enable {
+ services.udev.packages = with pkgs; [ brightnessctl ];
+ environment.systemPackages = with pkgs; [ brightnessctl ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/ckb-next.nix b/nixpkgs/nixos/modules/hardware/ckb-next.nix
new file mode 100644
index 00000000000..20b2756d8b2
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/hardware/cpu/amd-microcode.nix b/nixpkgs/nixos/modules/hardware/cpu/amd-microcode.nix
new file mode 100644
index 00000000000..621c7066bfe
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/cpu/amd-microcode.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ hardware.cpu.amd.updateMicrocode = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Update the CPU microcode for AMD processors.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.hardware.cpu.amd.updateMicrocode {
+ # Microcode updates must be the first item prepended in the initrd
+ boot.initrd.prepend = mkOrder 1 [ "${pkgs.microcodeAmd}/amd-ucode.img" ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/cpu/intel-microcode.nix b/nixpkgs/nixos/modules/hardware/cpu/intel-microcode.nix
new file mode 100644
index 00000000000..acce565fd80
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/cpu/intel-microcode.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ hardware.cpu.intel.updateMicrocode = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Update the CPU microcode for Intel processors.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.hardware.cpu.intel.updateMicrocode {
+ # Microcode updates must be the first item prepended in the initrd
+ boot.initrd.prepend = mkOrder 1 [ "${pkgs.microcodeIntel}/intel-ucode.img" ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/device-tree.nix b/nixpkgs/nixos/modules/hardware/device-tree.nix
new file mode 100644
index 00000000000..f57502d4c83
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/device-tree.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.hardware.deviceTree;
+in {
+ options = {
+ hardware.deviceTree = {
+ enable = mkOption {
+ default = pkgs.stdenv.hostPlatform.platform.kernelDTB or false;
+ type = types.bool;
+ description = ''
+ Build device tree files. These are used to describe the
+ non-discoverable hardware of a system.
+ '';
+ };
+
+ base = mkOption {
+ default = "${config.boot.kernelPackages.kernel}/dtbs";
+ defaultText = "\${config.boot.kernelPackages.kernel}/dtbs";
+ example = literalExample "pkgs.deviceTree_rpi";
+ type = types.path;
+ description = ''
+ The package containing the base device-tree (.dtb) to boot. Contains
+ device trees bundled with the Linux kernel by default.
+ '';
+ };
+
+ overlays = mkOption {
+ default = [];
+ example = literalExample
+ "[\"\${pkgs.deviceTree_rpi.overlays}/w1-gpio.dtbo\"]";
+ type = types.listOf types.path;
+ description = ''
+ A path containing device tree overlays (.dtbo) to be applied to all
+ base device-trees.
+ '';
+ };
+
+ package = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ internal = true;
+ description = ''
+ A path containing the result of applying `overlays` to `base`.
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.enable) {
+ hardware.deviceTree.package = if (cfg.overlays != [])
+ then pkgs.deviceTree.applyOverlays cfg.base cfg.overlays else cfg.base;
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/digitalbitbox.nix b/nixpkgs/nixos/modules/hardware/digitalbitbox.nix
new file mode 100644
index 00000000000..0888cfbef2a
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/digitalbitbox.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.hardware.digitalbitbox;
+in
+
+{
+ options.hardware.digitalbitbox = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables udev rules for Digital Bitbox devices.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.digitalbitbox;
+ defaultText = "pkgs.digitalbitbox";
+ description = "The Digital Bitbox package to use. This can be used to install a package with udev rules that differ from the defaults.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.udev.packages = [ cfg.package ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/ksm.nix b/nixpkgs/nixos/modules/hardware/ksm.nix
new file mode 100644
index 00000000000..99d46c25236
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/ksm.nix
@@ -0,0 +1,34 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.hardware.ksm;
+
+in {
+ options.hardware.ksm = {
+ enable = mkEnableOption "Kernel Same-Page Merging";
+ sleep = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ How many milliseconds ksmd should sleep between scans.
+ Setting it to <literal>null</literal> uses the kernel's default time.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.enable-ksm = {
+ description = "Enable Kernel Same-Page Merging";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "systemd-udev-settle.service" ];
+ script = ''
+ if [ -e /sys/kernel/mm/ksm ]; then
+ echo 1 > /sys/kernel/mm/ksm/run
+ ${optionalString (cfg.sleep != null) ''echo ${toString cfg.sleep} > /sys/kernel/mm/ksm/sleep_millisecs''}
+ fi
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/ledger.nix b/nixpkgs/nixos/modules/hardware/ledger.nix
new file mode 100644
index 00000000000..41abe74315a
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/ledger.nix
@@ -0,0 +1,14 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.hardware.ledger;
+
+in {
+ options.hardware.ledger.enable = mkEnableOption "udev rules for Ledger devices";
+
+ config = mkIf cfg.enable {
+ services.udev.packages = [ pkgs.ledger-udev-rules ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/logitech.nix b/nixpkgs/nixos/modules/hardware/logitech.nix
new file mode 100644
index 00000000000..d6f43bdddcc
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/logitech.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.hardware.logitech;
+
+in {
+ options.hardware.logitech = {
+ enable = mkEnableOption "Logitech Devices";
+
+ enableGraphical = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable graphical support applications.";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = [
+ pkgs.ltunify
+ ] ++ lib.optional cfg.enableGraphical pkgs.solaar;
+
+ # ltunifi and solaar both provide udev rules but the most up-to-date have been split
+ # out into a dedicated derivation
+ services.udev.packages = with pkgs; [ logitech-udev-rules ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/mcelog.nix b/nixpkgs/nixos/modules/hardware/mcelog.nix
new file mode 100644
index 00000000000..13ad238870c
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/mcelog.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = with maintainers; [ grahamc ];
+ options = {
+
+ hardware.mcelog = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the Machine Check Exception logger.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf config.hardware.mcelog.enable {
+ systemd = {
+ packages = [ pkgs.mcelog ];
+
+ services.mcelog = {
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ProtectHome = true;
+ PrivateNetwork = true;
+ PrivateTmp = true;
+ };
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/network/b43.nix b/nixpkgs/nixos/modules/hardware/network/b43.nix
new file mode 100644
index 00000000000..e63f2d04d1a
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/network/b43.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let kernelVersion = config.boot.kernelPackages.kernel.version; in
+
+{
+
+ ###### interface
+
+ options = {
+
+ networking.enableB43Firmware = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Turn on this option if you want firmware for the NICs supported by the b43 module.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.networking.enableB43Firmware {
+ assertions = singleton
+ { assertion = lessThan 0 (builtins.compareVersions kernelVersion "3.2");
+ message = "b43 firmware for kernels older than 3.2 not packaged yet!";
+ };
+ hardware.firmware = [ pkgs.b43Firmware_5_1_138 ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/network/broadcom-43xx.nix b/nixpkgs/nixos/modules/hardware/network/broadcom-43xx.nix
new file mode 100644
index 00000000000..c92b7a0509d
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/network/broadcom-43xx.nix
@@ -0,0 +1,3 @@
+{
+ hardware.enableRedistributableFirmware = true;
+}
diff --git a/nixpkgs/nixos/modules/hardware/network/intel-2200bg.nix b/nixpkgs/nixos/modules/hardware/network/intel-2200bg.nix
new file mode 100644
index 00000000000..17b973474c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/network/intel-2200bg.nix
@@ -0,0 +1,30 @@
+{ config, pkgs, lib, ... }:
+
+{
+
+ ###### interface
+
+ options = {
+
+ networking.enableIntel2200BGFirmware = lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ description = ''
+ Turn on this option if you want firmware for the Intel
+ PRO/Wireless 2200BG to be loaded automatically. This is
+ required if you want to use this device.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = lib.mkIf config.networking.enableIntel2200BGFirmware {
+
+ hardware.firmware = [ pkgs.intel2200BGFirmware ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/network/smc-2632w/default.nix b/nixpkgs/nixos/modules/hardware/network/smc-2632w/default.nix
new file mode 100644
index 00000000000..b00286464f3
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/network/smc-2632w/default.nix
@@ -0,0 +1,9 @@
+{lib, ...}:
+
+{
+ hardware = {
+ pcmcia = {
+ firmware = [ (lib.cleanSource ./firmware) ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/network/smc-2632w/firmware/cis/SMC2632W-v1.02.cis b/nixpkgs/nixos/modules/hardware/network/smc-2632w/firmware/cis/SMC2632W-v1.02.cis
new file mode 100644
index 00000000000..5f13088c373
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/network/smc-2632w/firmware/cis/SMC2632W-v1.02.cis
@@ -0,0 +1,8 @@
+ vers_1 5.0, "SMC", "SMC2632W", "Version 01.02", ""
+ manfid 0x0156, 0x0002
+ funcid network_adapter
+ cftable_entry 0x01 [default]
+ Vcc Vmin 3000mV Vmax 3300mV Iavg 300mA Ipeak 300mA
+ Idown 10mA
+ io 0x0000-0x003f [lines=6] [16bit]
+ irq mask 0xffff [level] [pulse]
diff --git a/nixpkgs/nixos/modules/hardware/network/zydas-zd1211.nix b/nixpkgs/nixos/modules/hardware/network/zydas-zd1211.nix
new file mode 100644
index 00000000000..5dd7f30ed82
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/network/zydas-zd1211.nix
@@ -0,0 +1,5 @@
+{pkgs, ...}:
+
+{
+ hardware.firmware = [ pkgs.zd1211fw ];
+}
diff --git a/nixpkgs/nixos/modules/hardware/nitrokey.nix b/nixpkgs/nixos/modules/hardware/nitrokey.nix
new file mode 100644
index 00000000000..02e4c3f46f8
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/nitrokey.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.hardware.nitrokey;
+
+in
+
+{
+ options.hardware.nitrokey = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables udev rules for Nitrokey devices. By default grants access
+ to users in the "nitrokey" group. You may want to install the
+ nitrokey-app package, depending on your device and needs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "nitrokey";
+ example = "wheel";
+ description = ''
+ Grant access to Nitrokey devices to users in this group.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.udev.packages = [
+ (pkgs.nitrokey-udev-rules.override (attrs:
+ { inherit (cfg) group; }
+ ))
+ ];
+ users.groups.${cfg.group} = {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/onlykey.nix b/nixpkgs/nixos/modules/hardware/onlykey.nix
new file mode 100644
index 00000000000..b6820fe0191
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/onlykey.nix
@@ -0,0 +1,33 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+
+ ####### interface
+
+ options = {
+
+ hardware.onlykey = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable OnlyKey device (https://crp.to/p/) support.
+ '';
+ };
+ };
+
+ };
+
+ ## As per OnlyKey's documentation piece (hhttps://docs.google.com/document/d/1Go_Rs218fKUx-j_JKhddbSVTqY6P0vQO831t2MKCJC8),
+ ## it is important to add udev rule for OnlyKey for it to work on Linux
+
+ ####### implementation
+
+ config = mkIf config.hardware.onlykey.enable {
+ services.udev.extraRules = builtin.readFile ./onlykey.udev;
+ };
+
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/onlykey.udev b/nixpkgs/nixos/modules/hardware/onlykey.udev
new file mode 100644
index 00000000000..6583530e568
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/onlykey.udev
@@ -0,0 +1,4 @@
+ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789A]?", ENV{MTP_NO_PROBE}="1"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789ABCD]?", GROUP+="plugdev"
+KERNEL=="ttyACM*", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", GROUP+="plugdev"
diff --git a/nixpkgs/nixos/modules/hardware/opengl.nix b/nixpkgs/nixos/modules/hardware/opengl.nix
new file mode 100644
index 00000000000..57cac56bd8a
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/opengl.nix
@@ -0,0 +1,169 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.hardware.opengl;
+
+ kernelPackages = config.boot.kernelPackages;
+
+ videoDrivers = config.services.xserver.videoDrivers;
+
+ makePackage = p: pkgs.buildEnv {
+ name = "mesa-drivers+txc-${p.mesa.version}";
+ paths =
+ [ p.mesa.drivers
+ (if cfg.s3tcSupport then p.libtxc_dxtn else p.libtxc_dxtn_s2tc)
+ ];
+ };
+
+ package = pkgs.buildEnv {
+ name = "opengl-drivers";
+ paths = [ cfg.package ] ++ cfg.extraPackages;
+ };
+
+ package32 = pkgs.buildEnv {
+ name = "opengl-drivers-32bit";
+ paths = [ cfg.package32 ] ++ cfg.extraPackages32;
+ };
+
+in
+
+{
+ options = {
+
+ hardware.opengl = {
+ enable = mkOption {
+ description = ''
+ Whether to enable OpenGL drivers. This is needed to enable
+ OpenGL support in X11 systems, as well as for Wayland compositors
+ like sway, way-cooler and Weston. It is enabled by default
+ by the corresponding modules, so you do not usually have to
+ set it yourself, only if there is no module for your wayland
+ compositor of choice. See services.xserver.enable,
+ programs.sway.enable, and programs.way-cooler.enable.
+ '';
+ type = types.bool;
+ default = false;
+ };
+
+ driSupport = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable accelerated OpenGL rendering through the
+ Direct Rendering Interface (DRI).
+ '';
+ };
+
+ driSupport32Bit = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ On 64-bit systems, whether to support Direct Rendering for
+ 32-bit applications (such as Wine). This is currently only
+ supported for the <literal>nvidia</literal> and
+ <literal>ati_unfree</literal> drivers, as well as
+ <literal>Mesa</literal>.
+ '';
+ };
+
+ s3tcSupport = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Make S3TC(S3 Texture Compression) via libtxc_dxtn available
+ to OpenGL drivers instead of the patent-free S2TC replacement.
+
+ Using this library may require a patent license depending on your location.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ internal = true;
+ description = ''
+ The package that provides the OpenGL implementation.
+ '';
+ };
+
+ package32 = mkOption {
+ type = types.package;
+ internal = true;
+ description = ''
+ The package that provides the 32-bit OpenGL implementation on
+ 64-bit systems. Used when <option>driSupport32Bit</option> is
+ set.
+ '';
+ };
+
+ extraPackages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "with pkgs; [ vaapiIntel libvdpau-va-gl vaapiVdpau intel-ocl ]";
+ description = ''
+ Additional packages to add to OpenGL drivers. This can be used
+ to add OpenCL drivers, VA-API/VDPAU drivers etc.
+ '';
+ };
+
+ extraPackages32 = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "with pkgs.pkgsi686Linux; [ vaapiIntel libvdpau-va-gl vaapiVdpau ]";
+ description = ''
+ Additional packages to add to 32-bit OpenGL drivers on
+ 64-bit systems. Used when <option>driSupport32Bit</option> is
+ set. This can be used to add OpenCL drivers, VA-API/VDPAU drivers etc.
+ '';
+ };
+
+ setLdLibraryPath = mkOption {
+ type = types.bool;
+ internal = true;
+ default = false;
+ description = ''
+ Whether the <literal>LD_LIBRARY_PATH</literal> environment variable
+ should be set to the locations of driver libraries. Drivers which
+ rely on overriding libraries should set this to true. Drivers which
+ support <literal>libglvnd</literal> and other dispatch libraries
+ instead of overriding libraries should not set this.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ 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 = mkIf cfg.setLdLibraryPath
+ ([ "/run/opengl-driver/lib" ] ++ optional cfg.driSupport32Bit "/run/opengl-driver-32/lib");
+
+ hardware.opengl.package = mkDefault (makePackage pkgs);
+ hardware.opengl.package32 = mkDefault (makePackage pkgs.pkgsi686Linux);
+
+ boot.extraModulePackages = optional (elem "virtualbox" videoDrivers) kernelPackages.virtualboxGuestAdditions;
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/openrazer.nix b/nixpkgs/nixos/modules/hardware/openrazer.nix
new file mode 100644
index 00000000000..883db7f2f4f
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/openrazer.nix
@@ -0,0 +1,133 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.hardware.openrazer;
+ kernelPackages = config.boot.kernelPackages;
+
+ toPyBoolStr = b: if b then "True" else "False";
+
+ daemonExe = "${pkgs.openrazer-daemon}/bin/openrazer-daemon --config ${daemonConfFile}";
+
+ daemonConfFile = pkgs.writeTextFile {
+ name = "razer.conf";
+ text = ''
+ [General]
+ verbose_logging = ${toPyBoolStr cfg.verboseLogging}
+
+ [Startup]
+ sync_effects_enabled = ${toPyBoolStr cfg.syncEffectsEnabled}
+ devices_off_on_screensaver = ${toPyBoolStr cfg.devicesOffOnScreensaver}
+ mouse_battery_notifier = ${toPyBoolStr cfg.mouseBatteryNotifier}
+
+ [Statistics]
+ key_statistics = ${toPyBoolStr cfg.keyStatistics}
+ '';
+ };
+
+ dbusServiceFile = pkgs.writeTextFile rec {
+ name = "org.razer.service";
+ destination = "/share/dbus-1/services/${name}";
+ text = ''
+ [D-BUS Service]
+ Name=org.razer
+ Exec=${daemonExe}
+ SystemdService=openrazer-daemon.service
+ '';
+ };
+
+ drivers = [
+ "razerkbd"
+ "razermouse"
+ "razerfirefly"
+ "razerkraken"
+ "razermug"
+ "razercore"
+ ];
+in
+{
+ options = {
+ hardware.openrazer = {
+ enable = mkEnableOption "OpenRazer drivers and userspace daemon.";
+
+ verboseLogging = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable verbose logging. Logs debug messages.
+ '';
+ };
+
+ syncEffectsEnabled = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Set the sync effects flag to true so any assignment of
+ effects will work across devices.
+ '';
+ };
+
+ devicesOffOnScreensaver = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Turn off the devices when the systems screensaver kicks in.
+ '';
+ };
+
+ mouseBatteryNotifier = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Mouse battery notifier.
+ '';
+ };
+
+ keyStatistics = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Collects number of keypresses per hour per key used to
+ generate a heatmap.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ boot.extraModulePackages = [ kernelPackages.openrazer ];
+ boot.kernelModules = drivers;
+
+ # Makes the man pages available so you can succesfully run
+ # > systemctl --user help openrazer-daemon
+ environment.systemPackages = [ pkgs.python3Packages.openrazer-daemon.man ];
+
+ services.udev.packages = [ kernelPackages.openrazer ];
+ services.dbus.packages = [ dbusServiceFile ];
+
+ # A user must be a member of the plugdev group in order to start
+ # the openrazer-daemon. Therefore we make sure that the plugdev
+ # group exists.
+ users.groups.plugdev = {};
+
+ systemd.user.services.openrazer-daemon = {
+ description = "Daemon to manage razer devices in userspace";
+ unitConfig.Documentation = "man:openrazer-daemon(8)";
+ # Requires a graphical session so the daemon knows when the screensaver
+ # starts. See the 'devicesOffOnScreensaver' option.
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+ serviceConfig = {
+ Type = "dbus";
+ BusName = "org.razer";
+ ExecStart = "${daemonExe} --foreground";
+ Restart = "always";
+ };
+ };
+ };
+
+ meta = {
+ maintainers = with lib.maintainers; [ roelvandijk ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/pcmcia.nix b/nixpkgs/nixos/modules/hardware/pcmcia.nix
new file mode 100644
index 00000000000..d7d002ae6c8
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/pcmcia.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ pcmciaUtils = pkgs.pcmciaUtils.passthru.function {
+ inherit (config.hardware.pcmcia) firmware config;
+ };
+
+in
+
+
+{
+ ###### interface
+
+ options = {
+
+ hardware.pcmcia = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable this option to support PCMCIA card.
+ '';
+ };
+
+ firmware = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ List of firmware used to handle specific PCMCIA card.
+ '';
+ };
+
+ config = mkOption {
+ default = null;
+ description = ''
+ Path to the configuration file which maps the memory, IRQs
+ and ports used by the PCMCIA hardware.
+ '';
+ };
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf config.hardware.pcmcia.enable {
+
+ boot.kernelModules = [ "pcmcia" ];
+
+ services.udev.packages = [ pcmciaUtils ];
+
+ environment.systemPackages = [ pcmciaUtils ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/printers.nix b/nixpkgs/nixos/modules/hardware/printers.nix
new file mode 100644
index 00000000000..56b91933477
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/printers.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.hardware.printers;
+ ppdOptionsString = options: optionalString (options != {})
+ (concatStringsSep " "
+ (mapAttrsToList (name: value: "-o '${name}'='${value}'") options)
+ );
+ ensurePrinter = p: ''
+ ${pkgs.cups}/bin/lpadmin -p '${p.name}' -E \
+ ${optionalString (p.location != null) "-L '${p.location}'"} \
+ ${optionalString (p.description != null) "-D '${p.description}'"} \
+ -v '${p.deviceUri}' \
+ -m '${p.model}' \
+ ${ppdOptionsString p.ppdOptions}
+ '';
+ ensureDefaultPrinter = name: ''
+ ${pkgs.cups}/bin/lpoptions -d '${name}'
+ '';
+
+ # "graph but not # or /" can't be implemented as regex alone due to missing lookahead support
+ noInvalidChars = str: all (c: c != "#" && c != "/") (stringToCharacters str);
+ printerName = (types.addCheck (types.strMatching "[[:graph:]]+") noInvalidChars)
+ // { description = "printable string without spaces, # and /"; };
+
+
+in {
+ options = {
+ hardware.printers = {
+ ensureDefaultPrinter = mkOption {
+ type = types.nullOr printerName;
+ default = null;
+ description = ''
+ Ensures the named printer is the default CUPS printer / printer queue.
+ '';
+ };
+ ensurePrinters = mkOption {
+ description = ''
+ Will regularly ensure that the given CUPS printers are configured as declared here.
+ If a printer's options are manually changed afterwards, they will be overwritten eventually.
+ This option will never delete any printer, even if removed from this list.
+ You can check existing printers with <command>lpstat -s</command>
+ and remove printers with <command>lpadmin -x &lt;printer-name&gt;</command>.
+ Printers not listed here can still be manually configured.
+ '';
+ default = [];
+ type = types.listOf (types.submodule {
+ options = {
+ name = mkOption {
+ type = printerName;
+ example = "BrotherHL_Workroom";
+ description = ''
+ Name of the printer / printer queue.
+ May contain any printable characters except "/", "#", and space.
+ '';
+ };
+ location = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "Workroom";
+ description = ''
+ Optional human-readable location.
+ '';
+ };
+ description = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "Brother HL-5140";
+ description = ''
+ Optional human-readable description.
+ '';
+ };
+ deviceUri = mkOption {
+ type = types.str;
+ example = [
+ "ipp://printserver.local/printers/BrotherHL_Workroom"
+ "usb://HP/DESKJET%20940C?serial=CN16E6C364BH"
+ ];
+ description = ''
+ How to reach the printer.
+ <command>lpinfo -v</command> shows a list of supported device URIs and schemes.
+ '';
+ };
+ model = mkOption {
+ type = types.str;
+ example = literalExample ''
+ gutenprint.''${lib.version.majorMinor (lib.getVersion pkgs.cups)}://brother-hl-5140/expert
+ '';
+ description = ''
+ Location of the ppd driver file for the printer.
+ <command>lpinfo -m</command> shows a list of supported models.
+ '';
+ };
+ ppdOptions = mkOption {
+ type = types.attrsOf types.str;
+ example = {
+ PageSize = "A4";
+ Duplex = "DuplexNoTumble";
+ };
+ default = {};
+ description = ''
+ Sets PPD options for the printer.
+ <command>lpoptions [-p printername] -l</command> shows suported PPD options for the given printer.
+ '';
+ };
+ };
+ });
+ };
+ };
+ };
+
+ config = mkIf (cfg.ensurePrinters != [] && config.services.printing.enable) {
+ systemd.services.ensure-printers = let
+ cupsUnit = if config.services.printing.startWhenNeeded then "cups.socket" else "cups.service";
+ in {
+ description = "Ensure NixOS-configured CUPS printers";
+ wantedBy = [ "multi-user.target" ];
+ requires = [ cupsUnit ];
+ # in contrast to cups.socket, for cups.service, this is actually not enough,
+ # as the cups service reports its activation before clients can actually interact with it.
+ # Because of this, commands like `lpinfo -v` will report a bad file descriptor
+ # due to the missing UNIX socket without sufficient sleep time.
+ after = [ cupsUnit ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ };
+
+ # sleep 10 is required to wait until cups.service is actually initialized and has created its UNIX socket file
+ script = (optionalString (!config.services.printing.startWhenNeeded) "sleep 10\n")
+ + (concatMapStringsSep "\n" ensurePrinter cfg.ensurePrinters)
+ + optionalString (cfg.ensureDefaultPrinter != null) (ensureDefaultPrinter cfg.ensureDefaultPrinter);
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/raid/hpsa.nix b/nixpkgs/nixos/modules/hardware/raid/hpsa.nix
new file mode 100644
index 00000000000..4d7af138292
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/raid/hpsa.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ hpssacli = pkgs.stdenv.mkDerivation rec {
+ pname = "hpssacli";
+ version = "2.40-13.0";
+
+ src = pkgs.fetchurl {
+ url = "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/${pname}-${version}_amd64.deb";
+ sha256 = "11w7fwk93lmfw0yya4jpjwdmgjimqxx6412sqa166g1pz4jil4sw";
+ };
+
+ nativeBuildInputs = [ pkgs.dpkg ];
+
+ unpackPhase = "dpkg -x $src ./";
+
+ installPhase = ''
+ mkdir -p $out/bin $out/share/doc $out/share/man
+ mv opt/hp/hpssacli/bld/{hpssascripting,hprmstr,hpssacli} $out/bin/
+ mv opt/hp/hpssacli/bld/*.{license,txt} $out/share/doc/
+ mv usr/man $out/share/
+
+ for file in $out/bin/*; do
+ chmod +w $file
+ patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
+ --set-rpath ${lib.makeLibraryPath [ pkgs.stdenv.cc.cc ]} \
+ $file
+ done
+ '';
+
+ dontStrip = true;
+
+ meta = with lib; {
+ description = "HP Smart Array CLI";
+ homepage = https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/;
+ license = licenses.unfreeRedistributable;
+ platforms = [ "x86_64-linux" ];
+ maintainers = with maintainers; [ volth ];
+ };
+ };
+in {
+ ###### interface
+
+ options = {
+ hardware.raid.HPSmartArray = {
+ enable = mkEnableOption "HP Smart Array kernel modules and CLI utility";
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf config.hardware.raid.HPSmartArray.enable {
+
+ boot.initrd.kernelModules = [ "sg" ]; /* hpssacli wants it */
+ boot.initrd.availableKernelModules = [ "hpsa" ];
+
+ environment.systemPackages = [ hpssacli ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/sensor/iio.nix b/nixpkgs/nixos/modules/hardware/sensor/iio.nix
new file mode 100644
index 00000000000..a8bc1880002
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/sensor/iio.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+
+ options = {
+ hardware.sensor.iio = {
+ enable = mkOption {
+ description = "Enable this option to support IIO sensors.";
+ type = types.bool;
+ default = false;
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf config.hardware.sensor.iio.enable {
+
+ boot.initrd.availableKernelModules = [ "hid-sensor-hub" ];
+
+ environment.systemPackages = with pkgs; [ iio-sensor-proxy ];
+
+ services.dbus.packages = with pkgs; [ iio-sensor-proxy ];
+ services.udev.packages = with pkgs; [ iio-sensor-proxy ];
+ systemd.packages = with pkgs; [ iio-sensor-proxy ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/steam-hardware.nix b/nixpkgs/nixos/modules/hardware/steam-hardware.nix
new file mode 100644
index 00000000000..378aeffe71b
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/hardware/usb-wwan.nix b/nixpkgs/nixos/modules/hardware/usb-wwan.nix
new file mode 100644
index 00000000000..2d20421586a
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/usb-wwan.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+
+ options = {
+
+ hardware.usbWwan = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable this option to support USB WWAN adapters.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf config.hardware.usbWwan.enable {
+ services.udev.packages = with pkgs; [ usb-modeswitch-data ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/video/amdgpu-pro.nix b/nixpkgs/nixos/modules/hardware/video/amdgpu-pro.nix
new file mode 100644
index 00000000000..8e91e9d2baa
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/amdgpu-pro.nix
@@ -0,0 +1,68 @@
+# This module provides the proprietary AMDGPU-PRO drivers.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ drivers = config.services.xserver.videoDrivers;
+
+ enabled = elem "amdgpu-pro" drivers;
+
+ package = config.boot.kernelPackages.amdgpu-pro;
+ package32 = pkgs.pkgsi686Linux.linuxPackages.amdgpu-pro.override { libsOnly = true; kernel = null; };
+
+ opengl = config.hardware.opengl;
+
+ kernel = pkgs.linux_4_9.override {
+ extraConfig = ''
+ KALLSYMS_ALL y
+ '';
+ };
+
+in
+
+{
+
+ config = mkIf enabled {
+
+ nixpkgs.config.xorg.abiCompat = "1.19";
+
+ services.xserver.drivers = singleton
+ { name = "amdgpu"; modules = [ package ]; };
+
+ hardware.opengl.package = package;
+ hardware.opengl.package32 = package32;
+ hardware.opengl.setLdLibraryPath = true;
+
+ boot.extraModulePackages = [ package ];
+
+ boot.kernelPackages =
+ pkgs.recurseIntoAttrs (pkgs.linuxPackagesFor kernel);
+
+ boot.blacklistedKernelModules = [ "radeon" ];
+
+ hardware.firmware = [ package ];
+
+ system.activationScripts.setup-amdgpu-pro = ''
+ mkdir -p /run/lib
+ ln -sfn ${package}/lib ${package.libCompatDir}
+ ln -sfn ${package} /run/amdgpu-pro
+ '' + optionalString opengl.driSupport32Bit ''
+ ln -sfn ${package32}/lib ${package32.libCompatDir}
+ '';
+
+ system.requiredKernelConfig = with config.lib.kernelConfig; [
+ (isYes "KALLSYMS_ALL")
+ ];
+
+ environment.etc = {
+ "amd/amdrc".source = package + "/etc/amd/amdrc";
+ "amd/amdapfxx.blb".source = package + "/etc/amd/amdapfxx.blb";
+ "gbm/gbm.conf".source = package + "/etc/gbm/gbm.conf";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/video/amdgpu.nix b/nixpkgs/nixos/modules/hardware/video/amdgpu.nix
new file mode 100644
index 00000000000..42fc8fa362d
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/amdgpu.nix
@@ -0,0 +1,9 @@
+{ config, lib, ... }:
+
+with lib;
+{
+ config = mkIf (elem "amdgpu" config.services.xserver.videoDrivers) {
+ boot.blacklistedKernelModules = [ "radeon" ];
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/hardware/video/ati.nix b/nixpkgs/nixos/modules/hardware/video/ati.nix
new file mode 100644
index 00000000000..0aab7bd6b92
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/ati.nix
@@ -0,0 +1,40 @@
+# This module provides the proprietary ATI X11 / OpenGL drivers.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ drivers = config.services.xserver.videoDrivers;
+
+ enabled = elem "ati_unfree" drivers;
+
+ ati_x11 = config.boot.kernelPackages.ati_drivers_x11;
+
+in
+
+{
+
+ config = mkIf enabled {
+
+ nixpkgs.config.xorg.abiCompat = "1.17";
+
+ services.xserver.drivers = singleton
+ { name = "fglrx"; modules = [ ati_x11 ]; };
+
+ hardware.opengl.package = ati_x11;
+ hardware.opengl.package32 = pkgs.pkgsi686Linux.linuxPackages.ati_drivers_x11.override { libsOnly = true; kernel = null; };
+ hardware.opengl.setLdLibraryPath = true;
+
+ environment.systemPackages = [ ati_x11 ];
+
+ boot.extraModulePackages = [ ati_x11 ];
+
+ boot.blacklistedKernelModules = [ "radeon" ];
+
+ environment.etc.ati.source = "${ati_x11}/etc/ati";
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/video/bumblebee.nix b/nixpkgs/nixos/modules/hardware/video/bumblebee.nix
new file mode 100644
index 00000000000..2278c7b4061
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/bumblebee.nix
@@ -0,0 +1,93 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.hardware.bumblebee;
+
+ kernel = config.boot.kernelPackages;
+
+ useNvidia = cfg.driver == "nvidia";
+
+ bumblebee = pkgs.bumblebee.override {
+ inherit useNvidia;
+ useDisplayDevice = cfg.connectDisplay;
+ };
+
+ useBbswitch = cfg.pmMethod == "bbswitch" || cfg.pmMethod == "auto" && useNvidia;
+
+ primus = pkgs.primus.override {
+ inherit useNvidia;
+ };
+
+in
+
+{
+
+ options = {
+ hardware.bumblebee = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable the bumblebee daemon to manage Optimus hybrid video cards.
+ This should power off secondary GPU until its use is requested
+ by running an application with optirun.
+ '';
+ };
+
+ group = mkOption {
+ default = "wheel";
+ example = "video";
+ type = types.str;
+ description = ''Group for bumblebee socket'';
+ };
+
+ connectDisplay = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Set to true if you intend to connect your discrete card to a
+ monitor. This option will set up your Nvidia card for EDID
+ discovery and to turn on the monitor signal.
+
+ Only nvidia driver is supported so far.
+ '';
+ };
+
+ driver = mkOption {
+ default = "nvidia";
+ type = types.enum [ "nvidia" "nouveau" ];
+ description = ''
+ Set driver used by bumblebeed. Supported are nouveau and nvidia.
+ '';
+ };
+
+ pmMethod = mkOption {
+ default = "auto";
+ type = types.enum [ "auto" "bbswitch" "switcheroo" "none" ];
+ description = ''
+ Set preferred power management method for unused card.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+ boot.blacklistedKernelModules = [ "nvidia-drm" "nvidia" "nouveau" ];
+ boot.kernelModules = optional useBbswitch "bbswitch";
+ boot.extraModulePackages = optional useBbswitch kernel.bbswitch ++ optional useNvidia kernel.nvidia_x11.bin;
+
+ environment.systemPackages = [ bumblebee primus ];
+
+ systemd.services.bumblebeed = {
+ description = "Bumblebee Hybrid Graphics Switcher";
+ wantedBy = [ "multi-user.target" ];
+ before = [ "display-manager.service" ];
+ serviceConfig = {
+ ExecStart = "${bumblebee}/bin/bumblebeed --use-syslog -g ${cfg.group} --driver ${cfg.driver} --pm-method ${cfg.pmMethod}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/video/capture/mwprocapture.nix b/nixpkgs/nixos/modules/hardware/video/capture/mwprocapture.nix
new file mode 100644
index 00000000000..61bab533eda
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/capture/mwprocapture.nix
@@ -0,0 +1,61 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.hardware.mwProCapture;
+
+ kernelPackages = config.boot.kernelPackages;
+
+in
+
+{
+
+ options.hardware.mwProCapture.enable = mkEnableOption "Magewell Pro Capture family kernel module";
+
+ config = mkIf cfg.enable {
+
+ assertions = singleton {
+ assertion = versionAtLeast kernelPackages.kernel.version "3.2";
+ message = "Magewell Pro Capture family module is not supported for kernels older than 3.2";
+ };
+
+ boot.kernelModules = [ "ProCapture" ];
+
+ environment.systemPackages = [ kernelPackages.mwprocapture ];
+
+ boot.extraModulePackages = [ kernelPackages.mwprocapture ];
+
+ boot.extraModprobeConfig = ''
+ # Set the png picture to be displayed when no input signal is detected.
+ options ProCapture nosignal_file=${kernelPackages.mwprocapture}/res/NoSignal.png
+
+ # Set the png picture to be displayed when an unsupported input signal is detected.
+ options ProCapture unsupported_file=${kernelPackages.mwprocapture}/res/Unsupported.png
+
+ # Set the png picture to be displayed when an loking input signal is detected.
+ options ProCapture locking_file=${kernelPackages.mwprocapture}/res/Locking.png
+
+ # Message signaled interrupts switch
+ #options ProCapture disable_msi=0
+
+ # Set the debug level
+ #options ProCapture debug_level=0
+
+ # Force init switch eeprom
+ #options ProCapture init_switch_eeprom=0
+
+ # Min frame interval for VIDIOC_ENUM_FRAMEINTERVALS (default: 166666(100ns))
+ #options ProCapture enum_frameinterval_min=166666
+
+ # VIDIOC_ENUM_FRAMESIZES type (1: DISCRETE; 2: STEPWISE; otherwise: CONTINUOUS )
+ #options ProCapture enum_framesizes_type=0
+
+ # Parameters for internal usage
+ #options ProCapture internal_params=""
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/video/displaylink.nix b/nixpkgs/nixos/modules/hardware/video/displaylink.nix
new file mode 100644
index 00000000000..669ac849cba
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/displaylink.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ enabled = elem "displaylink" config.services.xserver.videoDrivers;
+
+ evdi = config.boot.kernelPackages.evdi;
+
+ displaylink = pkgs.displaylink.override {
+ inherit evdi;
+ };
+
+in
+
+{
+
+ config = mkIf enabled {
+
+ boot.extraModulePackages = [ evdi ];
+
+ # Those are taken from displaylink-installer.sh and from Arch Linux AUR package.
+
+ services.udev.packages = [ displaylink ];
+
+ powerManagement.powerDownCommands = ''
+ #flush any bytes in pipe
+ while read -n 1 -t 1 SUSPEND_RESULT < /tmp/PmMessagesPort_out; do : ; done;
+
+ #suspend DisplayLinkManager
+ echo "S" > /tmp/PmMessagesPort_in
+
+ #wait until suspend of DisplayLinkManager finish
+ if [ -f /tmp/PmMessagesPort_out ]; then
+ #wait until suspend of DisplayLinkManager finish
+ read -n 1 -t 10 SUSPEND_RESULT < /tmp/PmMessagesPort_out
+ fi
+ '';
+
+ powerManagement.resumeCommands = ''
+ #resume DisplayLinkManager
+ echo "R" > /tmp/PmMessagesPort_in
+ '';
+
+ systemd.services.dlm = {
+ description = "DisplayLink Manager Service";
+ after = [ "display-manager.service" ];
+ conflicts = [ "getty@tty7.service" ];
+ path = [ pkgs.kmod ];
+
+ serviceConfig = {
+ ExecStart = "${displaylink}/bin/DisplayLinkManager";
+ Restart = "always";
+ RestartSec = 5;
+ };
+
+ preStart = ''
+ mkdir -p /var/log/displaylink
+ modprobe evdi
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/video/nvidia.nix b/nixpkgs/nixos/modules/hardware/video/nvidia.nix
new file mode 100644
index 00000000000..3ab2afc9740
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/nvidia.nix
@@ -0,0 +1,212 @@
+# This module provides the proprietary NVIDIA X11 / OpenGL drivers.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ drivers = config.services.xserver.videoDrivers;
+
+ # FIXME: should introduce an option like
+ # ‘hardware.video.nvidia.package’ for overriding the default NVIDIA
+ # driver.
+ nvidiaForKernel = kernelPackages:
+ if elem "nvidia" drivers then
+ kernelPackages.nvidia_x11
+ else if elem "nvidiaBeta" drivers then
+ kernelPackages.nvidia_x11_beta
+ else if elem "nvidiaLegacy304" drivers then
+ 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 =
+ 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.allowExternalGpu = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Configure X to allow external NVIDIA GPUs when using optimus.
+ '';
+ };
+
+ hardware.nvidia.optimus_prime.nvidiaBusId = lib.mkOption {
+ type = lib.types.str;
+ 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.str;
+ 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 = 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.
+ '';
+ }
+ ];
+
+ # 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 ];
+ deviceSection = optionalString optimusCfg.enable
+ ''
+ BusID "${optimusCfg.nvidiaBusId}"
+ ${optionalString optimusCfg.allowExternalGpu "Option \"AllowExternalGpus\""}
+ '';
+ screenSection =
+ ''
+ Option "RandRRotation" "on"
+ ${optionalString optimusCfg.enable "Option \"AllowEmptyInitialConfiguration\""}
+ '';
+ };
+
+ 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
+ ''
+ 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;
+
+ environment.systemPackages = [ nvidia_x11.bin nvidia_x11.settings ]
+ ++ lib.filter (p: p != null) [ nvidia_x11.persistenced ];
+
+ systemd.tmpfiles.rules = optional config.virtualisation.docker.enableNvidia
+ "L+ /run/nvidia-docker/bin - - - - ${nvidia_x11.bin}/origBin"
+ ++ optional (nvidia_x11.persistenced != null && config.virtualisation.docker.enableNvidia)
+ "L+ /run/nvidia-docker/extras/bin/nvidia-persistenced - - - - ${nvidia_x11.persistenced}/origBin/nvidia-persistenced";
+
+ boot.extraModulePackages = [ nvidia_x11.bin ];
+
+ # nvidia-uvm is required by CUDA applications.
+ 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 =
+ ''
+ KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c $(grep nvidia-frontend /proc/devices | cut -d \ -f 1) 255'"
+ KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c $(grep nvidia-frontend /proc/devices | cut -d \ -f 1) 254'"
+ KERNEL=="card*", SUBSYSTEM=="drm", DRIVERS=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia%n c $(grep nvidia-frontend /proc/devices | cut -d \ -f 1) %n'"
+ KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm c $(grep nvidia-uvm /proc/devices | cut -d \ -f 1) 0'"
+ '';
+
+ boot.blacklistedKernelModules = [ "nouveau" "nvidiafb" ];
+
+ services.acpid.enable = true;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/video/radeon.nix b/nixpkgs/nixos/modules/hardware/video/radeon.nix
new file mode 100644
index 00000000000..c92b7a0509d
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/radeon.nix
@@ -0,0 +1,3 @@
+{
+ hardware.enableRedistributableFirmware = true;
+}
diff --git a/nixpkgs/nixos/modules/hardware/video/uvcvideo/default.nix b/nixpkgs/nixos/modules/hardware/video/uvcvideo/default.nix
new file mode 100644
index 00000000000..7e3e94fdf2b
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/uvcvideo/default.nix
@@ -0,0 +1,64 @@
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.uvcvideo;
+
+ uvcdynctrl-udev-rules = packages: pkgs.callPackage ./uvcdynctrl-udev-rules.nix {
+ drivers = packages;
+ udevDebug = false;
+ };
+
+in
+
+{
+
+ options = {
+ services.uvcvideo.dynctrl = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable <command>uvcvideo</command> dynamic controls.
+
+ Note that enabling this brings the <command>uvcdynctrl</command> tool
+ into your environement and register all dynamic controls from
+ specified <command>packages</command> to the <command>uvcvideo</command> driver.
+ '';
+ };
+
+ packages = mkOption {
+ type = types.listOf types.path;
+ example = literalExample "[ pkgs.tiscamera ]";
+ description = ''
+ List of packages containing <command>uvcvideo</command> dynamic controls
+ rules. All files found in
+ <filename><replaceable>pkg</replaceable>/share/uvcdynctrl/data</filename>
+ will be included.
+
+ Note that these will serve as input to the <command>libwebcam</command>
+ package which through its own <command>udev</command> rule will register
+ the dynamic controls from specified packages to the <command>uvcvideo</command>
+ driver.
+ '';
+ apply = map getBin;
+ };
+ };
+ };
+
+ config = mkIf cfg.dynctrl.enable {
+
+ services.udev.packages = [
+ (uvcdynctrl-udev-rules cfg.dynctrl.packages)
+ ];
+
+ environment.systemPackages = [
+ pkgs.libwebcam
+ ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix b/nixpkgs/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix
new file mode 100644
index 00000000000..a808429c999
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix
@@ -0,0 +1,45 @@
+{ buildEnv
+, libwebcam
+, makeWrapper
+, runCommand
+, drivers ? []
+, udevDebug ? false
+}:
+
+let
+ version = "0.0.0";
+
+ dataPath = buildEnv {
+ name = "uvcdynctrl-with-drivers-data-path";
+ paths = drivers ++ [ libwebcam ];
+ pathsToLink = [ "/share/uvcdynctrl/data" ];
+ ignoreCollisions = false;
+ };
+
+ dataDir = "${dataPath}/share/uvcdynctrl/data";
+ udevDebugVarValue = if udevDebug then "1" else "0";
+in
+
+runCommand "uvcdynctrl-udev-rules-${version}"
+{
+ inherit dataPath;
+ buildInputs = [
+ makeWrapper
+ libwebcam
+ ];
+ dontPatchELF = true;
+ dontStrip = true;
+ preferLocalBuild = true;
+}
+''
+ mkdir -p "$out/lib/udev"
+ makeWrapper "${libwebcam}/lib/udev/uvcdynctrl" "$out/lib/udev/uvcdynctrl" \
+ --set NIX_UVCDYNCTRL_DATA_DIR "${dataDir}" \
+ --set NIX_UVCDYNCTRL_UDEV_DEBUG "${udevDebugVarValue}"
+
+ mkdir -p "$out/lib/udev/rules.d"
+ cat "${libwebcam}/lib/udev/rules.d/80-uvcdynctrl.rules" | \
+ sed -r "s#RUN\+\=\"([^\"]+)\"#RUN\+\=\"$out/lib/udev/uvcdynctrl\"#g" > \
+ "$out/lib/udev/rules.d/80-uvcdynctrl.rules"
+''
+
diff --git a/nixpkgs/nixos/modules/hardware/video/webcam/facetimehd.nix b/nixpkgs/nixos/modules/hardware/video/webcam/facetimehd.nix
new file mode 100644
index 00000000000..d311f600c31
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/webcam/facetimehd.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.hardware.facetimehd;
+
+ kernelPackages = config.boot.kernelPackages;
+
+in
+
+{
+
+ options.hardware.facetimehd.enable = mkEnableOption "facetimehd kernel module";
+
+ config = mkIf cfg.enable {
+
+ assertions = singleton {
+ assertion = versionAtLeast kernelPackages.kernel.version "3.19";
+ message = "facetimehd is not supported for kernels older than 3.19";
+ };
+
+ boot.kernelModules = [ "facetimehd" ];
+
+ boot.blacklistedKernelModules = [ "bdc_pci" ];
+
+ boot.extraModulePackages = [ kernelPackages.facetimehd ];
+
+ hardware.firmware = [ pkgs.facetimehd-firmware ];
+
+ # unload module during suspend/hibernate as it crashes the whole system
+ powerManagement.powerDownCommands = ''
+ ${pkgs.kmod}/bin/lsmod | ${pkgs.gnugrep}/bin/grep -q "^facetimehd" && ${pkgs.kmod}/bin/rmmod -f -v facetimehd
+ '';
+
+ # and load it back on resume
+ powerManagement.resumeCommands = ''
+ ${pkgs.kmod}/bin/modprobe -v facetimehd
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/i18n/input-method/default.nix b/nixpkgs/nixos/modules/i18n/input-method/default.nix
new file mode 100644
index 00000000000..9548a249efa
--- /dev/null
+++ b/nixpkgs/nixos/modules/i18n/input-method/default.nix
@@ -0,0 +1,71 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+ cfg = config.i18n.inputMethod;
+
+ gtk2_cache = pkgs.runCommand "gtk2-immodule.cache"
+ { preferLocalBuild = true;
+ allowSubstitutes = false;
+ buildInputs = [ pkgs.gtk2 cfg.package ];
+ }
+ ''
+ mkdir -p $out/etc/gtk-2.0/
+ GTK_PATH=${cfg.package}/lib/gtk-2.0/ gtk-query-immodules-2.0 > $out/etc/gtk-2.0/immodules.cache
+ '';
+
+ gtk3_cache = pkgs.runCommand "gtk3-immodule.cache"
+ { preferLocalBuild = true;
+ allowSubstitutes = false;
+ buildInputs = [ pkgs.gtk3 cfg.package ];
+ }
+ ''
+ mkdir -p $out/etc/gtk-3.0/
+ GTK_PATH=${cfg.package}/lib/gtk-3.0/ gtk-query-immodules-3.0 > $out/etc/gtk-3.0/immodules.cache
+ '';
+
+in
+{
+ options.i18n = {
+ inputMethod = {
+ enabled = mkOption {
+ type = types.nullOr (types.enum [ "ibus" "fcitx" "nabi" "uim" ]);
+ default = null;
+ example = "fcitx";
+ description = ''
+ Select the enabled input method. Input methods is a software to input symbols that are not available on standard input devices.
+
+ Input methods are specially used to input Chinese, Japanese and Korean characters.
+
+ Currently the following input methods are available in NixOS:
+
+ <itemizedlist>
+ <listitem><para>ibus: The intelligent input bus, extra input engines can be added using <literal>i18n.inputMethod.ibus.engines</literal>.</para></listitem>
+ <listitem><para>fcitx: A customizable lightweight input method, extra input engines can be added using <literal>i18n.inputMethod.fcitx.engines</literal>.</para></listitem>
+ <listitem><para>nabi: A Korean input method based on XIM. Nabi doesn't support Qt 5.</para></listitem>
+ <listitem><para>uim: The universal input method, is a library with a XIM bridge. uim mainly support Chinese, Japanese and Korean.</para></listitem>
+ </itemizedlist>
+ '';
+ };
+
+ package = mkOption {
+ internal = true;
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The input method method package.
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.enabled != null) {
+ environment.systemPackages = [ cfg.package gtk2_cache gtk3_cache ];
+ };
+
+ meta = {
+ maintainers = with lib.maintainers; [ ericsagnes ];
+ doc = ./default.xml;
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/i18n/input-method/default.xml b/nixpkgs/nixos/modules/i18n/input-method/default.xml
new file mode 100644
index 00000000000..117482fb0d5
--- /dev/null
+++ b/nixpkgs/nixos/modules/i18n/input-method/default.xml
@@ -0,0 +1,244 @@
+<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-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>
+
+<programlisting>
+i18n.inputMethod = {
+ <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "ibus";
+ <link linkend="opt-i18n.inputMethod.ibus.engines">ibus.engines</link> = with pkgs.ibus-engines; [ anthy hangul mozc ];
+};
+</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:
+<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>
+
+<programlisting>
+i18n.inputMethod = {
+ <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "fcitx";
+ <link linkend="opt-i18n.inputMethod.fcitx.engines">fcitx.engines</link> = with pkgs.fcitx-engines; [ mozc hangul m17n ];
+};
+</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>
+
+<programlisting>
+i18n.inputMethod = {
+ <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "nabi";
+};
+</programlisting>
+ </section>
+ <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>
+ The following snippet can be used to configure uim:
+ </para>
+
+<programlisting>
+i18n.inputMethod = {
+ <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "uim";
+};
+</programlisting>
+
+ <para>
+ Note: The <xref linkend="opt-i18n.inputMethod.uim.toolbar"/> option can be
+ used to choose uim toolbar.
+ </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/i18n/input-method/fcitx.nix b/nixpkgs/nixos/modules/i18n/input-method/fcitx.nix
new file mode 100644
index 00000000000..440f13b4152
--- /dev/null
+++ b/nixpkgs/nixos/modules/i18n/input-method/fcitx.nix
@@ -0,0 +1,43 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.i18n.inputMethod.fcitx;
+ fcitxPackage = pkgs.fcitx.override { plugins = cfg.engines; };
+ fcitxEngine = types.package // {
+ name = "fcitx-engine";
+ check = x: (lib.types.package.check x) && (attrByPath ["meta" "isFcitxEngine"] false x);
+ };
+in
+{
+ options = {
+
+ i18n.inputMethod.fcitx = {
+ engines = mkOption {
+ type = with types; listOf fcitxEngine;
+ default = [];
+ example = literalExample "with pkgs.fcitx-engines; [ mozc hangul ]";
+ description =
+ let
+ enginesDrv = filterAttrs (const isDerivation) pkgs.fcitx-engines;
+ engines = concatStringsSep ", "
+ (map (name: "<literal>${name}</literal>") (attrNames enginesDrv));
+ in
+ "Enabled Fcitx engines. Available engines are: ${engines}.";
+ };
+ };
+
+ };
+
+ config = mkIf (config.i18n.inputMethod.enabled == "fcitx") {
+ i18n.inputMethod.package = fcitxPackage;
+
+ environment.variables = {
+ GTK_IM_MODULE = "fcitx";
+ QT_IM_MODULE = "fcitx";
+ XMODIFIERS = "@im=fcitx";
+ };
+ services.xserver.displayManager.sessionCommands = "${fcitxPackage}/bin/fcitx";
+ };
+}
diff --git a/nixpkgs/nixos/modules/i18n/input-method/ibus.nix b/nixpkgs/nixos/modules/i18n/input-method/ibus.nix
new file mode 100644
index 00000000000..8109ef76c40
--- /dev/null
+++ b/nixpkgs/nixos/modules/i18n/input-method/ibus.nix
@@ -0,0 +1,67 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.i18n.inputMethod.ibus;
+ ibusPackage = pkgs.ibus-with-plugins.override { plugins = cfg.engines; };
+ ibusEngine = types.package // {
+ name = "ibus-engine";
+ check = x: (lib.types.package.check x) && (attrByPath ["meta" "isIbusEngine"] false x);
+ };
+
+ impanel =
+ if cfg.panel != null
+ then "--panel=${cfg.panel}"
+ else "";
+
+ ibusAutostart = pkgs.writeTextFile {
+ name = "autostart-ibus-daemon";
+ destination = "/etc/xdg/autostart/ibus-daemon.desktop";
+ text = ''
+ [Desktop Entry]
+ Name=IBus
+ Type=Application
+ Exec=${ibusPackage}/bin/ibus-daemon --daemonize --xim ${impanel}
+ '';
+ };
+in
+{
+ options = {
+ i18n.inputMethod.ibus = {
+ engines = mkOption {
+ type = with types; listOf ibusEngine;
+ default = [];
+ example = literalExample "with pkgs.ibus-engines; [ mozc hangul ]";
+ description =
+ let
+ enginesDrv = filterAttrs (const isDerivation) pkgs.ibus-engines;
+ engines = concatStringsSep ", "
+ (map (name: "<literal>${name}</literal>") (attrNames enginesDrv));
+ in
+ "Enabled IBus engines. Available engines are: ${engines}.";
+ };
+ panel = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ example = literalExample "''${pkgs.plasma5.plasma-desktop}/lib/libexec/kimpanel-ibus-panel";
+ description = "Replace the IBus panel with another panel.";
+ };
+ };
+ };
+
+ config = mkIf (config.i18n.inputMethod.enabled == "ibus") {
+ i18n.inputMethod.package = ibusPackage;
+
+ # Without dconf enabled it is impossible to use IBus
+ environment.systemPackages = with pkgs; [
+ gnome3.dconf ibusAutostart
+ ];
+
+ environment.variables = {
+ GTK_IM_MODULE = "ibus";
+ QT_IM_MODULE = "ibus";
+ XMODIFIERS = "@im=ibus";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/i18n/input-method/nabi.nix b/nixpkgs/nixos/modules/i18n/input-method/nabi.nix
new file mode 100644
index 00000000000..87620ae4e7b
--- /dev/null
+++ b/nixpkgs/nixos/modules/i18n/input-method/nabi.nix
@@ -0,0 +1,16 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+{
+ config = mkIf (config.i18n.inputMethod.enabled == "nabi") {
+ i18n.inputMethod.package = pkgs.nabi;
+
+ environment.variables = {
+ GTK_IM_MODULE = "nabi";
+ QT_IM_MODULE = "nabi";
+ XMODIFIERS = "@im=nabi";
+ };
+
+ services.xserver.displayManager.sessionCommands = "${pkgs.nabi}/bin/nabi &";
+ };
+}
diff --git a/nixpkgs/nixos/modules/i18n/input-method/uim.nix b/nixpkgs/nixos/modules/i18n/input-method/uim.nix
new file mode 100644
index 00000000000..7ad68bf851f
--- /dev/null
+++ b/nixpkgs/nixos/modules/i18n/input-method/uim.nix
@@ -0,0 +1,37 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.i18n.inputMethod.uim;
+in
+{
+ options = {
+
+ i18n.inputMethod.uim = {
+ toolbar = mkOption {
+ type = types.enum [ "gtk" "gtk3" "gtk-systray" "gtk3-systray" "qt4" ];
+ default = "gtk";
+ example = "gtk-systray";
+ description = ''
+ selected UIM toolbar.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf (config.i18n.inputMethod.enabled == "uim") {
+ i18n.inputMethod.package = pkgs.uim;
+
+ environment.variables = {
+ GTK_IM_MODULE = "uim";
+ QT_IM_MODULE = "uim";
+ XMODIFIERS = "@im=uim";
+ };
+ services.xserver.displayManager.sessionCommands = ''
+ ${pkgs.uim}/bin/uim-xim &
+ ${pkgs.uim}/bin/uim-toolbar-${cfg.toolbar} &
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/channel.nix b/nixpkgs/nixos/modules/installer/cd-dvd/channel.nix
new file mode 100644
index 00000000000..ab5e7c0645f
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/channel.nix
@@ -0,0 +1,47 @@
+# Provide an initial copy of the NixOS channel so that the user
+# doesn't need to run "nix-channel --update" first.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ nixpkgs = lib.cleanSource pkgs.path;
+
+ # We need a copy of the Nix expressions for Nixpkgs and NixOS on the
+ # CD. These are installed into the "nixos" channel of the root
+ # user, as expected by nixos-rebuild/nixos-install. FIXME: merge
+ # with make-channel.nix.
+ channelSources = pkgs.runCommand "nixos-${config.system.nixos.version}"
+ { preferLocalBuild = true; }
+ ''
+ mkdir -p $out
+ cp -prd ${nixpkgs.outPath} $out/nixos
+ chmod -R u+w $out/nixos
+ if [ ! -e $out/nixos/nixpkgs ]; then
+ ln -s . $out/nixos/nixpkgs
+ fi
+ echo -n ${config.system.nixos.revision} > $out/nixos/.git-revision
+ echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
+ echo ${config.system.nixos.versionSuffix} | sed -e s/pre// > $out/nixos/svn-revision
+ '';
+
+in
+
+{
+ # Provide the NixOS/Nixpkgs sources in /etc/nixos. This is required
+ # for nixos-install.
+ boot.postBootCommands = mkAfter
+ ''
+ if ! [ -e /var/lib/nixos/did-channel-init ]; then
+ echo "unpacking the NixOS/Nixpkgs sources..."
+ mkdir -p /nix/var/nix/profiles/per-user/root
+ ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/per-user/root/channels \
+ -i ${channelSources} --quiet --option build-use-substitutes false
+ mkdir -m 0700 -p /root/.nix-defexpr
+ ln -s /nix/var/nix/profiles/per-user/root/channels /root/.nix-defexpr/channels
+ mkdir -m 0755 -p /var/lib/nixos
+ touch /var/lib/nixos/did-channel-init
+ fi
+ '';
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-base.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-base.nix
new file mode 100644
index 00000000000..24070a78694
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-base.nix
@@ -0,0 +1,33 @@
+# This module contains the basic configuration for building a NixOS
+# installation CD.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports =
+ [ ./iso-image.nix
+
+ # Profiles of this basic installation CD.
+ ../../profiles/all-hardware.nix
+ ../../profiles/base.nix
+ ../../profiles/installation-device.nix
+ ];
+
+ # ISO naming.
+ isoImage.isoName = "${config.isoImage.isoBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.iso";
+
+ isoImage.volumeID = substring 0 11 "NIXOS_ISO";
+
+ # EFI booting
+ isoImage.makeEfiBootable = true;
+
+ # USB booting
+ isoImage.makeUsbBootable = true;
+
+ # Add Memtest86+ to the CD.
+ boot.loader.grub.memtest86.enable = true;
+
+ system.stateVersion = mkDefault "18.03";
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
new file mode 100644
index 00000000000..1578e1547bc
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
@@ -0,0 +1,66 @@
+# This module contains the basic configuration for building a graphical NixOS
+# installation CD.
+
+{ lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [ ./installation-cd-base.nix ];
+
+ # Whitelist wheel users to do anything
+ # This is useful for things like pkexec
+ #
+ # WARNING: this is dangerous for systems
+ # outside the installation-cd and shouldn't
+ # be used anywhere else.
+ security.polkit.extraConfig = ''
+ polkit.addRule(function(action, subject) {
+ if (subject.isInGroup("wheel")) {
+ return polkit.Result.YES;
+ }
+ });
+ '';
+
+ services.xserver = {
+ enable = true;
+
+ # Don't start the X server by default.
+ autorun = mkForce false;
+
+ # Automatically login as nixos.
+ displayManager.slim = {
+ enable = true;
+ defaultUser = "nixos";
+ 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;
+
+ # Enable sound in graphical iso's.
+ hardware.pulseaudio.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/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
new file mode 100644
index 00000000000..0b813bbf37b
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
@@ -0,0 +1,21 @@
+# This module defines a NixOS installation CD that contains X11 and
+# GNOME 3.
+
+{ lib, ... }:
+
+with lib;
+
+{
+ imports = [ ./installation-cd-graphical-base.nix ];
+
+ services.xserver.desktopManager.gnome3.enable = true;
+
+ services.xserver.displayManager.slim.enable = mkForce false;
+
+ # Auto-login as root.
+ services.xserver.displayManager.gdm.autoLogin = {
+ enable = true;
+ user = "root";
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde-new-kernel.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde-new-kernel.nix
new file mode 100644
index 00000000000..3336d512cfd
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde-new-kernel.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+{
+ imports = [ ./installation-cd-graphical-kde.nix ];
+
+ boot.kernelPackages = pkgs.linuxPackages_latest;
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix
new file mode 100644
index 00000000000..559899b0a3b
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix
@@ -0,0 +1,46 @@
+# This module defines a NixOS installation CD that contains X11 and
+# Plasma 5.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [ ./installation-cd-graphical-base.nix ];
+
+ services.xserver = {
+ desktopManager.plasma5 = {
+ enable = true;
+ enableQt4Support = false;
+ };
+ };
+
+ environment.systemPackages = with pkgs; [
+ # Graphical text editor
+ kate
+ ];
+
+ system.activationScripts.installerDesktop = let
+
+ manualDesktopFile = pkgs.writeScript "nixos-manual.desktop" ''
+ [Desktop Entry]
+ Version=1.0
+ Type=Application
+ Name=NixOS Manual
+ Exec=firefox ${config.system.build.manual.manual}/share/doc/nixos/index.html
+ Icon=text-html
+ '';
+
+ homeDir = "/home/nixos/";
+ desktopDir = homeDir + "Desktop/";
+
+ in ''
+ mkdir -p ${desktopDir}
+ chown nixos ${homeDir} ${desktopDir}
+
+ ln -sfT ${manualDesktopFile} ${desktopDir + "nixos-manual.desktop"}
+ ln -sfT ${pkgs.gparted}/share/applications/gparted.desktop ${desktopDir + "gparted.desktop"}
+ ln -sfT ${pkgs.konsole}/share/applications/org.kde.konsole.desktop ${desktopDir + "org.kde.konsole.desktop"}
+ '';
+
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix
new file mode 100644
index 00000000000..3911a2b01b1
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+{
+ imports = [ ./installation-cd-minimal.nix ];
+
+ boot.kernelPackages = pkgs.linuxPackages_latest;
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
new file mode 100644
index 00000000000..bcdbffdc20b
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
@@ -0,0 +1,12 @@
+# This module defines a small NixOS installation CD. It does not
+# contain any graphical stuff.
+
+{ ... }:
+
+{
+ imports =
+ [ ./installation-cd-base.nix
+ ];
+
+ fonts.fontconfig.enable = false;
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix b/nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix
new file mode 100644
index 00000000000..d5c92cfc1d9
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -0,0 +1,665 @@
+# This module creates a bootable ISO image containing the given NixOS
+# configuration. The derivation for the ISO image will be placed in
+# config.system.build.isoImage.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ /**
+ * Given a list of `options`, concats the result of mapping each options
+ * to a menuentry for use in grub.
+ *
+ * * defaults: {name, image, params, initrd}
+ * * options: [ option... ]
+ * * option: {name, params, class}
+ */
+ menuBuilderGrub2 =
+ defaults: options: lib.concatStrings
+ (
+ map
+ (option: ''
+ menuentry '${defaults.name} ${
+ # Name appended to menuentry defaults to params if no specific name given.
+ option.name or (if option ? params then "(${option.params})" else "")
+ }' ${if option ? class then " --class ${option.class}" else ""} {
+ linux ${defaults.image} ${defaults.params} ${
+ option.params or ""
+ }
+ initrd ${defaults.initrd}
+ }
+ '')
+ options
+ )
+ ;
+
+ /**
+ * Given a `config`, builds the default options.
+ */
+ buildMenuGrub2 = config:
+ buildMenuAdditionalParamsGrub2 config ""
+ ;
+
+ /**
+ * Given a `config` and params to add to `params`, build a set of default options.
+ * Use this one when creating a variant (e.g. hidpi)
+ */
+ buildMenuAdditionalParamsGrub2 = config: additional:
+ 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/${config.system.boot.loader.kernelFile}";
+ initrd = "/boot/initrd";
+ };
+ in
+ menuBuilderGrub2
+ finalCfg
+ [
+ { class = "installer"; }
+ { class = "nomodeset"; params = "nomodeset"; }
+ { class = "copytoram"; params = "copytoram"; }
+ { class = "debug"; params = "debug"; }
+ ]
+ ;
+
+ # Timeout in syslinux is in units of 1/10 of a second.
+ # 0 is used to disable timeouts.
+ syslinuxTimeout = if config.boot.loader.timeout == null then
+ 0
+ else
+ max (config.boot.loader.timeout * 10) 1;
+
+
+ max = x: y: if x > y then x else y;
+
+ # The configuration file for syslinux.
+
+ # Notes on syslinux configuration and UNetbootin compatiblity:
+ # * Do not use '/syslinux/syslinux.cfg' as the path for this
+ # configuration. UNetbootin will not parse the file and use it as-is.
+ # This results in a broken configuration if the partition label does
+ # not match the specified config.isoImage.volumeID. For this reason
+ # we're using '/isolinux/isolinux.cfg'.
+ # * Use APPEND instead of adding command-line arguments directly after
+ # the LINUX entries.
+ # * COM32 entries (chainload, reboot, poweroff) are not recognized. They
+ # result in incorrect boot entries.
+
+ baseIsolinuxCfg = ''
+ SERIAL 0 115200
+ TIMEOUT ${builtins.toString syslinuxTimeout}
+ UI vesamenu.c32
+ MENU TITLE NixOS
+ MENU BACKGROUND /isolinux/background.png
+ MENU RESOLUTION 800 600
+ MENU CLEAR
+ MENU ROWS 6
+ MENU CMDLINEROW -4
+ MENU TIMEOUTROW -3
+ MENU TABMSGROW -2
+ MENU HELPMSGROW -1
+ MENU HELPMSGENDROW -1
+ MENU MARGIN 0
+
+ # FG:AARRGGBB BG:AARRGGBB shadow
+ MENU COLOR BORDER 30;44 #00000000 #00000000 none
+ MENU COLOR SCREEN 37;40 #FF000000 #00E2E8FF none
+ MENU COLOR TABMSG 31;40 #80000000 #00000000 none
+ MENU COLOR TIMEOUT 1;37;40 #FF000000 #00000000 none
+ MENU COLOR TIMEOUT_MSG 37;40 #FF000000 #00000000 none
+ MENU COLOR CMDMARK 1;36;40 #FF000000 #00000000 none
+ MENU COLOR CMDLINE 37;40 #FF000000 #00000000 none
+ MENU COLOR TITLE 1;36;44 #00000000 #00000000 none
+ MENU COLOR UNSEL 37;44 #FF000000 #00000000 none
+ MENU COLOR SEL 7;37;40 #FFFFFFFF #FF5277C3 std
+
+ DEFAULT boot
+
+ LABEL boot
+ MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}
+ LINUX /boot/${config.system.boot.loader.kernelFile}
+ APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}
+ INITRD /boot/${config.system.boot.loader.initrdFile}
+
+ # A variant to boot with 'nomodeset'
+ LABEL boot-nomodeset
+ MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (nomodeset)
+ LINUX /boot/${config.system.boot.loader.kernelFile}
+ APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} nomodeset
+ INITRD /boot/${config.system.boot.loader.initrdFile}
+
+ # A variant to boot with 'copytoram'
+ LABEL boot-copytoram
+ MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (copytoram)
+ LINUX /boot/${config.system.boot.loader.kernelFile}
+ APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} copytoram
+ INITRD /boot/${config.system.boot.loader.initrdFile}
+
+ # A variant to boot with verbose logging to the console
+ LABEL boot-debug
+ MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (debug)
+ LINUX /boot/${config.system.boot.loader.kernelFile}
+ APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} loglevel=7
+ INITRD /boot/${config.system.boot.loader.initrdFile}
+ '';
+
+ isolinuxMemtest86Entry = ''
+ LABEL memtest
+ MENU LABEL Memtest86+
+ LINUX /boot/memtest.bin
+ APPEND ${toString config.boot.loader.grub.memtest86.params}
+ '';
+
+ isolinuxCfg = concatStringsSep "\n"
+ ([ baseIsolinuxCfg ] ++ optional config.boot.loader.grub.memtest86.enable isolinuxMemtest86Entry);
+
+ # Setup instructions for rEFInd.
+ refind =
+ if targetArch == "x64" then
+ ''
+ # Adds rEFInd to the ISO.
+ cp -v ${pkgs.refind}/share/refind/refind_x64.efi $out/EFI/boot/
+ ''
+ else
+ "# No refind for ${targetArch}"
+ ;
+
+ grubPkgs = if config.boot.loader.grub.forcei686 then pkgs.pkgsi686Linux else pkgs;
+
+ grubMenuCfg = ''
+ #
+ # Menu configuration
+ #
+
+ insmod gfxterm
+ insmod png
+ set gfxpayload=keep
+
+ # Fonts can be loaded?
+ # (This font is assumed to always be provided as a fallback by NixOS)
+ if loadfont (hd0)/EFI/boot/unicode.pf2; then
+ # Use graphical term, it can be either with background image or a theme.
+ # input is "console", while output is "gfxterm".
+ # This enables "serial" input and output only when possible.
+ # Otherwise the failure mode is to not even enable gfxterm.
+ if test "\$with_serial" == "yes"; then
+ terminal_output gfxterm serial
+ terminal_input console serial
+ else
+ terminal_output gfxterm
+ terminal_input console
+ fi
+ else
+ # Sets colors for the non-graphical term.
+ set menu_color_normal=cyan/blue
+ set menu_color_highlight=white/blue
+ fi
+
+ ${ # When there is a theme configured, use it, otherwise use the background image.
+ if config.isoImage.grubTheme != null then ''
+ # Sets theme.
+ set theme=(hd0)/EFI/boot/grub-theme/theme.txt
+ # Load theme fonts
+ $(find ${config.isoImage.grubTheme} -iname '*.pf2' -printf "loadfont (hd0)/EFI/boot/grub-theme/%P\n")
+ '' else ''
+ if background_image (hd0)/EFI/boot/efi-background.png; then
+ # Black background means transparent background when there
+ # is a background image set... This seems undocumented :(
+ set color_normal=black/black
+ set color_highlight=white/blue
+ else
+ # Falls back again to proper colors.
+ set menu_color_normal=cyan/blue
+ set menu_color_highlight=white/blue
+ fi
+ ''}
+ '';
+
+ # The EFI boot image.
+ # Notes about grub:
+ # * Yes, the grubMenuCfg has to be repeated in all submenus. Otherwise you
+ # will get white-on-black console-like text on sub-menus. *sigh*
+ 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 \
+ 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 ${grubPkgs.grub2_efi}/lib/grub/${grubPkgs.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.
+ ${grubPkgs.grub2_efi}/bin/grub-mkimage -o $out/EFI/boot/boot${targetArch}.efi -p /EFI/boot -O ${grubPkgs.grub2_efi.grubTarget} \
+ $MODULES
+ cp ${grubPkgs.grub2_efi}/share/grub/unicode.pf2 $out/EFI/boot/
+
+ cat <<EOF > $out/EFI/boot/grub.cfg
+
+ # If you want to use serial for "terminal_*" commands, you need to set one up:
+ # Example manual configuration:
+ # → serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
+ # This uses the defaults, and makes the serial terminal available.
+ set with_serial=no
+ if serial; then set with_serial=yes ;fi
+ export with_serial
+ clear
+ set timeout=10
+ ${grubMenuCfg}
+
+ #
+ # Menu entries
+ #
+
+ ${buildMenuGrub2 config}
+ submenu "HiDPI, Quirks and Accessibility" --class hidpi --class submenu {
+ ${grubMenuCfg}
+ submenu "Suggests resolution @720p" --class hidpi-720p {
+ ${grubMenuCfg}
+ ${buildMenuAdditionalParamsGrub2 config "video=1280x720@60"}
+ }
+ submenu "Suggests resolution @1080p" --class hidpi-1080p {
+ ${grubMenuCfg}
+ ${buildMenuAdditionalParamsGrub2 config "video=1920x1080@60"}
+ }
+
+ # Some laptop and convertibles have the panel installed in an
+ # inconvenient way, rotated away from the keyboard.
+ # Those entries makes it easier to use the installer.
+ submenu "" {return}
+ submenu "Rotate framebuffer Clockwise" --class rotate-90cw {
+ ${grubMenuCfg}
+ ${buildMenuAdditionalParamsGrub2 config "fbcon=rotate:1"}
+ }
+ submenu "Rotate framebuffer Upside-Down" --class rotate-180 {
+ ${grubMenuCfg}
+ ${buildMenuAdditionalParamsGrub2 config "fbcon=rotate:2"}
+ }
+ submenu "Rotate framebuffer Counter-Clockwise" --class rotate-90ccw {
+ ${grubMenuCfg}
+ ${buildMenuAdditionalParamsGrub2 config "fbcon=rotate:3"}
+ }
+
+ # As a proof of concept, mainly. (Not sure it has accessibility merits.)
+ submenu "" {return}
+ submenu "Use black on white" --class accessibility-blakconwhite {
+ ${grubMenuCfg}
+ ${buildMenuAdditionalParamsGrub2 config "vt.default_red=0xFF,0xBC,0x4F,0xB4,0x56,0xBC,0x4F,0x00,0xA1,0xCF,0x84,0xCA,0x8D,0xB4,0x84,0x68 vt.default_grn=0xFF,0x55,0xBA,0xBA,0x4D,0x4D,0xB3,0x00,0xA0,0x8F,0xB3,0xCA,0x88,0x93,0xA4,0x68 vt.default_blu=0xFF,0x58,0x5F,0x58,0xC5,0xBD,0xC5,0x00,0xA8,0xBB,0xAB,0x97,0xBD,0xC7,0xC5,0x68"}
+ }
+
+ # Serial access is a must!
+ submenu "" {return}
+ submenu "Serial console=ttyS0,115200n8" --class serial {
+ ${grubMenuCfg}
+ ${buildMenuAdditionalParamsGrub2 config "console=ttyS0,115200n8"}
+ }
+ }
+
+ menuentry 'rEFInd' --class refind {
+ # UUID is hard-coded in the derivation.
+ search --set=root --no-floppy --fs-uuid 1234-5678
+ chainloader (\$root)/EFI/boot/refind_x64.efi
+ }
+ menuentry 'Firmware Setup' --class settings {
+ fwsetup
+ clear
+ echo ""
+ echo "If you see this message, your EFI system doesn't support this feature."
+ echo ""
+ }
+ menuentry 'Shutdown' --class shutdown {
+ halt
+ }
+ EOF
+
+ ${refind}
+ '';
+
+ efiImg = pkgs.runCommand "efi-image_eltorito" { buildInputs = [ pkgs.mtools pkgs.libfaketime ]; }
+ # Be careful about determinism: du --apparent-size,
+ # dates (cp -p, touch, mcopy -m, faketime for label), IDs (mkfs.vfat -i)
+ ''
+ mkdir ./contents && cd ./contents
+ 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 ./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
+ image_size=$(( ($usage_size * 110) / 100 ))
+ # Make the image fit blocks of 1M
+ block_size=$((1024*1024))
+ image_size=$(( ($image_size / $block_size + 1) * $block_size ))
+ echo "Usage size: $usage_size"
+ 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 -psvm -i "$out" ./EFI ./boot ::
+ # Verify the FAT partition.
+ ${pkgs.dosfstools}/sbin/fsck.vfat -vn "$out"
+ ''; # */
+
+ # Name used by UEFI for architectures.
+ targetArch =
+ if pkgs.stdenv.isi686 || config.boot.loader.grub.forcei686 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
+
+{
+ options = {
+
+ isoImage.isoName = mkOption {
+ default = "${config.isoImage.isoBaseName}.iso";
+ description = ''
+ Name of the generated ISO image file.
+ '';
+ };
+
+ isoImage.isoBaseName = mkOption {
+ default = "nixos";
+ description = ''
+ Prefix of the name of the generated ISO image file.
+ '';
+ };
+
+ isoImage.compressImage = mkOption {
+ default = false;
+ description = ''
+ Whether the ISO image should be compressed using
+ <command>bzip2</command>.
+ '';
+ };
+
+ isoImage.volumeID = mkOption {
+ default = "NIXOS_BOOT_CD";
+ description = ''
+ Specifies the label or volume ID of the generated ISO image.
+ Note that the label is used by stage 1 of the boot process to
+ mount the CD, so it should be reasonably distinctive.
+ '';
+ };
+
+ isoImage.contents = mkOption {
+ example = literalExample ''
+ [ { source = pkgs.memtest86 + "/memtest.bin";
+ target = "boot/memtest.bin";
+ }
+ ]
+ '';
+ description = ''
+ This option lists files to be copied to fixed locations in the
+ generated ISO image.
+ '';
+ };
+
+ isoImage.storeContents = mkOption {
+ example = literalExample "[ pkgs.stdenv ]";
+ description = ''
+ This option lists additional derivations to be included in the
+ Nix store in the generated ISO image.
+ '';
+ };
+
+ isoImage.includeSystemBuildDependencies = mkOption {
+ default = false;
+ description = ''
+ Set this option to include all the needed sources etc in the
+ image. It significantly increases image size. Use that when
+ you want to be able to keep all the sources needed to build your
+ system or when you are going to install the system on a computer
+ with slow or non-existent network connection.
+ '';
+ };
+
+ isoImage.makeEfiBootable = mkOption {
+ default = false;
+ description = ''
+ Whether the ISO image should be an efi-bootable volume.
+ '';
+ };
+
+ isoImage.makeUsbBootable = mkOption {
+ default = false;
+ description = ''
+ Whether the ISO image should be bootable from CD as well as USB.
+ '';
+ };
+
+ isoImage.efiSplashImage = mkOption {
+ default = pkgs.fetchurl {
+ url = https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/efi-background.png;
+ sha256 = "18lfwmp8yq923322nlb9gxrh5qikj1wsk6g5qvdh31c4h5b1538x";
+ };
+ description = ''
+ The splash image to use in the EFI bootloader.
+ '';
+ };
+
+ isoImage.splashImage = mkOption {
+ default = pkgs.fetchurl {
+ url = https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/isolinux/bios-boot.png;
+ sha256 = "1wp822zrhbg4fgfbwkr7cbkr4labx477209agzc0hr6k62fr6rxd";
+ };
+ description = ''
+ The splash image to use in the legacy-boot bootloader.
+ '';
+ };
+
+ isoImage.grubTheme = mkOption {
+ default = pkgs.nixos-grub2-theme;
+ type = types.nullOr (types.either types.path types.package);
+ description = ''
+ The grub2 theme used for UEFI boot.
+ '';
+ };
+
+ isoImage.appendToMenuLabel = mkOption {
+ default = " Installer";
+ example = " Live System";
+ description = ''
+ The string to append after the menu label for the NixOS system.
+ This will be directly appended (without whitespace) to the NixOS version
+ string, like for example if it is set to <literal>XXX</literal>:
+
+ <para><literal>NixOS 99.99-pre666XXX</literal></para>
+ '';
+ };
+
+ };
+
+ config = {
+
+ boot.loader.grub.version = 2;
+
+ # Don't build the GRUB menu builder script, since we don't need it
+ # here and it causes a cyclic dependency.
+ boot.loader.grub.enable = false;
+
+ environment.systemPackages = [ grubPkgs.grub2 grubPkgs.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
+ # root filesystem on the kernel command line, rather than in
+ # `fileSystems' below. This allows CD-to-USB converters such as
+ # UNetbootin to rewrite the kernel command line to pass the label or
+ # UUID of the USB stick. It would be nicer to write
+ # `root=/dev/disk/by-label/...' here, but UNetbootin doesn't
+ # recognise that.
+ boot.kernelParams =
+ [ "root=LABEL=${config.isoImage.volumeID}"
+ "boot.shell_on_fail"
+ ];
+
+ fileSystems."/" =
+ { fsType = "tmpfs";
+ options = [ "mode=0755" ];
+ };
+
+ # Note that /dev/root is a symlink to the actual root device
+ # specified on the kernel command line, created in the stage 1
+ # init script.
+ fileSystems."/iso" =
+ { device = "/dev/root";
+ neededForBoot = true;
+ noCheck = true;
+ };
+
+ # In stage 1, mount a tmpfs on top of /nix/store (the squashfs
+ # image) to make this a live CD.
+ fileSystems."/nix/.ro-store" =
+ { fsType = "squashfs";
+ device = "/iso/nix-store.squashfs";
+ options = [ "loop" ];
+ neededForBoot = true;
+ };
+
+ fileSystems."/nix/.rw-store" =
+ { fsType = "tmpfs";
+ options = [ "mode=0755" ];
+ neededForBoot = true;
+ };
+
+ fileSystems."/nix/store" =
+ { fsType = "unionfs-fuse";
+ device = "unionfs";
+ options = [ "allow_other" "cow" "nonempty" "chroot=/mnt-root" "max_files=32768" "hide_meta_files" "dirs=/nix/.rw-store=rw:/nix/.ro-store=ro" ];
+ };
+
+ boot.initrd.availableKernelModules = [ "squashfs" "iso9660" "uas" ];
+
+ boot.blacklistedKernelModules = [ "nouveau" ];
+
+ boot.initrd.kernelModules = [ "loop" ];
+
+ # Closures to be copied to the Nix store on the CD, namely the init
+ # script and the top-level system configuration directory.
+ isoImage.storeContents =
+ [ config.system.build.toplevel ] ++
+ optional config.isoImage.includeSystemBuildDependencies
+ config.system.build.toplevel.drvPath;
+
+ # Create the squashfs image that contains the Nix store.
+ system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
+ storeContents = config.isoImage.storeContents;
+ };
+
+ # Individual files to be included on the CD, outside of the Nix
+ # store on the CD.
+ isoImage.contents =
+ [
+ { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
+ target = "/boot/" + config.system.boot.loader.kernelFile;
+ }
+ { source = config.system.build.initialRamdisk + "/" + config.system.boot.loader.initrdFile;
+ target = "/boot/" + config.system.boot.loader.initrdFile;
+ }
+ { source = config.system.build.squashfsStore;
+ target = "/nix-store.squashfs";
+ }
+ { source = config.isoImage.efiSplashImage;
+ target = "/EFI/boot/efi-background.png";
+ }
+ { source = config.isoImage.splashImage;
+ target = "/isolinux/background.png";
+ }
+ { 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";
+ }
+ { source = "${efiDir}/EFI";
+ target = "/EFI";
+ }
+ ] ++ optionals (config.boot.loader.grub.memtest86.enable && canx86BiosBoot) [
+ { source = "${pkgs.memtest86plus}/memtest.bin";
+ target = "/boot/memtest.bin";
+ }
+ ] ++ optionals (config.isoImage.grubTheme != null) [
+ { source = config.isoImage.grubTheme;
+ target = "/EFI/boot/grub-theme";
+ }
+ ];
+
+ boot.loader.timeout = 10;
+
+ # Create the ISO image.
+ system.build.isoImage = pkgs.callPackage ../../../lib/make-iso9660-image.nix ({
+ inherit (config.isoImage) isoName compressImage volumeID contents;
+ bootable = canx86BiosBoot;
+ bootImage = "/isolinux/isolinux.bin";
+ 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 {
+ efiBootable = true;
+ efiBootImage = "boot/efi.img";
+ });
+
+ boot.postBootCommands =
+ ''
+ # After booting, register the contents of the Nix store on the
+ # CD in the Nix database in the tmpfs.
+ ${config.nix.package.out}/bin/nix-store --load-db < /nix/store/nix-path-registration
+
+ # nixos-rebuild also requires a "system" profile and an
+ # /etc/NIXOS tag.
+ touch /etc/NIXOS
+ ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
+ '';
+
+ # Add vfat support to the initrd to enable people to copy the
+ # contents of the CD to a bootable USB stick.
+ boot.initrd.supportedFilesystems = [ "vfat" ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix b/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix
new file mode 100644
index 00000000000..2882fbcc730
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix b/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
new file mode 100644
index 00000000000..2d34406a032
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
@@ -0,0 +1,66 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-aarch64.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+let
+ extlinux-conf-builder =
+ import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
+ pkgs = pkgs.buildPackages;
+ };
+in
+{
+ imports = [
+ ../../profiles/base.nix
+ ../../profiles/installation-device.nix
+ ./sd-image.nix
+ ];
+
+ boot.loader.grub.enable = false;
+ boot.loader.generic-extlinux-compatible.enable = true;
+
+ boot.consoleLogLevel = lib.mkDefault 7;
+
+ # The serial ports listed here are:
+ # - ttyS0: for Tegra (Jetson TX1)
+ # - ttyAMA0: for QEMU's -machine virt
+ # Also increase the amount of CMA to ensure the virtual console on the RPi3 works.
+ boot.kernelParams = ["cma=32M" "console=ttyS0,115200n8" "console=ttyAMA0,115200n8" "console=tty0"];
+
+ boot.initrd.availableKernelModules = [
+ # Allows early (earlier) modesetting for the Raspberry Pi
+ "vc4" "bcm2835_dma" "i2c_bcm2835"
+ # Allows early (earlier) modesetting for Allwinner SoCs
+ "sun4i_drm" "sun8i_drm_hdmi" "sun8i_mixer"
+ ];
+
+ sdImage = {
+ populateFirmwareCommands = let
+ configTxt = pkgs.writeText "config.txt" ''
+ kernel=u-boot-rpi3.bin
+
+ # Boot in 64-bit mode.
+ arm_control=0x200
+
+ # U-Boot used to need this to work, regardless of whether UART is actually used or not.
+ # TODO: check when/if this can be removed.
+ enable_uart=1
+
+ # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
+ # when attempting to show low-voltage or overtemperature warnings.
+ avoid_warnings=1
+ '';
+ in ''
+ (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
+ cp ${pkgs.ubootRaspberryPi3_64bit}/u-boot.bin firmware/u-boot-rpi3.bin
+ cp ${configTxt} firmware/config.txt
+ '';
+ populateRootCommands = ''
+ mkdir -p ./files/boot
+ ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./files/boot
+ '';
+ };
+
+ # the installation media is also the installation target,
+ # so we don't want to provide the installation configuration.nix.
+ installer.cloneConfig = false;
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix b/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
new file mode 100644
index 00000000000..651d1a36dc1
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
@@ -0,0 +1,63 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+let
+ extlinux-conf-builder =
+ import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
+ pkgs = pkgs.buildPackages;
+ };
+in
+{
+ imports = [
+ ../../profiles/base.nix
+ ../../profiles/installation-device.nix
+ ./sd-image.nix
+ ];
+
+ 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 TK1)
+ # - ttymxc0: for i.MX6 (Wandboard)
+ # - ttyAMA0: for Allwinner (pcDuino3 Nano) and QEMU's -machine virt
+ # - ttyO0: for OMAP (BeagleBone Black)
+ # - ttySAC2: for Exynos (ODROID-XU3)
+ boot.kernelParams = ["console=ttyS0,115200n8" "console=ttymxc0,115200n8" "console=ttyAMA0,115200n8" "console=ttyO0,115200n8" "console=ttySAC2,115200n8" "console=tty0"];
+
+ sdImage = {
+ populateFirmwareCommands = let
+ configTxt = pkgs.writeText "config.txt" ''
+ # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
+ # when attempting to show low-voltage or overtemperature warnings.
+ avoid_warnings=1
+
+ [pi2]
+ kernel=u-boot-rpi2.bin
+
+ [pi3]
+ kernel=u-boot-rpi3.bin
+
+ # U-Boot used to need this to work, regardless of whether UART is actually used or not.
+ # TODO: check when/if this can be removed.
+ enable_uart=1
+ '';
+ in ''
+ (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
+ cp ${pkgs.ubootRaspberryPi2}/u-boot.bin firmware/u-boot-rpi2.bin
+ cp ${pkgs.ubootRaspberryPi3_32bit}/u-boot.bin firmware/u-boot-rpi3.bin
+ cp ${configTxt} firmware/config.txt
+ '';
+ populateRootCommands = ''
+ mkdir -p ./files/boot
+ ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./files/boot
+ '';
+ };
+
+ # the installation media is also the installation target,
+ # so we don't want to provide the installation configuration.nix.
+ installer.cloneConfig = false;
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix b/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
new file mode 100644
index 00000000000..2a131d9ce98
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
@@ -0,0 +1,52 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+let
+ extlinux-conf-builder =
+ import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
+ pkgs = pkgs.buildPackages;
+ };
+in
+{
+ imports = [
+ ../../profiles/base.nix
+ ../../profiles/installation-device.nix
+ ./sd-image.nix
+ ];
+
+ boot.loader.grub.enable = false;
+ boot.loader.generic-extlinux-compatible.enable = true;
+
+ boot.consoleLogLevel = lib.mkDefault 7;
+ boot.kernelPackages = pkgs.linuxPackages_rpi;
+
+ sdImage = {
+ populateFirmwareCommands = let
+ configTxt = pkgs.writeText "config.txt" ''
+ # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
+ # when attempting to show low-voltage or overtemperature warnings.
+ avoid_warnings=1
+
+ [pi0]
+ kernel=u-boot-rpi0.bin
+
+ [pi1]
+ kernel=u-boot-rpi1.bin
+ '';
+ in ''
+ (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
+ cp ${pkgs.ubootRaspberryPiZero}/u-boot.bin firmware/u-boot-rpi0.bin
+ cp ${pkgs.ubootRaspberryPi}/u-boot.bin firmware/u-boot-rpi1.bin
+ cp ${configTxt} firmware/config.txt
+ '';
+ populateRootCommands = ''
+ mkdir -p ./files/boot
+ ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./files/boot
+ '';
+ };
+
+ # the installation media is also the installation target,
+ # so we don't want to provide the installation configuration.nix.
+ installer.cloneConfig = false;
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/sd-image.nix b/nixpkgs/nixos/modules/installer/cd-dvd/sd-image.nix
new file mode 100644
index 00000000000..a2a8e8ef752
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/sd-image.nix
@@ -0,0 +1,200 @@
+# This module creates a bootable SD card image containing the given NixOS
+# configuration. The generated image is MBR partitioned, with a FAT
+# /boot/firmware partition, and ext4 root partition. The generated image
+# is sized to fit its contents, and a boot script automatically resizes
+# the root partition to fit the device on the first boot.
+#
+# The firmware partition is built with expectation to hold the Raspberry
+# Pi firmware and bootloader, and be removed and replaced with a firmware
+# build for the target SoC for other board families.
+#
+# The derivation for the SD image will be placed in
+# config.system.build.sdImage
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix ({
+ inherit (config.sdImage) storePaths;
+ populateImageCommands = config.sdImage.populateRootCommands;
+ volumeLabel = "NIXOS_SD";
+ } // optionalAttrs (config.sdImage.rootPartitionUUID != null) {
+ uuid = config.sdImage.rootPartitionUUID;
+ });
+in
+{
+ imports = [
+ (mkRemovedOptionModule [ "sdImage" "bootPartitionID" ] "The FAT partition for SD image now only holds the Raspberry Pi firmware files. Use firmwarePartitionID to configure that partition's ID.")
+ (mkRemovedOptionModule [ "sdImage" "bootSize" ] "The boot files for SD image have been moved to the main ext4 partition. The FAT partition now only holds the Raspberry Pi firmware files. Changing its size may not be required.")
+ ];
+
+ options.sdImage = {
+ imageName = mkOption {
+ default = "${config.sdImage.imageBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.img";
+ description = ''
+ Name of the generated image file.
+ '';
+ };
+
+ imageBaseName = mkOption {
+ default = "nixos-sd-image";
+ description = ''
+ Prefix of the name of the generated image file.
+ '';
+ };
+
+ storePaths = mkOption {
+ type = with types; listOf package;
+ example = literalExample "[ pkgs.stdenv ]";
+ description = ''
+ Derivations to be included in the Nix store in the generated SD image.
+ '';
+ };
+
+ firmwarePartitionID = mkOption {
+ type = types.str;
+ default = "0x2178694e";
+ description = ''
+ Volume ID for the /boot/firmware partition on the SD card. This value
+ must be a 32-bit hexadecimal number.
+ '';
+ };
+
+ rootPartitionUUID = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "14e19a7b-0ae0-484d-9d54-43bd6fdc20c7";
+ description = ''
+ UUID for the main NixOS partition on the SD card.
+ '';
+ };
+
+ firmwareSize = mkOption {
+ type = types.int;
+ # As of 2019-08-18 the Raspberry pi firmware + u-boot takes ~18MiB
+ default = 30;
+ description = ''
+ Size of the /boot/firmware partition, in megabytes.
+ '';
+ };
+
+ populateFirmwareCommands = mkOption {
+ example = literalExample "'' cp \${pkgs.myBootLoader}/u-boot.bin firmware/ ''";
+ description = ''
+ Shell commands to populate the ./firmware directory.
+ All files in that directory are copied to the
+ /boot/firmware partition on the SD image.
+ '';
+ };
+
+ populateRootCommands = mkOption {
+ example = literalExample "''\${extlinux-conf-builder} -t 3 -c \${config.system.build.toplevel} -d ./files/boot''";
+ description = ''
+ Shell commands to populate the ./files directory.
+ All files in that directory are copied to the
+ root (/) partition on the SD image. Use this to
+ populate the ./files/boot (/boot) directory.
+ '';
+ };
+ };
+
+ config = {
+ fileSystems = {
+ "/boot/firmware" = {
+ device = "/dev/disk/by-label/FIRMWARE";
+ fsType = "vfat";
+ # Alternatively, this could be removed from the configuration.
+ # The filesystem is not needed at runtime, it could be treated
+ # as an opaque blob instead of a discrete FAT32 filesystem.
+ options = [ "nofail" "noauto" ];
+ };
+ "/" = {
+ device = "/dev/disk/by-label/NIXOS_SD";
+ fsType = "ext4";
+ };
+ };
+
+ sdImage.storePaths = [ config.system.build.toplevel ];
+
+ system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs, mtools, libfaketime, utillinux }: stdenv.mkDerivation {
+ name = config.sdImage.imageName;
+
+ nativeBuildInputs = [ dosfstools e2fsprogs mtools libfaketime utillinux ];
+
+ buildCommand = ''
+ mkdir -p $out/nix-support $out/sd-image
+ export img=$out/sd-image/${config.sdImage.imageName}
+
+ echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system
+ echo "file sd-image $img" >> $out/nix-support/hydra-build-products
+
+ # Gap in front of the first partition, in MiB
+ gap=8
+
+ # Create the image file sized to fit /boot/firmware and /, plus slack for the gap.
+ rootSizeBlocks=$(du -B 512 --apparent-size ${rootfsImage} | awk '{ print $1 }')
+ firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512))
+ imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024))
+ truncate -s $imageSize $img
+
+ # type=b is 'W95 FAT32', type=83 is 'Linux'.
+ # The "bootable" partition is where u-boot will look file for the bootloader
+ # information (dtbs, extlinux.conf file).
+ sfdisk $img <<EOF
+ label: dos
+ label-id: ${config.sdImage.firmwarePartitionID}
+
+ start=''${gap}M, size=$firmwareSizeBlocks, type=b
+ start=$((gap + ${toString config.sdImage.firmwareSize}))M, type=83, bootable
+ EOF
+
+ # Copy the rootfs into the SD image
+ eval $(partx $img -o START,SECTORS --nr 2 --pairs)
+ dd conv=notrunc if=${rootfsImage} of=$img seek=$START count=$SECTORS
+
+ # Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img
+ eval $(partx $img -o START,SECTORS --nr 1 --pairs)
+ truncate -s $((SECTORS * 512)) firmware_part.img
+ faketime "1970-01-01 00:00:00" mkfs.vfat -i ${config.sdImage.firmwarePartitionID} -n FIRMWARE firmware_part.img
+
+ # Populate the files intended for /boot/firmware
+ mkdir firmware
+ ${config.sdImage.populateFirmwareCommands}
+
+ # Copy the populated /boot/firmware into the SD image
+ (cd firmware; mcopy -psvm -i ../firmware_part.img ./* ::)
+ # Verify the FAT partition before copying it.
+ fsck.vfat -vn firmware_part.img
+ dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS
+ '';
+ }) {};
+
+ boot.postBootCommands = ''
+ # On the first boot do some maintenance tasks
+ if [ -f /nix-path-registration ]; then
+ set -euo pipefail
+ set -x
+ # Figure out device names for the boot device and root filesystem.
+ rootPart=$(${pkgs.utillinux}/bin/findmnt -n -o SOURCE /)
+ bootDevice=$(lsblk -npo PKNAME $rootPart)
+
+ # Resize the root partition and the filesystem to fit the disk
+ echo ",+," | sfdisk -N2 --no-reread $bootDevice
+ ${pkgs.parted}/bin/partprobe
+ ${pkgs.e2fsprogs}/bin/resize2fs $rootPart
+
+ # Register the contents of the initial Nix store
+ ${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration
+
+ # nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag.
+ touch /etc/NIXOS
+ ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
+
+ # Prevents this from running on later boots.
+ rm -f /nix-path-registration
+ fi
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix b/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
new file mode 100644
index 00000000000..6d4ba96dba0
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
@@ -0,0 +1,160 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ # A dummy /etc/nixos/configuration.nix in the booted CD that
+ # rebuilds the CD's configuration (and allows the configuration to
+ # be modified, of course, providing a true live CD). Problem is
+ # that we don't really know how the CD was built - the Nix
+ # expression language doesn't allow us to query the expression being
+ # evaluated. So we'll just hope for the best.
+ dummyConfiguration = pkgs.writeText "configuration.nix"
+ ''
+ { config, pkgs, ... }:
+
+ { # Add your own options below, e.g.:
+ # services.openssh.enable = true;
+ nixpkgs.config.platform = pkgs.platforms.fuloong2f_n32;
+ }
+ '';
+
+
+ pkgs2storeContents = l : map (x: { object = x; symlink = "none"; }) l;
+
+ # A clue for the kernel loading
+ kernelParams = pkgs.writeText "kernel-params.txt" ''
+ Kernel Parameters:
+ init=/boot/init systemConfig=/boot/init ${toString config.boot.kernelParams}
+ '';
+
+ # System wide nixpkgs config
+ nixpkgsUserConfig = pkgs.writeText "config.nix" ''
+ pkgs:
+ {
+ platform = pkgs.platforms.fuloong2f_n32;
+ }
+ '';
+
+in
+
+{
+ imports = [ ./system-tarball.nix ];
+
+ # Disable some other stuff we don't need.
+ security.sudo.enable = false;
+
+ # Include only the en_US locale. This saves 75 MiB or so compared to
+ # the full glibcLocales package.
+ i18n.supportedLocales = ["en_US.UTF-8/UTF-8" "en_US/ISO-8859-1"];
+
+ # Include some utilities that are useful for installing or repairing
+ # the system.
+ environment.systemPackages =
+ [ pkgs.w3m # needed for the manual anyway
+ pkgs.testdisk # useful for repairing boot problems
+ pkgs.ms-sys # for writing Microsoft boot sectors / MBRs
+ pkgs.parted
+ pkgs.ddrescue
+ pkgs.ccrypt
+ pkgs.cryptsetup # needed for dm-crypt volumes
+
+ # Some networking tools.
+ pkgs.sshfs-fuse
+ pkgs.socat
+ pkgs.screen
+ pkgs.wpa_supplicant # !!! should use the wpa module
+
+ # Hardware-related tools.
+ pkgs.sdparm
+ pkgs.hdparm
+ pkgs.dmraid
+
+ # Tools to create / manipulate filesystems.
+ pkgs.ntfsprogs # for resizing NTFS partitions
+ pkgs.btrfs-progs
+ pkgs.jfsutils
+
+ # Some compression/archiver tools.
+ pkgs.unzip
+ pkgs.zip
+ pkgs.xz
+ pkgs.dar # disk archiver
+
+ # Some editors.
+ pkgs.nvi
+ pkgs.bvi # binary editor
+ pkgs.joe
+ ];
+
+ # The initrd has to contain any module that might be necessary for
+ # mounting the CD/DVD.
+ boot.initrd.availableKernelModules =
+ [ "vfat" "reiserfs" ];
+
+ boot.kernelPackages = pkgs.linuxPackages_3_10;
+ boot.kernelParams = [ "console=tty1" ];
+
+ boot.postBootCommands =
+ ''
+ mkdir -p /mnt
+
+ cp ${dummyConfiguration} /etc/nixos/configuration.nix
+ '';
+
+ # Some more help text.
+ services.mingetty.helpLine =
+ ''
+
+ Log in as "root" with an empty password. ${
+ if config.services.xserver.enable then
+ "Type `start xserver' to start\nthe graphical user interface."
+ else ""
+ }
+ '';
+
+ # Include the firmware for various wireless cards.
+ networking.enableRalinkFirmware = true;
+ networking.enableIntel2200BGFirmware = true;
+
+ # To speed up further installation of packages, include the complete stdenv
+ # in the Nix store of the tarball.
+ tarball.storeContents = pkgs2storeContents [ pkgs.stdenv ]
+ ++ [
+ {
+ object = config.system.build.bootStage2;
+ symlink = "/boot/init";
+ }
+ {
+ object = config.system.build.toplevel;
+ symlink = "/boot/system";
+ }
+ ];
+
+ tarball.contents = [
+ { source = kernelParams;
+ target = "/kernelparams.txt";
+ }
+ { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
+ target = "/boot/" + config.system.boot.loader.kernelFile;
+ }
+ { source = nixpkgsUserConfig;
+ target = "/root/.nixpkgs/config.nix";
+ }
+ ];
+
+ # Allow sshd to be started manually through "start sshd". It should
+ # not be started by default on the installation CD because the
+ # default root password is empty.
+ services.openssh.enable = true;
+ systemd.services.openssh.wantedBy = lib.mkOverride 50 [];
+
+ boot.loader.grub.enable = false;
+ boot.loader.generationsDir.enable = false;
+ system.boot.loader.kernelFile = "vmlinux";
+
+ nixpkgs.config = {
+ platform = pkgs.platforms.fuloong2f_n32;
+ };
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt b/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt
new file mode 100644
index 00000000000..84252f292c5
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt
@@ -0,0 +1,89 @@
+Let all the files in the system tarball sit in a directory served by NFS (the
+NFS root) like this in exportfs:
+ /home/pcroot 192.168.1.0/24(rw,no_root_squash,no_all_squash)
+
+Run "exportfs -a" after editing /etc/exportfs, for the nfs server to be aware
+of the changes.
+
+Use a tftp server serving the root of boot/ (from the system tarball).
+
+In order to have PXE boot, use the boot/dhcpd.conf-example file for your dhcpd
+server, as it will point your PXE clients to pxelinux.0 from the tftp server.
+Adapt the configuration to your network.
+
+Adapt the pxelinux configuration (boot/pxelinux.cfg/default) to set the path to
+your nfrroot. If you use ip=dhcp in the kernel, the nfs server ip will be taken
+from dhcp and so you don't have to specify it.
+
+The linux in bzImage includes network drivers for some usual cards.
+
+
+QEMU Testing
+---------------
+
+You can test qemu pxe boot without having a DHCP server adapted, but having
+nfsroot, like this:
+ qemu-system-x86_64 -tftp /home/pcroot/boot -net nic -net user,bootfile=pxelinux.0 -boot n
+
+I don't know how to use NFS through the qemu '-net user' though.
+
+
+QEMU Testing with NFS root and bridged network
+-------------------------------------------------
+
+This allows testing with qemu as any other host in your LAN.
+
+Testing with the real dhcpd server requires setting up a bridge and having a
+tap device.
+ tunctl -t tap0
+ brctl addbr br0
+ brctl addif br0 eth0
+ brctl addif tap0 eth0
+ ifconfig eth0 0.0.0.0 up
+ ifconfig tap0 0.0.0.0 up
+ ifconfig br0 up # With your ip configuration
+
+Then you can run qemu:
+ qemu-system-x86_64 -boot n -net tap,ifname=tap0,script=no -net nic,model=e1000
+
+
+Using the system-tarball-pc in a chroot
+--------------------------------------------------
+
+Installation:
+ mkdir nixos-chroot && cd nixos-chroot
+ tar xf your-system-tarball.tar.xz
+ mkdir sys dev proc tmp root var run
+ mount --bind /sys sys
+ mount --bind /dev dev
+ mount --bind /proc proc
+
+Activate the system: look for a directory in nix/store similar to:
+ "/nix/store/y0d1lcj9fppli0hl3x0m0ba5g1ndjv2j-nixos-feb97bx-53f008"
+Having found it, activate that nixos system *twice*:
+ chroot . /nix/store/SOMETHING-nixos-SOMETHING/activate
+ chroot . /nix/store/SOMETHING-nixos-SOMETHING/activate
+
+This runs a 'hostname' command. Restore your old hostname with:
+ hostname OLDHOSTNAME
+
+Copy your system resolv.conf to the /etc/resolv.conf inside the chroot:
+ cp /etc/resolv.conf etc
+
+Then you can get an interactive shell in the nixos chroot. '*' means
+to run inside the chroot interactive shell
+ chroot . /bin/sh
+* source /etc/profile
+
+Populate the nix database: that should be done in the init script if you
+had booted this nixos. Run:
+* `grep local-cmds run/current-system/init`
+
+Then you can proceed normally subscribing to a nixos channel:
+ nix-channel --add https://nixos.org/channels/nixos-unstable
+ nix-channel --update
+
+Testing:
+ nix-env -i hello
+ which hello
+ hello
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-pc.nix b/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
new file mode 100644
index 00000000000..bf8b7deb59e
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
@@ -0,0 +1,164 @@
+# This module contains the basic configuration for building a NixOS
+# tarball, that can directly boot, maybe using PXE or unpacking on a fs.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ pkgs2storeContents = l : map (x: { object = x; symlink = "none"; }) l;
+
+ # For PXE kernel loading
+ pxeconfig = pkgs.writeText "pxeconfig-default" ''
+ default menu.c32
+ prompt 0
+
+ label bootlocal
+ menu default
+ localboot 0
+ timeout 80
+ TOTALTIMEOUT 9000
+
+ label nixos
+ MENU LABEL ^NixOS using nfsroot
+ KERNEL bzImage
+ append ip=dhcp nfsroot=/home/pcroot systemConfig=${config.system.build.toplevel} init=${config.system.build.toplevel}/init rw
+
+ # I don't know how to make this boot with nfsroot (using the initrd)
+ label nixos_initrd
+ MENU LABEL NixOS booting the poor ^initrd.
+ KERNEL bzImage
+ append initrd=initrd ip=dhcp nfsroot=/home/pcroot systemConfig=${config.system.build.toplevel} init=${config.system.build.toplevel}/init rw
+
+ label memtest
+ MENU LABEL ^${pkgs.memtest86.name}
+ KERNEL memtest
+ '';
+
+ dhcpdExampleConfig = pkgs.writeText "dhcpd.conf-example" ''
+ # Example configuration for booting PXE.
+ allow booting;
+ allow bootp;
+
+ # Adapt this to your network configuration.
+ option domain-name "local";
+ option subnet-mask 255.255.255.0;
+ option broadcast-address 192.168.1.255;
+ option domain-name-servers 192.168.1.1;
+ option routers 192.168.1.1;
+
+ # PXE-specific configuration directives...
+ # Some BIOS don't accept slashes for paths inside the tftp servers,
+ # and will report Access Violation if they see slashes.
+ filename "pxelinux.0";
+ # For the TFTP and NFS root server. Set the IP of your server.
+ next-server 192.168.1.34;
+
+ subnet 192.168.1.0 netmask 255.255.255.0 {
+ range 192.168.1.50 192.168.1.55;
+ }
+ '';
+
+ readme = ./system-tarball-pc-readme.txt;
+
+in
+
+{
+ imports =
+ [ ./system-tarball.nix
+
+ # Profiles of this basic installation.
+ ../../profiles/all-hardware.nix
+ ../../profiles/base.nix
+ ../../profiles/installation-device.nix
+ ];
+
+ # To speed up further installation of packages, include the complete stdenv
+ # in the Nix store of the tarball.
+ tarball.storeContents = pkgs2storeContents [ pkgs.stdenv ];
+
+ tarball.contents =
+ [ { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
+ target = "/boot/" + config.system.boot.loader.kernelFile;
+ }
+ { source = "${pkgs.syslinux}/share/syslinux/pxelinux.0";
+ target = "/boot/pxelinux.0";
+ }
+ { source = "${pkgs.syslinux}/share/syslinux/menu.c32";
+ target = "/boot/menu.c32";
+ }
+ { source = pxeconfig;
+ target = "/boot/pxelinux.cfg/default";
+ }
+ { source = readme;
+ target = "/readme.txt";
+ }
+ { source = dhcpdExampleConfig;
+ target = "/boot/dhcpd.conf-example";
+ }
+ { source = "${pkgs.memtest86}/memtest.bin";
+ # We can't leave '.bin', because pxelinux interprets this specially,
+ # and it would not load the image fine.
+ # http://forum.canardpc.com/threads/46464-0104-when-launched-via-pxe
+ target = "/boot/memtest";
+ }
+ ];
+
+ # Allow sshd to be started manually through "start sshd". It should
+ # not be started by default on the installation CD because the
+ # default root password is empty.
+ services.openssh.enable = true;
+ systemd.services.openssh.wantedBy = lib.mkOverride 50 [];
+
+ # To be able to use the systemTarball to catch troubles.
+ boot.crashDump = {
+ enable = true;
+ kernelPackages = pkgs.linuxPackages_3_4;
+ };
+
+ # No grub for the tarball.
+ boot.loader.grub.enable = false;
+
+ /* fake entry, just to have a happy stage-1. Users
+ may boot without having stage-1 though */
+ fileSystems = [
+ { mountPoint = "/";
+ device = "/dev/something";
+ }
+ ];
+
+ nixpkgs.config = {
+ packageOverrides = p: {
+ linux_3_4 = p.linux_3_4.override {
+ extraConfig = ''
+ # Enable drivers in kernel for most NICs.
+ E1000 y
+ # E1000E y
+ # ATH5K y
+ 8139TOO y
+ NE2K_PCI y
+ ATL1 y
+ ATL1E y
+ ATL1C y
+ VORTEX y
+ VIA_RHINE y
+ R8169 y
+
+ # Enable nfs root boot
+ UNIX y # http://www.linux-mips.org/archives/linux-mips/2006-11/msg00113.html
+ IP_PNP y
+ IP_PNP_DHCP y
+ FSCACHE y
+ NFS_FS y
+ NFS_FSCACHE y
+ ROOT_NFS y
+
+ # Enable devtmpfs
+ DEVTMPFS y
+ DEVTMPFS_MOUNT y
+ '';
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix b/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
new file mode 100644
index 00000000000..90a5128c02a
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
@@ -0,0 +1,173 @@
+# This module contains the basic configuration for building a NixOS
+# tarball for the sheevaplug.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ # A dummy /etc/nixos/configuration.nix in the booted CD that
+ # rebuilds the CD's configuration (and allows the configuration to
+ # be modified, of course, providing a true live CD). Problem is
+ # that we don't really know how the CD was built - the Nix
+ # expression language doesn't allow us to query the expression being
+ # evaluated. So we'll just hope for the best.
+ dummyConfiguration = pkgs.writeText "configuration.nix"
+ ''
+ { config, pkgs, ... }:
+
+ {
+ # Add your own options below and run "nixos-rebuild switch".
+ # E.g.,
+ # services.openssh.enable = true;
+ }
+ '';
+
+
+ pkgs2storeContents = l : map (x: { object = x; symlink = "none"; }) l;
+
+ # A clue for the kernel loading
+ kernelParams = pkgs.writeText "kernel-params.txt" ''
+ Kernel Parameters:
+ init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}
+ '';
+
+
+in
+
+{
+ imports = [ ./system-tarball.nix ];
+
+ # Disable some other stuff we don't need.
+ security.sudo.enable = false;
+
+ # Include only the en_US locale. This saves 75 MiB or so compared to
+ # the full glibcLocales package.
+ i18n.supportedLocales = ["en_US.UTF-8/UTF-8" "en_US/ISO-8859-1"];
+
+ # Include some utilities that are useful for installing or repairing
+ # the system.
+ environment.systemPackages =
+ [ pkgs.w3m # needed for the manual anyway
+ pkgs.ddrescue
+ pkgs.ccrypt
+ pkgs.cryptsetup # needed for dm-crypt volumes
+
+ # Some networking tools.
+ pkgs.sshfs-fuse
+ pkgs.socat
+ pkgs.screen
+ pkgs.wpa_supplicant # !!! should use the wpa module
+
+ # Hardware-related tools.
+ pkgs.sdparm
+ pkgs.hdparm
+ pkgs.dmraid
+
+ # Tools to create / manipulate filesystems.
+ pkgs.btrfs-progs
+
+ # Some compression/archiver tools.
+ pkgs.unzip
+ pkgs.zip
+ pkgs.xz
+ pkgs.dar # disk archiver
+
+ # Some editors.
+ pkgs.nvi
+ pkgs.bvi # binary editor
+ pkgs.joe
+ ];
+
+ boot.loader.grub.enable = false;
+ boot.loader.generationsDir.enable = false;
+ system.boot.loader.kernelFile = "uImage";
+
+ boot.initrd.availableKernelModules =
+ [ "mvsdio" "reiserfs" "ext3" "ums-cypress" "rtc_mv" "ext4" ];
+
+ boot.postBootCommands =
+ ''
+ mkdir -p /mnt
+
+ cp ${dummyConfiguration} /etc/nixos/configuration.nix
+ '';
+
+ boot.initrd.extraUtilsCommands =
+ ''
+ copy_bin_and_libs ${pkgs.utillinux}/sbin/hwclock
+ '';
+
+ boot.initrd.postDeviceCommands =
+ ''
+ hwclock -s
+ '';
+
+ boot.kernelParams =
+ [
+ "selinux=0"
+ "console=tty1"
+ # "console=ttyS0,115200n8" # serial console
+ ];
+
+ boot.kernelPackages = pkgs.linuxPackages_3_4;
+
+ boot.supportedFilesystems = [ "reiserfs" ];
+
+ /* fake entry, just to have a happy stage-1. Users
+ may boot without having stage-1 though */
+ fileSystems = [
+ { mountPoint = "/";
+ device = "/dev/something";
+ }
+ ];
+
+ services.mingetty = {
+ # Some more help text.
+ helpLine = ''
+ Log in as "root" with an empty password. ${
+ if config.services.xserver.enable then
+ "Type `start xserver' to start\nthe graphical user interface."
+ else ""
+ }
+ '';
+ };
+
+ # Setting vesa, we don't get the nvidia driver, which can't work in arm.
+ services.xserver.videoDrivers = [ "vesa" ];
+
+ documentation.nixos.enable = false;
+
+ # Include the firmware for various wireless cards.
+ networking.enableRalinkFirmware = true;
+ networking.enableIntel2200BGFirmware = true;
+
+ # To speed up further installation of packages, include the complete stdenv
+ # in the Nix store of the tarball.
+ tarball.storeContents = pkgs2storeContents [ pkgs.stdenv ];
+ tarball.contents = [
+ { source = kernelParams;
+ target = "/kernelparams.txt";
+ }
+ { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
+ target = "/boot/" + config.system.boot.loader.kernelFile;
+ }
+ { source = pkgs.ubootSheevaplug;
+ target = "/boot/uboot";
+ }
+ ];
+
+ # Allow sshd to be started manually through "start sshd". It should
+ # not be started by default on the installation CD because the
+ # default root password is empty.
+ services.openssh.enable = true;
+ systemd.services.openssh.wantedBy = lib.mkOverride 50 [];
+
+ # cpufrequtils fails to build on non-pc
+ powerManagement.enable = false;
+
+ nixpkgs.config = {
+ platform = pkgs.platforms.sheevaplug;
+ };
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball.nix b/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball.nix
new file mode 100644
index 00000000000..b84096861f5
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/system-tarball.nix
@@ -0,0 +1,93 @@
+# This module creates a bootable ISO image containing the given NixOS
+# configuration. The derivation for the ISO image will be placed in
+# config.system.build.tarball.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ versionFile = pkgs.writeText "nixos-label" config.system.nixos.label;
+
+in
+
+{
+ options = {
+ tarball.contents = mkOption {
+ example = literalExample ''
+ [ { source = pkgs.memtest86 + "/memtest.bin";
+ target = "boot/memtest.bin";
+ }
+ ]
+ '';
+ description = ''
+ This option lists files to be copied to fixed locations in the
+ generated ISO image.
+ '';
+ };
+
+ tarball.storeContents = mkOption {
+ example = literalExample "[ pkgs.stdenv ]";
+ description = ''
+ This option lists additional derivations to be included in the
+ Nix store in the generated ISO image.
+ '';
+ };
+
+ };
+
+ config = {
+
+ # In stage 1 of the boot, mount the CD/DVD as the root FS by label
+ # so that we don't need to know its device.
+ fileSystems = [ ];
+
+ # boot.initrd.availableKernelModules = [ "mvsdio" "reiserfs" "ext3" "ext4" ];
+
+ # boot.initrd.kernelModules = [ "rtc_mv" ];
+
+ # Closures to be copied to the Nix store on the CD, namely the init
+ # script and the top-level system configuration directory.
+ tarball.storeContents =
+ [ { object = config.system.build.toplevel;
+ symlink = "/run/current-system";
+ }
+ ];
+
+ # Individual files to be included on the CD, outside of the Nix
+ # store on the CD.
+ tarball.contents =
+ [ { source = config.system.build.initialRamdisk + "/" + config.system.boot.loader.initrdFile;
+ target = "/boot/" + config.system.boot.loader.initrdFile;
+ }
+ { source = versionFile;
+ target = "/nixos-version.txt";
+ }
+ ];
+
+ # Create the tarball
+ system.build.tarball = import ../../../lib/make-system-tarball.nix {
+ inherit (pkgs) stdenv closureInfo pixz;
+
+ inherit (config.tarball) contents storeContents;
+ };
+
+ boot.postBootCommands =
+ ''
+ # After booting, register the contents of the Nix store on the
+ # CD in the Nix database in the tmpfs.
+ if [ -f /nix-path-registration ]; then
+ ${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration &&
+ rm /nix-path-registration
+ fi
+
+ # nixos-rebuild also requires a "system" profile and an
+ # /etc/NIXOS tag.
+ touch /etc/NIXOS
+ ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/installer/netboot/netboot-base.nix b/nixpkgs/nixos/modules/installer/netboot/netboot-base.nix
new file mode 100644
index 00000000000..7e66a49c739
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/netboot/netboot-base.nix
@@ -0,0 +1,17 @@
+# This module contains the basic configuration for building netboot
+# images
+
+{ lib, ... }:
+
+with lib;
+
+{
+ imports =
+ [ ./netboot.nix
+
+ # Profiles of this basic netboot media
+ ../../profiles/all-hardware.nix
+ ../../profiles/base.nix
+ ../../profiles/installation-device.nix
+ ];
+}
diff --git a/nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix b/nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix
new file mode 100644
index 00000000000..1563501a7e0
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix
@@ -0,0 +1,10 @@
+# This module defines a small netboot environment.
+
+{ ... }:
+
+{
+ imports =
+ [ ./netboot-base.nix
+ ../../profiles/minimal.nix
+ ];
+}
diff --git a/nixpkgs/nixos/modules/installer/netboot/netboot.nix b/nixpkgs/nixos/modules/installer/netboot/netboot.nix
new file mode 100644
index 00000000000..5146858cccf
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/netboot/netboot.nix
@@ -0,0 +1,108 @@
+# This module creates netboot media containing the given NixOS
+# configuration.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options = {
+
+ netboot.storeContents = mkOption {
+ example = literalExample "[ pkgs.stdenv ]";
+ description = ''
+ This option lists additional derivations to be included in the
+ Nix store in the generated netboot image.
+ '';
+ };
+
+ };
+
+ config = {
+ # Don't build the GRUB menu builder script, since we don't need it
+ # here and it causes a cyclic dependency.
+ boot.loader.grub.enable = false;
+
+ # !!! Hack - attributes expected by other modules.
+ environment.systemPackages = [ pkgs.grub2_efi ]
+ ++ (if pkgs.stdenv.hostPlatform.system == "aarch64-linux"
+ then []
+ else [ pkgs.grub2 pkgs.syslinux ]);
+
+ fileSystems."/" =
+ { fsType = "tmpfs";
+ options = [ "mode=0755" ];
+ };
+
+ # In stage 1, mount a tmpfs on top of /nix/store (the squashfs
+ # image) to make this a live CD.
+ fileSystems."/nix/.ro-store" =
+ { fsType = "squashfs";
+ device = "../nix-store.squashfs";
+ options = [ "loop" ];
+ neededForBoot = true;
+ };
+
+ fileSystems."/nix/.rw-store" =
+ { fsType = "tmpfs";
+ options = [ "mode=0755" ];
+ neededForBoot = true;
+ };
+
+ fileSystems."/nix/store" =
+ { fsType = "unionfs-fuse";
+ device = "unionfs";
+ options = [ "allow_other" "cow" "nonempty" "chroot=/mnt-root" "max_files=32768" "hide_meta_files" "dirs=/nix/.rw-store=rw:/nix/.ro-store=ro" ];
+ };
+
+ boot.initrd.availableKernelModules = [ "squashfs" ];
+
+ boot.initrd.kernelModules = [ "loop" ];
+
+ # Closures to be copied to the Nix store, namely the init
+ # script and the top-level system configuration directory.
+ netboot.storeContents =
+ [ config.system.build.toplevel ];
+
+ # Create the squashfs image that contains the Nix store.
+ system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
+ storeContents = config.netboot.storeContents;
+ };
+
+
+ # Create the initrd
+ system.build.netbootRamdisk = pkgs.makeInitrd {
+ inherit (config.boot.initrd) compressor;
+ prepend = [ "${config.system.build.initialRamdisk}/initrd" ];
+
+ contents =
+ [ { object = config.system.build.squashfsStore;
+ symlink = "/nix-store.squashfs";
+ }
+ ];
+ };
+
+ system.build.netbootIpxeScript = pkgs.writeTextDir "netboot.ipxe" ''
+ #!ipxe
+ kernel ${pkgs.stdenv.hostPlatform.platform.kernelTarget} init=${config.system.build.toplevel}/init initrd=initrd ${toString config.boot.kernelParams}
+ initrd initrd
+ boot
+ '';
+
+ boot.loader.timeout = 10;
+
+ boot.postBootCommands =
+ ''
+ # After booting, register the contents of the Nix store
+ # in the Nix database in the tmpfs.
+ ${config.nix.package}/bin/nix-store --load-db < /nix/store/nix-path-registration
+
+ # nixos-rebuild also requires a "system" profile and an
+ # /etc/NIXOS tag.
+ touch /etc/NIXOS
+ ${config.nix.package}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/installer/scan/detected.nix b/nixpkgs/nixos/modules/installer/scan/detected.nix
new file mode 100644
index 00000000000..5c5fba56f51
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/scan/detected.nix
@@ -0,0 +1,12 @@
+# List all devices which are detected by nixos-generate-config.
+# Common devices are enabled by default.
+{ lib, ... }:
+
+with lib;
+
+{
+ config = mkDefault {
+ # Common firmware, i.e. for wifi cards
+ hardware.enableRedistributableFirmware = true;
+ };
+}
diff --git a/nixpkgs/nixos/modules/installer/scan/not-detected.nix b/nixpkgs/nixos/modules/installer/scan/not-detected.nix
new file mode 100644
index 00000000000..baa068c08db
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/scan/not-detected.nix
@@ -0,0 +1,6 @@
+# Enables non-free firmware on devices not recognized by `nixos-generate-config`.
+{ lib, ... }:
+
+{
+ hardware.enableRedistributableFirmware = lib.mkDefault true;
+}
diff --git a/nixpkgs/nixos/modules/installer/tools/get-version-suffix b/nixpkgs/nixos/modules/installer/tools/get-version-suffix
new file mode 100644
index 00000000000..b8972cd57d2
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/get-version-suffix
@@ -0,0 +1,22 @@
+getVersion() {
+ local dir="$1"
+ rev=
+ if [ -e "$dir/.git" ]; then
+ if [ -z "$(type -P git)" ]; then
+ echo "warning: Git not found; cannot figure out revision of $dir" >&2
+ return
+ fi
+ cd "$dir"
+ rev=$(git rev-parse --short HEAD)
+ if git describe --always --dirty | grep -q dirty; then
+ rev+=M
+ fi
+ fi
+}
+
+if nixpkgs=$(nix-instantiate --find-file nixpkgs "$@"); then
+ getVersion $nixpkgs
+ if [ -n "$rev" ]; then
+ echo ".git.$rev"
+ fi
+fi
diff --git a/nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix
new file mode 100644
index 00000000000..2673887d2b9
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -0,0 +1,6 @@
+{
+ x86_64-linux = "/nix/store/3ds3cgji9vjxdbgp10av6smyym1126d1-nix-2.3";
+ i686-linux = "/nix/store/ln1ndqvfpc9cdl03vqxi6kvlxm9wfv9g-nix-2.3";
+ aarch64-linux = "/nix/store/n8a1rwzrp20qcr2c4hvyn6c5q9zx8csw-nix-2.3";
+ x86_64-darwin = "/nix/store/jq6npmpld02sz4rgniz0qrsdfnm6j17a-nix-2.3";
+}
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix b/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
new file mode 100644
index 00000000000..c1028a0ad7e
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
@@ -0,0 +1,13 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, networkExpr
+}:
+
+let nodes = import networkExpr; in
+
+with import ../../../../lib/testing.nix {
+ inherit system;
+ pkgs = import ../../../../.. { inherit system config; };
+};
+
+(makeTest { inherit nodes; testScript = ""; }).driver
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh b/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh
new file mode 100644
index 00000000000..25106733087
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh
@@ -0,0 +1,52 @@
+#! @shell@ -e
+
+# Shows the usage of this command to the user
+
+showUsage() {
+ exec man nixos-build-vms
+ exit 1
+}
+
+# Parse valid argument options
+
+nixBuildArgs=()
+networkExpr=
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --no-out-link)
+ nixBuildArgs+=("--no-out-link")
+ ;;
+ --show-trace)
+ nixBuildArgs+=("--show-trace")
+ ;;
+ -h|--help)
+ showUsage
+ exit 0
+ ;;
+ --option)
+ shift
+ nixBuildArgs+=("--option" "$1" "$2"); shift
+ ;;
+ *)
+ if [ ! -z "$networkExpr" ]; then
+ echo "Network expression already set!"
+ showUsage
+ exit 1
+ fi
+ networkExpr="$(readlink -f $1)"
+ ;;
+ esac
+
+ shift
+done
+
+if [ -z "$networkExpr" ]
+then
+ echo "ERROR: A network expression must be specified!" >&2
+ exit 1
+fi
+
+# Build a network of VMs
+nix-build '<nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix>' \
+ --argstr networkExpr $networkExpr "${nixBuildArgs[@]}"
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh b/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh
new file mode 100644
index 00000000000..4680cd8ae95
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh
@@ -0,0 +1,74 @@
+#! @shell@
+
+set -e
+
+# Re-exec ourselves in a private mount namespace so that our bind
+# mounts get cleaned up automatically.
+if [ -z "$NIXOS_ENTER_REEXEC" ]; then
+ export NIXOS_ENTER_REEXEC=1
+ if [ "$(id -u)" != 0 ]; then
+ extraFlags="-r"
+ fi
+ exec unshare --fork --mount --uts --mount-proc --pid $extraFlags -- "$0" "$@"
+else
+ mount --make-rprivate /
+fi
+
+mountPoint=/mnt
+system=/nix/var/nix/profiles/system
+command=("$system/sw/bin/bash" "--login")
+silent=0
+
+while [ "$#" -gt 0 ]; do
+ i="$1"; shift 1
+ case "$i" in
+ --root)
+ mountPoint="$1"; shift 1
+ ;;
+ --system)
+ system="$1"; shift 1
+ ;;
+ --help)
+ exec man nixos-enter
+ exit 1
+ ;;
+ --command|-c)
+ command=("$system/sw/bin/bash" "-c" "$1")
+ shift 1
+ ;;
+ --silent)
+ silent=1
+ ;;
+ --)
+ command=("$@")
+ break
+ ;;
+ *)
+ echo "$0: unknown option \`$i'"
+ exit 1
+ ;;
+ esac
+done
+
+if [[ ! -e $mountPoint/etc/NIXOS ]]; then
+ echo "$0: '$mountPoint' is not a NixOS installation" >&2
+ exit 126
+fi
+
+mkdir -p "$mountPoint/dev" "$mountPoint/sys"
+chmod 0755 "$mountPoint/dev" "$mountPoint/sys"
+mount --rbind /dev "$mountPoint/dev"
+mount --rbind /sys "$mountPoint/sys"
+
+# If silent, write both stdout and stderr of activation script to /dev/null
+# otherwise, write both streams to stderr of this process
+if [ "$silent" -eq 0 ]; then
+ PIPE_TARGET="/dev/stderr"
+else
+ PIPE_TARGET="/dev/null"
+fi
+
+# Run the activation script. Set $LOCALE_ARCHIVE to supress some Perl locale warnings.
+LOCALE_ARCHIVE="$system/sw/lib/locale/locale-archive" chroot "$mountPoint" "$system/activate" >>$PIPE_TARGET 2>&1 || true
+
+exec chroot "$mountPoint" "${command[@]}"
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl b/nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl
new file mode 100644
index 00000000000..cfdbdaabf5c
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -0,0 +1,617 @@
+#! @perl@
+
+use strict;
+use Cwd 'abs_path';
+use File::Spec;
+use File::Path;
+use File::Basename;
+use File::Slurp;
+use File::stat;
+
+umask(0022);
+
+sub uniq {
+ my %seen;
+ my @res = ();
+ foreach my $s (@_) {
+ if (!defined $seen{$s}) {
+ $seen{$s} = 1;
+ push @res, $s;
+ }
+ }
+ return @res;
+}
+
+sub runCommand {
+ my ($cmd) = @_;
+ open FILE, "$cmd 2>&1 |" or die "Failed to execute: $cmd\n";
+ my @ret = <FILE>;
+ close FILE;
+ return ($?, @ret);
+}
+
+# Process the command line.
+my $outDir = "/etc/nixos";
+my $rootDir = ""; # = /
+my $force = 0;
+my $noFilesystems = 0;
+my $showHardwareConfig = 0;
+
+for (my $n = 0; $n < scalar @ARGV; $n++) {
+ my $arg = $ARGV[$n];
+ if ($arg eq "--help") {
+ exec "man nixos-generate-config" or die;
+ }
+ elsif ($arg eq "--dir") {
+ $n++;
+ $outDir = $ARGV[$n];
+ die "$0: ‘--dir’ requires an argument\n" unless defined $outDir;
+ }
+ elsif ($arg eq "--root") {
+ $n++;
+ $rootDir = $ARGV[$n];
+ die "$0: ‘--root’ requires an argument\n" unless defined $rootDir;
+ $rootDir =~ s/\/*$//; # remove trailing slashes
+ }
+ elsif ($arg eq "--force") {
+ $force = 1;
+ }
+ elsif ($arg eq "--no-filesystems") {
+ $noFilesystems = 1;
+ }
+ elsif ($arg eq "--show-hardware-config") {
+ $showHardwareConfig = 1;
+ }
+ else {
+ die "$0: unrecognized argument ‘$arg’\n";
+ }
+}
+
+
+my @attrs = ();
+my @kernelModules = ();
+my @initrdKernelModules = ();
+my @initrdAvailableKernelModules = ();
+my @modulePackages = ();
+my @imports;
+
+
+sub debug {
+ return unless defined $ENV{"DEBUG"};
+ print STDERR @_;
+}
+
+
+my $cpuinfo = read_file "/proc/cpuinfo";
+
+
+sub hasCPUFeature {
+ my $feature = shift;
+ return $cpuinfo =~ /^flags\s*:.* $feature( |$)/m;
+}
+
+
+# Detect the number of CPU cores.
+my $cpus = scalar (grep {/^processor\s*:/} (split '\n', $cpuinfo));
+
+
+# Determine CPU governor to use
+if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") {
+ my $governors = read_file("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors");
+ # ondemand governor is not available on sandy bridge or later Intel CPUs
+ my @desired_governors = ("ondemand", "powersave");
+ my $e;
+
+ foreach $e (@desired_governors) {
+ if (index($governors, $e) != -1) {
+ last if (push @attrs, "powerManagement.cpuFreqGovernor = lib.mkDefault \"$e\";");
+ }
+ }
+}
+
+
+# Virtualization support?
+push @kernelModules, "kvm-intel" if hasCPUFeature "vmx";
+push @kernelModules, "kvm-amd" if hasCPUFeature "svm";
+
+
+# Look at the PCI devices and add necessary modules. Note that most
+# modules are auto-detected so we don't need to list them here.
+# However, some are needed in the initrd to boot the system.
+
+my $videoDriver;
+
+sub pciCheck {
+ my $path = shift;
+ my $vendor = read_file "$path/vendor"; chomp $vendor;
+ my $device = read_file "$path/device"; chomp $device;
+ my $class = read_file "$path/class"; chomp $class;
+
+ my $module;
+ if (-e "$path/driver/module") {
+ $module = basename `readlink -f $path/driver/module`;
+ chomp $module;
+ }
+
+ debug "$path: $vendor $device $class";
+ debug " $module" if defined $module;
+ debug "\n";
+
+ if (defined $module) {
+ # See the bottom of http://pciids.sourceforge.net/pci.ids for
+ # device classes.
+ if (# Mass-storage controller. Definitely important.
+ $class =~ /^0x01/ ||
+
+ # Firewire controller. A disk might be attached.
+ $class =~ /^0x0c00/ ||
+
+ # USB controller. Needed if we want to use the
+ # keyboard when things go wrong in the initrd.
+ $class =~ /^0x0c03/
+ )
+ {
+ push @initrdAvailableKernelModules, $module;
+ }
+ }
+
+ # broadcom STA driver (wl.ko)
+ # list taken from http://www.broadcom.com/docs/linux_sta/README.txt
+ if ($vendor eq "0x14e4" &&
+ ($device eq "0x4311" || $device eq "0x4312" || $device eq "0x4313" ||
+ $device eq "0x4315" || $device eq "0x4327" || $device eq "0x4328" ||
+ $device eq "0x4329" || $device eq "0x432a" || $device eq "0x432b" ||
+ $device eq "0x432c" || $device eq "0x432d" || $device eq "0x4353" ||
+ $device eq "0x4357" || $device eq "0x4358" || $device eq "0x4359" ||
+ $device eq "0x4331" || $device eq "0x43a0" || $device eq "0x43b1"
+ ) )
+ {
+ push @modulePackages, "config.boot.kernelPackages.broadcom_sta";
+ push @kernelModules, "wl";
+ }
+
+ # broadcom FullMac driver
+ # list taken from
+ # https://wireless.wiki.kernel.org/en/users/Drivers/brcm80211#brcmfmac
+ if ($vendor eq "0x14e4" &&
+ ($device eq "0x43a3" || $device eq "0x43df" || $device eq "0x43ec" ||
+ $device eq "0x43d3" || $device eq "0x43d9" || $device eq "0x43e9" ||
+ $device eq "0x43ba" || $device eq "0x43bb" || $device eq "0x43bc" ||
+ $device eq "0xaa52" || $device eq "0x43ca" || $device eq "0x43cb" ||
+ $device eq "0x43cc" || $device eq "0x43c3" || $device eq "0x43c4" ||
+ $device eq "0x43c5"
+ ) )
+ {
+ # we need e.g. brcmfmac43602-pcie.bin
+ push @imports, "<nixpkgs/nixos/modules/hardware/network/broadcom-43xx.nix>";
+ }
+
+ # Can't rely on $module here, since the module may not be loaded
+ # due to missing firmware. Ideally we would check modules.pcimap
+ # here.
+ push @attrs, "networking.enableIntel2200BGFirmware = true;" if
+ $vendor eq "0x8086" &&
+ ($device eq "0x1043" || $device eq "0x104f" || $device eq "0x4220" ||
+ $device eq "0x4221" || $device eq "0x4223" || $device eq "0x4224");
+
+ push @attrs, "networking.enableIntel3945ABGFirmware = true;" if
+ $vendor eq "0x8086" &&
+ ($device eq "0x4229" || $device eq "0x4230" ||
+ $device eq "0x4222" || $device eq "0x4227");
+
+ # Assume that all NVIDIA cards are supported by the NVIDIA driver.
+ # There may be exceptions (e.g. old cards).
+ # FIXME: do we want to enable an unfree driver here?
+ #$videoDriver = "nvidia" if $vendor eq "0x10de" && $class =~ /^0x03/;
+}
+
+foreach my $path (glob "/sys/bus/pci/devices/*") {
+ pciCheck $path;
+}
+
+# Idem for USB devices.
+
+sub usbCheck {
+ my $path = shift;
+ my $class = read_file "$path/bInterfaceClass"; chomp $class;
+ my $subclass = read_file "$path/bInterfaceSubClass"; chomp $subclass;
+ my $protocol = read_file "$path/bInterfaceProtocol"; chomp $protocol;
+
+ my $module;
+ if (-e "$path/driver/module") {
+ $module = basename `readlink -f $path/driver/module`;
+ chomp $module;
+ }
+
+ debug "$path: $class $subclass $protocol";
+ debug " $module" if defined $module;
+ debug "\n";
+
+ if (defined $module) {
+ if (# Mass-storage controller. Definitely important.
+ $class eq "08" ||
+
+ # Keyboard. Needed if we want to use the
+ # keyboard when things go wrong in the initrd.
+ ($class eq "03" && $protocol eq "01")
+ )
+ {
+ push @initrdAvailableKernelModules, $module;
+ }
+ }
+}
+
+foreach my $path (glob "/sys/bus/usb/devices/*") {
+ if (-e "$path/bInterfaceClass") {
+ usbCheck $path;
+ }
+}
+
+
+# Add the modules for all block and MMC devices.
+foreach my $path (glob "/sys/class/{block,mmc_host}/*") {
+ my $module;
+ if (-e "$path/device/driver/module") {
+ $module = basename `readlink -f $path/device/driver/module`;
+ chomp $module;
+ push @initrdAvailableKernelModules, $module;
+ }
+}
+
+# Add bcache module, if needed.
+my @bcacheDevices = glob("/dev/bcache*");
+if (scalar @bcacheDevices > 0) {
+ push @initrdAvailableKernelModules, "bcache";
+}
+
+# Prevent unbootable systems if LVM snapshots are present at boot time.
+if (`lsblk -o TYPE` =~ "lvm") {
+ push @initrdKernelModules, "dm-snapshot";
+}
+
+my $virt = `systemd-detect-virt`;
+chomp $virt;
+
+
+# Check if we're a VirtualBox guest. If so, enable the guest
+# additions.
+if ($virt eq "oracle") {
+ push @attrs, "virtualisation.virtualbox.guest.enable = true;"
+}
+
+
+# Likewise for QEMU.
+if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") {
+ push @imports, "<nixpkgs/nixos/modules/profiles/qemu-guest.nix>";
+}
+
+# Also for Hyper-V.
+if ($virt eq "microsoft") {
+ push @attrs, "virtualisation.hypervGuest.enable = true;"
+}
+
+
+# Pull in NixOS configuration for containers.
+if ($virt eq "systemd-nspawn") {
+ push @attrs, "boot.isContainer = true;";
+}
+
+
+# Provide firmware for devices that are not detected by this script,
+# unless we're in a VM/container.
+push @imports, "<nixpkgs/nixos/modules/installer/scan/not-detected.nix>"
+ if $virt eq "none";
+
+
+# For a device name like /dev/sda1, find a more stable path like
+# /dev/disk/by-uuid/X or /dev/disk/by-label/Y.
+sub findStableDevPath {
+ my ($dev) = @_;
+ return $dev if substr($dev, 0, 1) ne "/";
+ return $dev unless -e $dev;
+
+ my $st = stat($dev) or return $dev;
+
+ foreach my $dev2 (glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) {
+ my $st2 = stat($dev2) or next;
+ return $dev2 if $st->rdev == $st2->rdev;
+ }
+
+ return $dev;
+}
+
+push @attrs, "services.xserver.videoDrivers = [ \"$videoDriver\" ];" if $videoDriver;
+
+# Generate the swapDevices option from the currently activated swap
+# devices.
+my @swaps = read_file("/proc/swaps", err_mode => 'carp');
+my @swapDevices;
+if (@swaps) {
+ shift @swaps;
+ foreach my $swap (@swaps) {
+ my @fields = split ' ', $swap;
+ my $swapFilename = $fields[0];
+ my $swapType = $fields[1];
+ next unless -e $swapFilename;
+ my $dev = findStableDevPath $swapFilename;
+ if ($swapType =~ "partition") {
+ push @swapDevices, "{ device = \"$dev\"; }";
+ } elsif ($swapType =~ "file") {
+ # swap *files* are more likely specified in configuration.nix, so
+ # ignore them here.
+ } else {
+ die "Unsupported swap type: $swapType\n";
+ }
+ }
+}
+
+
+# Generate the fileSystems option from the currently mounted
+# filesystems.
+sub in {
+ my ($d1, $d2) = @_;
+ return $d1 eq $d2 || substr($d1, 0, length($d2) + 1) eq "$d2/";
+}
+
+my $fileSystems;
+my %fsByDev;
+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];
+
+ next if !in($mountPoint, $rootDir);
+ $mountPoint = substr($mountPoint, length($rootDir)); # strip the root directory (e.g. /mnt)
+ $mountPoint = "/" if $mountPoint eq "";
+
+ # Skip special filesystems.
+ next if in($mountPoint, "/proc") || in($mountPoint, "/dev") || in($mountPoint, "/sys") || in($mountPoint, "/run") || $mountPoint eq "/var/lib/nfs/rpc_pipefs";
+
+ # Skip the optional fields.
+ my $n = 6; $n++ while $fields[$n] ne "-"; $n++;
+ 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);
+
+ # Maybe this is a bind-mount of a filesystem we saw earlier?
+ if (defined $fsByDev{$fields[2]}) {
+ # Make sure this isn't a btrfs subvolume.
+ my $msg = `btrfs subvol show $rootDir$mountPoint`;
+ if ($? != 0 || $msg =~ /ERROR:/s) {
+ my $path = $fields[3]; $path = "" if $path eq "/";
+ my $base = $fsByDev{$fields[2]};
+ $base = "" if $base eq "/";
+ $fileSystems .= <<EOF;
+ fileSystems.\"$mountPoint\" =
+ { device = \"$base$path\";
+ fsType = \"none\";
+ options = \[ \"bind\" \];
+ };
+
+EOF
+ next;
+ }
+ }
+ $fsByDev{$fields[2]} = $mountPoint;
+
+ # We don't know how to handle FUSE filesystems.
+ if ($fsType eq "fuseblk" || $fsType eq "fuse") {
+ print STDERR "warning: don't know how to emit ‘fileSystem’ option for FUSE filesystem ‘$mountPoint’\n";
+ next;
+ }
+
+ # Is this a mount of a loopback device?
+ my @extraOptions;
+ if ($device =~ /\/dev\/loop(\d+)/) {
+ my $loopnr = $1;
+ my $backer = read_file "/sys/block/loop$loopnr/loop/backing_file";
+ if (defined $backer) {
+ chomp $backer;
+ $device = $backer;
+ push @extraOptions, "loop";
+ }
+ }
+
+ # Is this a btrfs filesystem?
+ if ($fsType eq "btrfs") {
+ my ($status, @info) = runCommand("btrfs subvol show $rootDir$mountPoint");
+ if ($status != 0 || join("", @info) =~ /ERROR:/) {
+ die "Failed to retrieve subvolume info for $mountPoint\n";
+ }
+ my @ids = join("\n", @info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
+ if ($#ids > 0) {
+ die "Btrfs subvol name for $mountPoint listed multiple times in mount\n"
+ } elsif ($#ids == 0) {
+ my @paths = join("", @info) =~ m/^([^\n]*)/;
+ if ($#paths > 0) {
+ die "Btrfs returned multiple paths for a single subvolume id, mountpoint $mountPoint\n";
+ } elsif ($#paths != 0) {
+ die "Btrfs did not return a path for the subvolume at $mountPoint\n";
+ }
+ push @extraOptions, "subvol=$paths[0]";
+ }
+ }
+
+ # Don't emit tmpfs entry for /tmp, because it most likely comes from the
+ # boot.tmpOnTmpfs option in configuration.nix (managed declaratively).
+ next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs");
+
+ # Emit the filesystem.
+ $fileSystems .= <<EOF;
+ fileSystems.\"$mountPoint\" =
+ { device = \"${\(findStableDevPath $device)}\";
+ fsType = \"$fsType\";
+EOF
+
+ if (scalar @extraOptions > 0) {
+ $fileSystems .= <<EOF;
+ options = \[ ${\join " ", map { "\"" . $_ . "\"" } uniq(@extraOptions)} \];
+EOF
+ }
+
+ $fileSystems .= <<EOF;
+ };
+
+EOF
+
+ # If this filesystem is on a LUKS device, then add a
+ # boot.initrd.luks.devices entry.
+ if (-e $device) {
+ my $deviceName = basename(abs_path($device));
+ if (-e "/sys/class/block/$deviceName"
+ && read_file("/sys/class/block/$deviceName/dm/uuid", err_mode => 'quiet') =~ /^CRYPT-LUKS/)
+ {
+ my @slaves = glob("/sys/class/block/$deviceName/slaves/*");
+ if (scalar @slaves == 1) {
+ my $slave = "/dev/" . basename($slaves[0]);
+ if (-e $slave) {
+ my $dmName = read_file("/sys/class/block/$deviceName/dm/name");
+ chomp $dmName;
+ # 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";
+ }
+ }
+ }
+ }
+ }
+}
+
+# For lack of a better way to determine it, guess whether we should use a
+# bigger font for the console from the display mode on the first
+# framebuffer. A way based on the physical size/actual DPI reported by
+# the monitor would be nice, but I don't know how to do this without X :)
+my $fb_modes_file = "/sys/class/graphics/fb0/modes";
+if (-f $fb_modes_file && -r $fb_modes_file) {
+ my $modes = read_file($fb_modes_file);
+ $modes =~ m/([0-9]+)x([0-9]+)/;
+ my $console_width = $1, my $console_height = $2;
+ if ($console_width > 1920) {
+ push @attrs, "# High-DPI console";
+ push @attrs, 'i18n.consoleFont = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-u28n.psf.gz";';
+ }
+}
+
+
+# Generate the hardware configuration file.
+
+sub toNixStringList {
+ my $res = "";
+ foreach my $s (@_) {
+ $res .= " \"$s\"";
+ }
+ return $res;
+}
+sub toNixList {
+ my $res = "";
+ foreach my $s (@_) {
+ $res .= " $s";
+ }
+ return $res;
+}
+
+sub multiLineList {
+ my $indent = shift;
+ return " [ ]" if !@_;
+ my $res = "\n${indent}[ ";
+ my $first = 1;
+ foreach my $s (@_) {
+ $res .= "$indent " if !$first;
+ $first = 0;
+ $res .= "$s\n";
+ }
+ $res .= "$indent]";
+ return $res;
+}
+
+my $initrdAvailableKernelModules = toNixStringList(uniq @initrdAvailableKernelModules);
+my $initrdKernelModules = toNixStringList(uniq @initrdKernelModules);
+my $kernelModules = toNixStringList(uniq @kernelModules);
+my $modulePackages = toNixList(uniq @modulePackages);
+
+my $fsAndSwap = "";
+if (!$noFilesystems) {
+ $fsAndSwap = "\n$fileSystems ";
+ $fsAndSwap .= "swapDevices =" . multiLineList(" ", @swapDevices) . ";\n";
+}
+
+my $hwConfig = <<EOF;
+# Do not modify this file! It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations. Please make changes
+# to /etc/nixos/configuration.nix instead.
+{ config, lib, pkgs, ... }:
+
+{
+ imports =${\multiLineList(" ", @imports)};
+
+ boot.initrd.availableKernelModules = [$initrdAvailableKernelModules ];
+ boot.initrd.kernelModules = [$initrdKernelModules ];
+ boot.kernelModules = [$kernelModules ];
+ boot.extraModulePackages = [$modulePackages ];
+$fsAndSwap
+ nix.maxJobs = lib.mkDefault $cpus;
+${\join "", (map { " $_\n" } (uniq @attrs))}}
+EOF
+
+
+if ($showHardwareConfig) {
+ print STDOUT $hwConfig;
+} else {
+ $outDir = "$rootDir$outDir";
+
+ my $fn = "$outDir/hardware-configuration.nix";
+ print STDERR "writing $fn...\n";
+ mkpath($outDir, 0, 0755);
+ write_file($fn, $hwConfig);
+
+ # Generate a basic configuration.nix, unless one already exists.
+ $fn = "$outDir/configuration.nix";
+ if ($force || ! -e $fn) {
+ print STDERR "writing $fn...\n";
+
+ my $bootLoaderConfig = "";
+ if (-e "/sys/firmware/efi/efivars") {
+ $bootLoaderConfig = <<EOF;
+ # Use the systemd-boot EFI boot loader.
+ boot.loader.systemd-boot.enable = true;
+ boot.loader.efi.canTouchEfiVariables = true;
+EOF
+ } elsif (-e "/boot/extlinux") {
+ $bootLoaderConfig = <<EOF;
+ # Use the extlinux boot loader. (NixOS wants to enable GRUB by default)
+ boot.loader.grub.enable = false;
+ # Enables the generation of /boot/extlinux/extlinux.conf
+ boot.loader.generic-extlinux-compatible.enable = true;
+EOF
+ } elsif ($virt ne "systemd-nspawn") {
+ $bootLoaderConfig = <<EOF;
+ # Use the GRUB 2 boot loader.
+ boot.loader.grub.enable = true;
+ boot.loader.grub.version = 2;
+ # boot.loader.grub.efiSupport = true;
+ # boot.loader.grub.efiInstallAsRemovable = true;
+ # boot.loader.efi.efiSysMountPoint = "/boot/efi";
+ # Define on which hard drive you want to install Grub.
+ # boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only
+EOF
+ }
+
+ write_file($fn, <<EOF);
+@configuration@
+EOF
+ } else {
+ print STDERR "warning: not overwriting existing $fn\n";
+ }
+}
+
+# workaround for a bug in substituteAll
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-install.sh b/nixpkgs/nixos/modules/installer/tools/nixos-install.sh
new file mode 100644
index 00000000000..8685cb345e1
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-install.sh
@@ -0,0 +1,155 @@
+#! @shell@
+
+set -e
+shopt -s nullglob
+
+export PATH=@path@:$PATH
+
+# Ensure a consistent umask.
+umask 0022
+
+# Parse the command line for the -I flag
+extraBuildFlags=()
+
+mountPoint=/mnt
+channelPath=
+system=
+
+while [ "$#" -gt 0 ]; do
+ i="$1"; shift 1
+ case "$i" in
+ --max-jobs|-j|--cores|-I|--substituters)
+ j="$1"; shift 1
+ extraBuildFlags+=("$i" "$j")
+ ;;
+ --option)
+ j="$1"; shift 1
+ k="$1"; shift 1
+ extraBuildFlags+=("$i" "$j" "$k")
+ ;;
+ --root)
+ mountPoint="$1"; shift 1
+ ;;
+ --system|--closure)
+ system="$1"; shift 1
+ ;;
+ --channel)
+ channelPath="$1"; shift 1
+ ;;
+ --no-channel-copy)
+ noChannelCopy=1
+ ;;
+ --no-root-passwd)
+ noRootPasswd=1
+ ;;
+ --no-bootloader)
+ noBootLoader=1
+ ;;
+ --show-trace)
+ extraBuildFlags+=("$i")
+ ;;
+ --help)
+ exec man nixos-install
+ exit 1
+ ;;
+ --debug)
+ set -x
+ ;;
+ *)
+ echo "$0: unknown option \`$i'"
+ exit 1
+ ;;
+ esac
+done
+
+if ! test -e "$mountPoint"; then
+ echo "mount point $mountPoint doesn't exist"
+ exit 1
+fi
+
+# Get the path of the NixOS configuration file.
+if [[ -z $NIXOS_CONFIG ]]; then
+ NIXOS_CONFIG=$mountPoint/etc/nixos/configuration.nix
+fi
+
+if [[ ${NIXOS_CONFIG:0:1} != / ]]; then
+ echo "$0: \$NIXOS_CONFIG is not an absolute path"
+ exit 1
+fi
+
+if [[ ! -e $NIXOS_CONFIG && -z $system ]]; then
+ echo "configuration file $NIXOS_CONFIG doesn't exist"
+ exit 1
+fi
+
+# A place to drop temporary stuff.
+trap "rm -rf $tmpdir" EXIT
+tmpdir="$(mktemp -d)"
+
+sub="auto?trusted=1"
+
+# Build the system configuration in the target filesystem.
+if [[ -z $system ]]; then
+ echo "building the configuration in $NIXOS_CONFIG..."
+ outLink="$tmpdir/system"
+ nix build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \
+ --extra-substituters "$sub" \
+ -f '<nixpkgs/nixos>' system -I "nixos-config=$NIXOS_CONFIG"
+ system=$(readlink -f $outLink)
+fi
+
+# Set the system profile to point to the configuration. TODO: combine
+# this with the previous step once we have a nix-env replacement with
+# a progress bar.
+nix-env --store "$mountPoint" "${extraBuildFlags[@]}" \
+ --extra-substituters "$sub" \
+ -p $mountPoint/nix/var/nix/profiles/system --set "$system"
+
+# Copy the NixOS/Nixpkgs sources to the target as the initial contents
+# of the NixOS channel.
+if [[ -z $noChannelCopy ]]; then
+ if [[ -z $channelPath ]]; then
+ channelPath="$(nix-env -p /nix/var/nix/profiles/per-user/root/channels -q nixos --no-name --out-path 2>/dev/null || echo -n "")"
+ fi
+ if [[ -n $channelPath ]]; then
+ echo "copying channel..."
+ mkdir -p $mountPoint/nix/var/nix/profiles/per-user/root
+ nix-env --store "$mountPoint" "${extraBuildFlags[@]}" --extra-substituters "$sub" \
+ -p $mountPoint/nix/var/nix/profiles/per-user/root/channels --set "$channelPath" --quiet
+ install -m 0700 -d $mountPoint/root/.nix-defexpr
+ ln -sfn /nix/var/nix/profiles/per-user/root/channels $mountPoint/root/.nix-defexpr/channels
+ fi
+fi
+
+# Mark the target as a NixOS installation, otherwise switch-to-configuration will chicken out.
+mkdir -m 0755 -p "$mountPoint/etc"
+touch "$mountPoint/etc/NIXOS"
+
+# Switch to the new system configuration. This will install Grub with
+# a menu default pointing at the kernel/initrd/etc of the new
+# configuration.
+if [[ -z $noBootLoader ]]; then
+ echo "installing the boot loader..."
+ # Grub needs an mtab.
+ ln -sfn /proc/mounts $mountPoint/etc/mtab
+ NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root "$mountPoint" -- /run/current-system/bin/switch-to-configuration boot
+fi
+
+# Ask the user to set a root password, but only if the passwd command
+# exists (i.e. when mutable user accounts are enabled).
+if [[ -z $noRootPasswd ]] && [ -t 0 ]; then
+ if nixos-enter --root "$mountPoint" -c 'test -e /nix/var/nix/profiles/system/sw/bin/passwd'; then
+ set +e
+ nixos-enter --root "$mountPoint" -c 'echo "setting root password..." && /nix/var/nix/profiles/system/sw/bin/passwd'
+ exit_code=$?
+ set -e
+
+ if [[ $exit_code != 0 ]]; then
+ echo "Setting a root password failed with the above printed error."
+ echo "You can set the root password manually by executing \`nixos-enter --root ${mountPoint@Q}\` and then running \`passwd\` in the shell of the new system."
+ exit $exit_code
+ fi
+ fi
+fi
+
+echo "installation finished!"
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-option.sh b/nixpkgs/nixos/modules/installer/tools/nixos-option.sh
new file mode 100644
index 00000000000..4560e9c7403
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-option.sh
@@ -0,0 +1,327 @@
+#! @shell@ -e
+
+# FIXME: rewrite this in a more suitable language.
+
+usage () {
+ exec man nixos-option
+ exit 1
+}
+
+#####################
+# Process Arguments #
+#####################
+
+xml=false
+verbose=false
+nixPath=""
+
+option=""
+exit_code=0
+
+argfun=""
+for arg; do
+ if test -z "$argfun"; then
+ case $arg in
+ -*)
+ sarg="$arg"
+ longarg=""
+ while test "$sarg" != "-"; do
+ case $sarg in
+ --*) longarg=$arg; sarg="--";;
+ -I) argfun="include_nixpath";;
+ -*) usage;;
+ esac
+ # remove the first letter option
+ sarg="-${sarg#??}"
+ done
+ ;;
+ *) longarg=$arg;;
+ esac
+ for larg in $longarg; do
+ case $larg in
+ --xml) xml=true;;
+ --verbose) verbose=true;;
+ --help) usage;;
+ -*) usage;;
+ *) if test -z "$option"; then
+ option="$larg"
+ else
+ usage
+ fi;;
+ esac
+ done
+ else
+ case $argfun in
+ set_*)
+ var=$(echo $argfun | sed 's,^set_,,')
+ eval $var=$arg
+ ;;
+ include_nixpath)
+ nixPath="-I $arg $nixPath"
+ ;;
+ esac
+ argfun=""
+ fi
+done
+
+if $verbose; then
+ set -x
+else
+ set +x
+fi
+
+#############################
+# Process the configuration #
+#############################
+
+evalNix(){
+ # disable `-e` flag, it's possible that the evaluation of `nix-instantiate` fails (e.g. due to broken pkgs)
+ set +e
+ result=$(nix-instantiate ${nixPath:+$nixPath} - --eval-only "$@" 2>&1)
+ exit_code=$?
+ set -e
+
+ if test $exit_code -eq 0; then
+ sed '/^warning: Nix search path/d' <<EOF
+$result
+EOF
+ return 0;
+ else
+ sed -n '
+ /^error/ { s/, at (string):[0-9]*:[0-9]*//; p; };
+ /^warning: Nix search path/ { p; };
+' >&2 <<EOF
+$result
+EOF
+ exit_code=1
+ fi
+}
+
+header="let
+ nixos = import <nixpkgs/nixos> {};
+ nixpkgs = import <nixpkgs> {};
+in with nixpkgs.lib;
+"
+
+# This function is used for converting the option definition path given by
+# the user into accessors for reaching the definition and the declaration
+# corresponding to this option.
+generateAccessors(){
+ if result=$(evalNix --strict --show-trace <<EOF
+$header
+
+let
+ path = "${option:+$option}";
+ pathList = splitString "." path;
+
+ walkOptions = attrsNames: result:
+ if attrsNames == [] then
+ result
+ else
+ let name = head attrsNames; rest = tail attrsNames; in
+ if isOption result.options then
+ walkOptions rest {
+ options = result.options.type.getSubOptions "";
+ opt = ''(\${result.opt}.type.getSubOptions "")'';
+ cfg = ''\${result.cfg}."\${name}"'';
+ }
+ else
+ walkOptions rest {
+ options = result.options.\${name};
+ opt = ''\${result.opt}."\${name}"'';
+ cfg = ''\${result.cfg}."\${name}"'';
+ }
+ ;
+
+ walkResult = (if path == "" then x: x else walkOptions pathList) {
+ options = nixos.options;
+ opt = ''nixos.options'';
+ cfg = ''nixos.config'';
+ };
+
+in
+ ''let option = \${walkResult.opt}; config = \${walkResult.cfg}; in''
+EOF
+)
+ then
+ echo $result
+ else
+ # In case of error we want to ignore the error message roduced by the
+ # script above, as it is iterating over each attribute, which does not
+ # produce a nice error message. The following code is a fallback
+ # solution which is cause a nicer error message in the next
+ # evaluation.
+ echo "\"let option = nixos.options${option:+.$option}; config = nixos.config${option:+.$option}; in\""
+ fi
+}
+
+header="$header
+$(eval echo $(generateAccessors))
+"
+
+evalAttr(){
+ local prefix="$1"
+ local strict="$2"
+ local suffix="$3"
+
+ # If strict is set, then set it to "true".
+ test -n "$strict" && strict=true
+
+ evalNix ${strict:+--strict} <<EOF
+$header
+
+let
+ value = $prefix${suffix:+.$suffix};
+ strict = ${strict:-false};
+ cleanOutput = x: with nixpkgs.lib;
+ if isDerivation x then x.outPath
+ else if isFunction x then "<CODE>"
+ else if strict then
+ if isAttrs x then mapAttrs (n: cleanOutput) x
+ else if isList x then map cleanOutput x
+ else x
+ else x;
+in
+ cleanOutput value
+EOF
+}
+
+evalOpt(){
+ evalAttr "option" "" "$@"
+}
+
+evalCfg(){
+ local strict="$1"
+ evalAttr "config" "$strict"
+}
+
+findSources(){
+ local suffix=$1
+ evalNix --strict <<EOF
+$header
+
+option.$suffix
+EOF
+}
+
+# Given a result from nix-instantiate, recover the list of attributes it
+# contains.
+attrNames() {
+ local attributeset=$1
+ # sed is used to replace un-printable subset by 0s, and to remove most of
+ # the inner-attribute set, which reduce the likelyhood to encounter badly
+ # pre-processed input.
+ echo "builtins.attrNames $attributeset" | \
+ sed 's,<[A-Z]*>,0,g; :inner; s/{[^\{\}]*};/0;/g; t inner;' | \
+ evalNix --strict
+}
+
+# map a simple list which contains strings or paths.
+nixMap() {
+ local fun="$1"
+ local list="$2"
+ local elem
+ for elem in $list; do
+ test $elem = '[' -o $elem = ']' && continue;
+ $fun $elem
+ done
+}
+
+# This duplicates the work made below, but it is useful for processing
+# the output of nixos-option with other tools such as nixos-gui.
+if $xml; then
+ evalNix --xml --no-location <<EOF
+$header
+
+let
+ sources = builtins.map (f: f.source);
+ opt = option;
+ cfg = config;
+in
+
+with nixpkgs.lib;
+
+let
+ optStrict = v:
+ let
+ traverse = x :
+ if isAttrs x then
+ if x ? outPath then true
+ else all id (mapAttrsFlatten (n: traverseNoAttrs) x)
+ else traverseNoAttrs x;
+ traverseNoAttrs = x:
+ # do not continue in attribute sets
+ if isAttrs x then true
+ else if isList x then all id (map traverse x)
+ else true;
+ in assert traverse v; v;
+in
+
+if isOption opt then
+ optStrict ({}
+ // optionalAttrs (opt ? default) { inherit (opt) default; }
+ // optionalAttrs (opt ? example) { inherit (opt) example; }
+ // optionalAttrs (opt ? description) { inherit (opt) description; }
+ // optionalAttrs (opt ? type) { typename = opt.type.description; }
+ // optionalAttrs (opt ? options) { inherit (opt) options; }
+ // {
+ # to disambiguate the xml output.
+ _isOption = true;
+ declarations = sources opt.declarations;
+ definitions = sources opt.definitions;
+ value = cfg;
+ })
+else
+ opt
+EOF
+ exit $?
+fi
+
+if test "$(evalOpt "_type" 2> /dev/null)" = '"option"'; then
+ echo "Value:"
+ evalCfg 1
+
+ echo
+
+ echo "Default:"
+ if default=$(evalOpt "default" - 2> /dev/null); then
+ echo "$default"
+ else
+ echo "<None>"
+ fi
+ echo
+ if example=$(evalOpt "example" - 2> /dev/null); then
+ echo "Example:"
+ echo "$example"
+ echo
+ fi
+ echo "Description:"
+ echo
+ echo $(evalOpt "description")
+
+ echo $desc;
+
+ printPath () { echo " $1"; }
+
+ echo "Declared by:"
+ nixMap printPath "$(findSources "declarations")"
+ echo
+ echo "Defined by:"
+ nixMap printPath "$(findSources "files")"
+ echo
+
+else
+ # echo 1>&2 "Warning: This value is not an option."
+
+ result=$(evalCfg "")
+ if [ ! -z "$result" ]; then
+ names=$(attrNames "$result" 2> /dev/null)
+ echo 1>&2 "This attribute set contains:"
+ escapeQuotes () { eval echo "$1"; }
+ nixMap escapeQuotes "$names"
+ else
+ echo 1>&2 "An error occurred while looking for attribute names. Are you sure that '$option' exists?"
+ fi
+fi
+
+exit $exit_code
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-rebuild.sh b/nixpkgs/nixos/modules/installer/tools/nixos-rebuild.sh
new file mode 100644
index 00000000000..6a08c9b4c6c
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-rebuild.sh
@@ -0,0 +1,404 @@
+#! @shell@
+
+if [ -x "@shell@" ]; then export SHELL="@shell@"; fi;
+
+set -e
+
+showSyntax() {
+ exec man nixos-rebuild
+ exit 1
+}
+
+
+# Parse the command line.
+origArgs=("$@")
+extraBuildFlags=()
+action=
+buildNix=1
+fast=
+rollback=
+upgrade=
+repair=
+profile=/nix/var/nix/profiles/system
+buildHost=
+targetHost=
+
+while [ "$#" -gt 0 ]; do
+ i="$1"; shift 1
+ case "$i" in
+ --help)
+ showSyntax
+ ;;
+ switch|boot|test|build|edit|dry-build|dry-run|dry-activate|build-vm|build-vm-with-bootloader)
+ if [ "$i" = dry-run ]; then i=dry-build; fi
+ action="$i"
+ ;;
+ --install-grub)
+ echo "$0: --install-grub deprecated, use --install-bootloader instead" >&2
+ export NIXOS_INSTALL_BOOTLOADER=1
+ ;;
+ --install-bootloader)
+ export NIXOS_INSTALL_BOOTLOADER=1
+ ;;
+ --no-build-nix)
+ buildNix=
+ ;;
+ --rollback)
+ rollback=1
+ ;;
+ --upgrade)
+ upgrade=1
+ ;;
+ --repair)
+ repair=1
+ extraBuildFlags+=("$i")
+ ;;
+ --max-jobs|-j|--cores|-I|--builders)
+ j="$1"; shift 1
+ extraBuildFlags+=("$i" "$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)
+ j="$1"; shift 1
+ k="$1"; shift 1
+ extraBuildFlags+=("$i" "$j" "$k")
+ ;;
+ --fast)
+ buildNix=
+ fast=1
+ extraBuildFlags+=(--show-trace)
+ ;;
+ --profile-name|-p)
+ if [ -z "$1" ]; then
+ echo "$0: ‘--profile-name’ requires an argument"
+ exit 1
+ fi
+ if [ "$1" != system ]; then
+ profile="/nix/var/nix/profiles/system-profiles/$1"
+ mkdir -p -m 0755 "$(dirname "$profile")"
+ fi
+ shift 1
+ ;;
+ --build-host|h)
+ buildHost="$1"
+ shift 1
+ ;;
+ --target-host|t)
+ targetHost="$1"
+ shift 1
+ ;;
+ *)
+ echo "$0: unknown option \`$i'"
+ exit 1
+ ;;
+ esac
+done
+
+
+if [ -z "$buildHost" -a -n "$targetHost" ]; then
+ buildHost="$targetHost"
+fi
+if [ "$targetHost" = localhost ]; then
+ targetHost=
+fi
+if [ "$buildHost" = localhost ]; then
+ buildHost=
+fi
+
+buildHostCmd() {
+ if [ -z "$buildHost" ]; then
+ "$@"
+ elif [ -n "$remoteNix" ]; then
+ ssh $SSHOPTS "$buildHost" PATH="$remoteNix:$PATH" "$@"
+ else
+ ssh $SSHOPTS "$buildHost" "$@"
+ fi
+}
+
+targetHostCmd() {
+ if [ -z "$targetHost" ]; then
+ "$@"
+ else
+ ssh $SSHOPTS "$targetHost" "$@"
+ fi
+}
+
+copyToTarget() {
+ if ! [ "$targetHost" = "$buildHost" ]; then
+ if [ -z "$targetHost" ]; then
+ NIX_SSHOPTS=$SSHOPTS nix-copy-closure --from "$buildHost" "$1"
+ elif [ -z "$buildHost" ]; then
+ NIX_SSHOPTS=$SSHOPTS nix-copy-closure --to "$targetHost" "$1"
+ else
+ buildHostCmd nix-copy-closure --to "$targetHost" "$1"
+ fi
+ fi
+}
+
+nixBuild() {
+ if [ -z "$buildHost" ]; then
+ nix-build "$@"
+ else
+ local instArgs=()
+ local buildArgs=()
+
+ while [ "$#" -gt 0 ]; do
+ local i="$1"; shift 1
+ case "$i" in
+ -o)
+ local out="$1"; shift 1
+ buildArgs+=("--add-root" "$out" "--indirect")
+ ;;
+ -A)
+ local j="$1"; shift 1
+ instArgs+=("$i" "$j")
+ ;;
+ -I) # We don't want this in buildArgs
+ shift 1
+ ;;
+ --no-out-link) # We don't want this in buildArgs
+ ;;
+ "<"*) # nix paths
+ instArgs+=("$i")
+ ;;
+ *)
+ buildArgs+=("$i")
+ ;;
+ esac
+ done
+
+ local drv="$(nix-instantiate "${instArgs[@]}" "${extraBuildFlags[@]}")"
+ if [ -a "$drv" ]; then
+ NIX_SSHOPTS=$SSHOPTS nix-copy-closure --to "$buildHost" "$drv"
+ buildHostCmd nix-store -r "$drv" "${buildArgs[@]}"
+ else
+ echo "nix-instantiate failed"
+ exit 1
+ fi
+ fi
+}
+
+
+if [ -z "$action" ]; then showSyntax; fi
+
+# Only run shell scripts from the Nixpkgs tree if the action is
+# "switch", "boot", or "test". With other actions (such as "build"),
+# the user may reasonably expect that no code from the Nixpkgs tree is
+# executed, so it's safe to run nixos-rebuild against a potentially
+# untrusted tree.
+canRun=
+if [ "$action" = switch -o "$action" = boot -o "$action" = test ]; then
+ canRun=1
+fi
+
+
+# If ‘--upgrade’ is given, run ‘nix-channel --update nixos’.
+if [ -n "$upgrade" -a -z "$_NIXOS_REBUILD_REEXEC" ]; then
+ nix-channel --update nixos
+
+ # If there are other channels that contain a file called
+ # ".update-on-nixos-rebuild", update them as well.
+ for channelpath in /nix/var/nix/profiles/per-user/root/channels/*; do
+ if [ -e "$channelpath/.update-on-nixos-rebuild" ]; then
+ nix-channel --update "$(basename "$channelpath")"
+ fi
+ done
+fi
+
+# Make sure that we use the Nix package we depend on, not something
+# else from the PATH for nix-{env,instantiate,build}. This is
+# important, because NixOS defaults the architecture of the rebuilt
+# system to the architecture of the nix-* binaries used. So if on an
+# amd64 system the user has an i686 Nix package in her PATH, then we
+# would silently downgrade the whole system to be i686 NixOS on the
+# next reboot.
+if [ -z "$_NIXOS_REBUILD_REEXEC" ]; then
+ export PATH=@nix@/bin:$PATH
+fi
+
+# Re-execute nixos-rebuild from the Nixpkgs tree.
+if [ -z "$_NIXOS_REBUILD_REEXEC" -a -n "$canRun" -a -z "$fast" ]; then
+ if p=$(nix-build --no-out-link --expr 'with import <nixpkgs/nixos> {}; config.system.build.nixos-rebuild' "${extraBuildFlags[@]}"); then
+ export _NIXOS_REBUILD_REEXEC=1
+ exec $p/bin/nixos-rebuild "${origArgs[@]}"
+ exit 1
+ fi
+fi
+
+# Find configuration.nix and open editor instead of building.
+if [ "$action" = edit ]; then
+ NIXOS_CONFIG=${NIXOS_CONFIG:-$(nix-instantiate --find-file nixos-config)}
+ exec "${EDITOR:-nano}" "$NIXOS_CONFIG"
+ exit 1
+fi
+
+
+tmpDir=$(mktemp -t -d nixos-rebuild.XXXXXX)
+SSHOPTS="$NIX_SSHOPTS -o ControlMaster=auto -o ControlPath=$tmpDir/ssh-%n -o ControlPersist=60"
+
+cleanup() {
+ for ctrl in "$tmpDir"/ssh-*; do
+ ssh -o ControlPath="$ctrl" -O exit dummyhost 2>/dev/null || true
+ done
+ rm -rf "$tmpDir"
+}
+trap cleanup EXIT
+
+
+
+# If the Nix daemon is running, then use it. This allows us to use
+# the latest Nix from Nixpkgs (below) for expression evaluation, while
+# still using the old Nix (via the daemon) for actual store access.
+# This matters if the new Nix in Nixpkgs has a schema change. It
+# would upgrade the schema, which should only happen once we actually
+# switch to the new configuration.
+# If --repair is given, don't try to use the Nix daemon, because the
+# flag can only be used directly.
+if [ -z "$repair" ] && systemctl show nix-daemon.socket nix-daemon.service | grep -q ActiveState=active; then
+ export NIX_REMOTE=${NIX_REMOTE-daemon}
+fi
+
+
+# First build Nix, since NixOS may require a newer version than the
+# current one.
+if [ -n "$rollback" -o "$action" = dry-build ]; then
+ buildNix=
+fi
+
+nixSystem() {
+ machine="$(uname -m)"
+ if [[ "$machine" =~ i.86 ]]; then
+ machine=i686
+ fi
+ echo $machine-linux
+}
+
+prebuiltNix() {
+ machine="$1"
+ if [ "$machine" = x86_64 ]; then
+ echo @nix_x86_64_linux@
+ elif [[ "$machine" =~ i.86 ]]; then
+ echo @nix_i686_linux@
+ else
+ echo "$0: unsupported platform"
+ exit 1
+ fi
+}
+
+remotePATH=
+
+if [ -n "$buildNix" ]; then
+ echo "building Nix..." >&2
+ nixDrv=
+ if ! nixDrv="$(nix-instantiate '<nixpkgs/nixos>' --add-root $tmpDir/nix.drv --indirect -A config.nix.package.out "${extraBuildFlags[@]}")"; then
+ if ! nixDrv="$(nix-instantiate '<nixpkgs>' --add-root $tmpDir/nix.drv --indirect -A nix "${extraBuildFlags[@]}")"; then
+ if ! nixStorePath="$(nix-instantiate --eval '<nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix>' -A $(nixSystem) | sed -e 's/^"//' -e 's/"$//')"; then
+ nixStorePath="$(prebuiltNix "$(uname -m)")"
+ fi
+ if ! nix-store -r $nixStorePath --add-root $tmpDir/nix --indirect \
+ --option extra-binary-caches https://cache.nixos.org/; then
+ echo "warning: don't know how to get latest Nix" >&2
+ fi
+ # Older version of nix-store -r don't support --add-root.
+ [ -e $tmpDir/nix ] || ln -sf $nixStorePath $tmpDir/nix
+ if [ -n "$buildHost" ]; then
+ remoteNixStorePath="$(prebuiltNix "$(buildHostCmd uname -m)")"
+ remoteNix="$remoteNixStorePath/bin"
+ if ! buildHostCmd nix-store -r $remoteNixStorePath \
+ --option extra-binary-caches https://cache.nixos.org/ >/dev/null; then
+ remoteNix=
+ echo "warning: don't know how to get latest Nix" >&2
+ fi
+ fi
+ fi
+ fi
+ if [ -a "$nixDrv" ]; then
+ nix-store -r "$nixDrv"'!'"out" --add-root $tmpDir/nix --indirect >/dev/null
+ if [ -n "$buildHost" ]; then
+ nix-copy-closure --to "$buildHost" "$nixDrv"
+ # The nix build produces multiple outputs, we add them all to the remote path
+ for p in $(buildHostCmd nix-store -r "$(readlink "$nixDrv")" "${buildArgs[@]}"); do
+ remoteNix="$remoteNix${remoteNix:+:}$p/bin"
+ done
+ fi
+ fi
+ PATH="$tmpDir/nix/bin:$PATH"
+fi
+
+
+# Update the version suffix if we're building from Git (so that
+# nixos-version shows something useful).
+if [ -n "$canRun" ]; then
+ if nixpkgs=$(nix-instantiate --find-file nixpkgs "${extraBuildFlags[@]}"); then
+ suffix=$($SHELL $nixpkgs/nixos/modules/installer/tools/get-version-suffix "${extraBuildFlags[@]}" || true)
+ if [ -n "$suffix" ]; then
+ echo -n "$suffix" > "$nixpkgs/.version-suffix" || true
+ fi
+ fi
+fi
+
+
+if [ "$action" = dry-build ]; then
+ extraBuildFlags+=(--dry-run)
+fi
+
+
+# Either upgrade the configuration in the system profile (for "switch"
+# or "boot"), or just build it and create a symlink "result" in the
+# current directory (for "build" and "test").
+if [ -z "$rollback" ]; then
+ echo "building the system configuration..." >&2
+ if [ "$action" = switch -o "$action" = boot ]; then
+ pathToConfig="$(nixBuild '<nixpkgs/nixos>' --no-out-link -A system "${extraBuildFlags[@]}")"
+ copyToTarget "$pathToConfig"
+ targetHostCmd nix-env -p "$profile" --set "$pathToConfig"
+ elif [ "$action" = test -o "$action" = build -o "$action" = dry-build -o "$action" = dry-activate ]; then
+ pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A system -k "${extraBuildFlags[@]}")"
+ elif [ "$action" = build-vm ]; then
+ pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A vm -k "${extraBuildFlags[@]}")"
+ elif [ "$action" = build-vm-with-bootloader ]; then
+ pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A vmWithBootLoader -k "${extraBuildFlags[@]}")"
+ else
+ showSyntax
+ fi
+ # Copy build to target host if we haven't already done it
+ if ! [ "$action" = switch -o "$action" = boot ]; then
+ copyToTarget "$pathToConfig"
+ fi
+else # [ -n "$rollback" ]
+ if [ "$action" = switch -o "$action" = boot ]; then
+ targetHostCmd nix-env --rollback -p "$profile"
+ pathToConfig="$profile"
+ elif [ "$action" = test -o "$action" = build ]; then
+ systemNumber=$(
+ targetHostCmd nix-env -p "$profile" --list-generations |
+ sed -n '/current/ {g; p;}; s/ *\([0-9]*\).*/\1/; h'
+ )
+ pathToConfig="$profile"-${systemNumber}-link
+ if [ -z "$targetHost" ]; then
+ ln -sT "$pathToConfig" ./result
+ fi
+ else
+ showSyntax
+ fi
+fi
+
+
+# If we're not just building, then make the new configuration the boot
+# default and/or activate it now.
+if [ "$action" = switch -o "$action" = boot -o "$action" = test -o "$action" = dry-activate ]; then
+ if ! targetHostCmd $pathToConfig/bin/switch-to-configuration "$action"; then
+ echo "warning: error(s) occurred while switching to the new configuration" >&2
+ exit 1
+ fi
+fi
+
+
+if [ "$action" = build-vm ]; then
+ cat >&2 <<EOF
+
+Done. The virtual machine can be started by running $(echo $pathToConfig/bin/run-*-vm)
+EOF
+fi
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-version.sh b/nixpkgs/nixos/modules/installer/tools/nixos-version.sh
new file mode 100644
index 00000000000..190c49a33ec
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-version.sh
@@ -0,0 +1,14 @@
+#! @shell@
+
+case "$1" in
+ -h|--help)
+ exec man nixos-version
+ exit 1
+ ;;
+ --hash|--revision)
+ echo "@revision@"
+ ;;
+ *)
+ echo "@version@ (@codeName@)"
+ ;;
+esac
diff --git a/nixpkgs/nixos/modules/installer/tools/tools.nix b/nixpkgs/nixos/modules/installer/tools/tools.nix
new file mode 100644
index 00000000000..05add59117d
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/tools/tools.nix
@@ -0,0 +1,185 @@
+# This module generates nixos-install, nixos-rebuild,
+# nixos-generate-config, etc.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ makeProg = args: pkgs.substituteAll (args // {
+ dir = "bin";
+ isExecutable = true;
+ });
+
+ nixos-build-vms = makeProg {
+ name = "nixos-build-vms";
+ src = ./nixos-build-vms/nixos-build-vms.sh;
+ };
+
+ nixos-install = makeProg {
+ name = "nixos-install";
+ src = ./nixos-install.sh;
+ nix = config.nix.package.out;
+ path = makeBinPath [ nixos-enter ];
+ };
+
+ nixos-rebuild =
+ let fallback = import ./nix-fallback-paths.nix; in
+ makeProg {
+ name = "nixos-rebuild";
+ src = ./nixos-rebuild.sh;
+ nix = config.nix.package.out;
+ nix_x86_64_linux = fallback.x86_64-linux;
+ nix_i686_linux = fallback.i686-linux;
+ };
+
+ nixos-generate-config = makeProg {
+ name = "nixos-generate-config";
+ src = ./nixos-generate-config.pl;
+ path = lib.optionals (lib.elem "btrfs" config.boot.supportedFilesystems) [ pkgs.btrfs-progs ];
+ perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix}";
+ inherit (config.system.nixos-generate-config) configuration;
+ };
+
+ nixos-option = makeProg {
+ name = "nixos-option";
+ src = ./nixos-option.sh;
+ };
+
+ nixos-version = makeProg {
+ name = "nixos-version";
+ src = ./nixos-version.sh;
+ inherit (config.system.nixos) version codeName revision;
+ };
+
+ nixos-enter = makeProg {
+ name = "nixos-enter";
+ src = ./nixos-enter.sh;
+ };
+
+in
+
+{
+
+ options.system.nixos-generate-config.configuration = mkOption {
+ internal = true;
+ type = types.str;
+ description = ''
+ The NixOS module that <literal>nixos-generate-config</literal>
+ saves to <literal>/etc/nixos/configuration.nix</literal>.
+
+ This is an internal option. No backward compatibility is guaranteed.
+ Use at your own risk!
+
+ Note that this string gets spliced into a Perl script. The perl
+ variable <literal>$bootLoaderConfig</literal> can be used to
+ splice in the boot loader configuration.
+ '';
+ };
+
+ config = {
+
+ system.nixos-generate-config.configuration = mkDefault ''
+ # Edit this configuration file to define what should be installed on
+ # your system. Help is available in the configuration.nix(5) man page
+ # and in the NixOS manual (accessible by running ‘nixos-help’).
+
+ { config, pkgs, ... }:
+
+ {
+ imports =
+ [ # Include the results of the hardware scan.
+ ./hardware-configuration.nix
+ ];
+
+ $bootLoaderConfig
+ # networking.hostName = "nixos"; # Define your hostname.
+ # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
+
+ # Configure network proxy if necessary
+ # networking.proxy.default = "http://user:password\@proxy:port/";
+ # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
+
+ # Select internationalisation properties.
+ # i18n = {
+ # consoleFont = "Lat2-Terminus16";
+ # consoleKeyMap = "us";
+ # defaultLocale = "en_US.UTF-8";
+ # };
+
+ # Set your time zone.
+ # time.timeZone = "Europe/Amsterdam";
+
+ # List packages installed in system profile. To search, run:
+ # \$ nix search wget
+ # environment.systemPackages = with pkgs; [
+ # wget vim
+ # ];
+
+ # Some programs need SUID wrappers, can be configured further or are
+ # started in user sessions.
+ # programs.mtr.enable = true;
+ # programs.gnupg.agent = { enable = true; enableSSHSupport = true; };
+
+ # List services that you want to enable:
+
+ # Enable the OpenSSH daemon.
+ # services.openssh.enable = true;
+
+ # Open ports in the firewall.
+ # networking.firewall.allowedTCPPorts = [ ... ];
+ # networking.firewall.allowedUDPPorts = [ ... ];
+ # Or disable the firewall altogether.
+ # networking.firewall.enable = false;
+
+ # Enable CUPS to print documents.
+ # services.printing.enable = true;
+
+ # Enable sound.
+ # sound.enable = true;
+ # hardware.pulseaudio.enable = true;
+
+ # Enable the X11 windowing system.
+ # services.xserver.enable = true;
+ # services.xserver.layout = "us";
+ # services.xserver.xkbOptions = "eurosign:e";
+
+ # Enable touchpad support.
+ # services.xserver.libinput.enable = true;
+
+ # Enable the KDE Desktop Environment.
+ # services.xserver.displayManager.sddm.enable = true;
+ # services.xserver.desktopManager.plasma5.enable = true;
+
+ # Define a user account. Don't forget to set a password with ‘passwd’.
+ # users.users.jane = {
+ # isNormalUser = true;
+ # extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
+ # };
+
+ # This value determines the NixOS release with which your system is to be
+ # compatible, in order to avoid breaking some software such as database
+ # servers. You should change this only after NixOS release notes say you
+ # should.
+ system.stateVersion = "${config.system.nixos.release}"; # Did you read the comment?
+
+ }
+ '';
+
+ environment.systemPackages =
+ [ nixos-build-vms
+ nixos-install
+ nixos-rebuild
+ nixos-generate-config
+ nixos-option
+ nixos-version
+ nixos-enter
+ ];
+
+ system.build = {
+ inherit nixos-install nixos-generate-config nixos-option nixos-rebuild nixos-enter;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/installer/virtualbox-demo.nix b/nixpkgs/nixos/modules/installer/virtualbox-demo.nix
new file mode 100644
index 00000000000..af3e1aecca7
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/virtualbox-demo.nix
@@ -0,0 +1,61 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ imports =
+ [ ../virtualisation/virtualbox-image.nix
+ ../installer/cd-dvd/channel.nix
+ ../profiles/demo.nix
+ ../profiles/clone-config.nix
+ ];
+
+ # FIXME: UUID detection is currently broken
+ boot.loader.grub.fsIdentifier = "provided";
+
+ # Allow mounting of shared folders.
+ users.users.demo.extraGroups = [ "vboxsf" ];
+
+ # Add some more video drivers to give X11 a shot at working in
+ # VMware and QEMU.
+ services.xserver.videoDrivers = mkOverride 40 [ "virtualbox" "vmware" "cirrus" "vesa" "modesetting" ];
+
+ 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;
+ '';
+}
diff --git a/nixpkgs/nixos/modules/misc/assertions.nix b/nixpkgs/nixos/modules/misc/assertions.nix
new file mode 100644
index 00000000000..550b3ac97f6
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/assertions.nix
@@ -0,0 +1,34 @@
+{ lib, ... }:
+
+with lib;
+
+{
+
+ options = {
+
+ assertions = mkOption {
+ type = types.listOf types.unspecified;
+ internal = true;
+ default = [];
+ example = [ { assertion = false; message = "you can't enable this for that reason"; } ];
+ description = ''
+ This option allows modules to express conditions that must
+ hold for the evaluation of the system configuration to
+ succeed, along with associated error messages for the user.
+ '';
+ };
+
+ warnings = mkOption {
+ internal = true;
+ default = [];
+ type = types.listOf types.str;
+ example = [ "The `foo' service is deprecated and will go away soon!" ];
+ description = ''
+ This option allows modules to show warnings to users during
+ the evaluation of the system configuration.
+ '';
+ };
+
+ };
+ # impl of assertions is in <nixpkgs/nixos/modules/system/activation/top-level.nix>
+}
diff --git a/nixpkgs/nixos/modules/misc/crashdump.nix b/nixpkgs/nixos/modules/misc/crashdump.nix
new file mode 100644
index 00000000000..3c47e79d051
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/crashdump.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ crashdump = config.boot.crashDump;
+
+ kernelParams = concatStringsSep " " crashdump.kernelParams;
+
+in
+###### interface
+{
+ options = {
+ boot = {
+ crashDump = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, NixOS will set up a kernel that will
+ boot on crash, and leave the user in systemd rescue
+ to be able to save the crashed kernel dump at
+ /proc/vmcore.
+ It also activates the NMI watchdog.
+ '';
+ };
+ reservedMemory = mkOption {
+ default = "128M";
+ description = ''
+ The amount of memory reserved for the crashdump kernel.
+ If you choose a too high value, dmesg will mention
+ "crashkernel reservation failed".
+ '';
+ };
+ kernelParams = mkOption {
+ type = types.listOf types.str;
+ default = [ "1" "boot.shell_on_fail" ];
+ description = ''
+ Parameters that will be passed to the kernel kexec-ed on crash.
+ '';
+ };
+ };
+ };
+ };
+
+###### implementation
+
+ config = mkIf crashdump.enable {
+ boot = {
+ postBootCommands = ''
+ echo "loading crashdump kernel...";
+ ${pkgs.kexectools}/sbin/kexec -p /run/current-system/kernel \
+ --initrd=/run/current-system/initrd \
+ --reset-vga --console-vga \
+ --command-line="systemConfig=$(readlink -f /run/current-system) init=$(readlink -f /run/current-system/init) irqpoll maxcpus=1 reset_devices ${kernelParams}"
+ '';
+ kernelParams = [
+ "crashkernel=${crashdump.reservedMemory}"
+ "nmi_watchdog=panic"
+ "softlockup_panic=1"
+ ];
+ kernelPatches = [ {
+ name = "crashdump-config";
+ patch = null;
+ extraConfig = ''
+ CRASH_DUMP y
+ DEBUG_INFO y
+ PROC_VMCORE y
+ LOCKUP_DETECTOR y
+ HARDLOCKUP_DETECTOR y
+ '';
+ } ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/misc/documentation.nix b/nixpkgs/nixos/modules/misc/documentation.nix
new file mode 100644
index 00000000000..deecb005270
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/documentation.nix
@@ -0,0 +1,207 @@
+{ config, lib, pkgs, baseModules, extraModules, modules, ... }:
+
+with lib;
+
+let
+
+ cfg = config.documentation;
+
+ manualModules = baseModules ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
+
+ /* For the purpose of generating docs, evaluate options with each derivation
+ in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
+ It isn't perfect, but it seems to cover a vast majority of use cases.
+ Caveat: even if the package is reached by a different means,
+ the path above will be shown and not e.g. `${config.services.foo.package}`. */
+ manual = import ../../doc/manual rec {
+ inherit pkgs config;
+ version = config.system.nixos.release;
+ revision = "release-${version}";
+ options =
+ let
+ scrubbedEval = evalModules {
+ modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ manualModules;
+ 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="${pkgs.w3m-nographics}/bin/w3m"
+ 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
+
+{
+
+ options = {
+
+ documentation = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to install documentation of packages from
+ <option>environment.systemPackages</option> into the generated system path.
+
+ See "Multiple-output packages" chapter in the nixpkgs manual for more info.
+ '';
+ # which is at ../../../doc/multiple-output.xml
+ };
+
+ man.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to install manual pages and the <command>man</command> command.
+ This also includes "man" outputs.
+ '';
+ };
+
+ info.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to install info pages and the <command>info</command> command.
+ This also includes "info" outputs.
+ '';
+ };
+
+ doc.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to install documentation distributed in packages' <literal>/share/doc</literal>.
+ Usually plain text and/or HTML.
+ This also includes "doc" outputs.
+ '';
+ };
+
+ dev.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to install documentation targeted at developers.
+ <itemizedlist>
+ <listitem><para>This includes man pages targeted at developers if <option>man.enable</option> is
+ set (this also includes "devman" outputs).</para></listitem>
+ <listitem><para>This includes info pages targeted at developers if <option>info.enable</option>
+ is set (this also includes "devinfo" outputs).</para></listitem>
+ <listitem><para>This includes other pages targeted at developers if <option>doc.enable</option>
+ is set (this also includes "devdoc" outputs).</para></listitem>
+ </itemizedlist>
+ '';
+ };
+
+ 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>
+ '';
+ };
+
+ nixos.includeAllModules = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether the generated NixOS's documentation should include documentation for all
+ the options from all the NixOS modules included in the current
+ <literal>configuration.nix</literal>. Disabling this will make the manual
+ generator to ignore options defined outside of <literal>baseModules</literal>.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+
+ (mkIf cfg.man.enable {
+ 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 {
+ environment.systemPackages = [ pkgs.texinfoInteractive ];
+ environment.pathsToLink = [ "/share/info" ];
+ environment.extraOutputsToInstall = [ "info" ] ++ optional cfg.dev.enable "devinfo";
+ environment.extraSetup = ''
+ if [ -w $out/share/info ]; then
+ shopt -s nullglob
+ for i in $out/share/info/*.info $out/share/info/*.info.gz; do
+ ${pkgs.buildPackages.texinfo}/bin/install-info $i $out/share/info/dir
+ done
+ fi
+ '';
+ })
+
+ (mkIf cfg.doc.enable {
+ environment.pathsToLink = [ "/share/doc" ];
+ 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/nixpkgs/nixos/modules/misc/extra-arguments.nix b/nixpkgs/nixos/modules/misc/extra-arguments.nix
new file mode 100644
index 00000000000..8716e3d9fef
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/extra-arguments.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+{
+ _module.args = {
+ utils = import ../../lib/utils.nix pkgs;
+ };
+}
diff --git a/nixpkgs/nixos/modules/misc/ids.nix b/nixpkgs/nixos/modules/misc/ids.nix
new file mode 100644
index 00000000000..ac6af1ce8b7
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/ids.nix
@@ -0,0 +1,657 @@
+# This module defines the global list of uids and gids. We keep a
+# central list to prevent id collisions.
+
+# IMPORTANT!
+# We only add static uids and gids for services where it is not feasible
+# to change uids/gids on service start, in example a service with a lot of
+# files. Please also check if the service is applicable for systemd's
+# DynamicUser option and does not need a uid/gid allocation at all.
+# Systemd can also change ownership of service directories using the
+# RuntimeDirectory/StateDirectory options.
+
+{ lib, ... }:
+
+{
+ options = {
+
+ ids.uids = lib.mkOption {
+ internal = true;
+ description = ''
+ The user IDs used in NixOS.
+ '';
+ };
+
+ ids.gids = lib.mkOption {
+ internal = true;
+ description = ''
+ The group IDs used in NixOS.
+ '';
+ };
+
+ };
+
+
+ config = {
+
+ ids.uids = {
+ root = 0;
+ #wheel = 1; # unused
+ #kmem = 2; # unused
+ #tty = 3; # unused
+ messagebus = 4; # D-Bus
+ haldaemon = 5;
+ #disk = 6; # unused
+ vsftpd = 7;
+ ftp = 8;
+ bitlbee = 9;
+ #avahi = 10; # removed 2019-05-22
+ nagios = 11;
+ atd = 12;
+ postfix = 13;
+ #postdrop = 14; # unused
+ dovecot = 15;
+ tomcat = 16;
+ #audio = 17; # unused
+ #floppy = 18; # unused
+ uucp = 19;
+ #lp = 20; # unused
+ #proc = 21; # unused
+ pulseaudio = 22; # must match `pulseaudio' GID
+ gpsd = 23;
+ #cdrom = 24; # unused
+ #tape = 25; # unused
+ #video = 26; # unused
+ #dialout = 27; # unused
+ polkituser = 28;
+ #utmp = 29; # unused
+ # ddclient = 30; # converted to DynamicUser = true
+ davfs2 = 31;
+ #disnix = 33; # unused
+ osgi = 34;
+ tor = 35;
+ cups = 36;
+ foldingathome = 37;
+ sabnzbd = 38;
+ #kdm = 39; # dropped in 17.03
+ #ghostone = 40; # dropped in 18.03
+ git = 41;
+ fourstore = 42;
+ fourstorehttp = 43;
+ virtuoso = 44;
+ rtkit = 45;
+ dovecot2 = 46;
+ dovenull2 = 47;
+ prayer = 49;
+ mpd = 50;
+ clamav = 51;
+ fprot = 52;
+ bind = 53;
+ wwwrun = 54;
+ #adm = 55; # unused
+ spamd = 56;
+ #networkmanager = 57; # unused
+ nslcd = 58;
+ scanner = 59;
+ nginx = 60;
+ chrony = 61;
+ #systemd-journal = 62; # unused
+ smtpd = 63;
+ smtpq = 64;
+ supybot = 65;
+ iodined = 66;
+ #libvirtd = 67; # unused
+ graphite = 68;
+ #statsd = 69; # removed 2018-11-14
+ transmission = 70;
+ postgres = 71;
+ #vboxusers = 72; # unused
+ #vboxsf = 73; # unused
+ smbguest = 74; # unused
+ varnish = 75;
+ datadog = 76;
+ lighttpd = 77;
+ lightdm = 78;
+ freenet = 79;
+ ircd = 80;
+ bacula = 81;
+ #almir = 82; # removed 2018-03-25, the almir package was removed in 30291227f2411abaca097773eedb49b8f259e297 during 2017-08
+ deluge = 83;
+ mysql = 84;
+ rabbitmq = 85;
+ activemq = 86;
+ gnunet = 87;
+ oidentd = 88;
+ quassel = 89;
+ amule = 90;
+ minidlna = 91;
+ elasticsearch = 92;
+ tcpcryptd = 93; # tcpcryptd uses a hard-coded uid. We patch it in Nixpkgs to match this choice.
+ firebird = 95;
+ #keys = 96; # unused
+ haproxy = 97;
+ mongodb = 98;
+ openldap = 99;
+ #users = 100; # unused
+ cgminer = 101;
+ munin = 102;
+ logcheck = 103;
+ nix-ssh = 104;
+ dictd = 105;
+ couchdb = 106;
+ searx = 107;
+ kippo = 108;
+ jenkins = 109;
+ systemd-journal-gateway = 110;
+ #notbit = 111; # unused
+ aerospike = 111;
+ ngircd = 112;
+ #btsync = 113; # unused
+ minecraft = 114;
+ vault = 115;
+ rippled = 116;
+ murmur = 117;
+ foundationdb = 118;
+ newrelic = 119;
+ starbound = 120;
+ hydra = 122;
+ spiped = 123;
+ teamspeak = 124;
+ influxdb = 125;
+ nsd = 126;
+ gitolite = 127;
+ znc = 128;
+ polipo = 129;
+ mopidy = 130;
+ #docker = 131; # unused
+ gdm = 132;
+ dhcpd = 133;
+ siproxd = 134;
+ mlmmj = 135;
+ neo4j = 136;
+ riemann = 137;
+ riemanndash = 138;
+ radvd = 139;
+ zookeeper = 140;
+ dnsmasq = 141;
+ uhub = 142;
+ yandexdisk = 143;
+ mxisd = 144; # was once collectd
+ consul = 145;
+ mailpile = 146;
+ redmine = 147;
+ seeks = 148;
+ prosody = 149;
+ i2pd = 150;
+ systemd-network = 152;
+ systemd-resolve = 153;
+ systemd-timesync = 154;
+ liquidsoap = 155;
+ etcd = 156;
+ hbase = 158;
+ opentsdb = 159;
+ scollector = 160;
+ bosun = 161;
+ kubernetes = 162;
+ peerflix = 163;
+ chronos = 164;
+ gitlab = 165;
+ tox-bootstrapd = 166;
+ cadvisor = 167;
+ nylon = 168;
+ apache-kafka = 169;
+ #panamax = 170; # unused
+ exim = 172;
+ #fleet = 173; # unused
+ #input = 174; # unused
+ sddm = 175;
+ tss = 176;
+ #memcached = 177; removed 2018-01-03
+ ntp = 179;
+ zabbix = 180;
+ #redis = 181; removed 2018-01-03
+ unifi = 183;
+ uptimed = 184;
+ zope2 = 185;
+ ripple-data-api = 186;
+ mediatomb = 187;
+ rdnssd = 188;
+ ihaskell = 189;
+ i2p = 190;
+ lambdabot = 191;
+ asterisk = 192;
+ plex = 193;
+ plexpy = 195;
+ grafana = 196;
+ skydns = 197;
+ # ripple-rest = 198; # unused, removed 2017-08-12
+ nix-serve = 199;
+ tvheadend = 200;
+ uwsgi = 201;
+ gitit = 202;
+ riemanntools = 203;
+ subsonic = 204;
+ riak = 205;
+ shout = 206;
+ gateone = 207;
+ namecoin = 208;
+ dnschain = 209;
+ #lxd = 210; # unused
+ kibana = 211;
+ xtreemfs = 212;
+ calibre-server = 213;
+ heapster = 214;
+ bepasty = 215;
+ # pumpio = 216; # unused, removed 2018-02-24
+ nm-openvpn = 217;
+ mathics = 218;
+ ejabberd = 219;
+ postsrsd = 220;
+ opendkim = 221;
+ dspam = 222;
+ gale = 223;
+ matrix-synapse = 224;
+ rspamd = 225;
+ # rmilter = 226; # unused, removed 2019-08-22
+ cfdyndns = 227;
+ gammu-smsd = 228;
+ pdnsd = 229;
+ octoprint = 230;
+ avahi-autoipd = 231;
+ nntp-proxy = 232;
+ mjpg-streamer = 233;
+ radicale = 234;
+ hydra-queue-runner = 235;
+ hydra-www = 236;
+ syncthing = 237;
+ caddy = 239;
+ taskd = 240;
+ # factorio = 241; # DynamicUser = true
+ # emby = 242; # unusued, removed 2019-05-01
+ graylog = 243;
+ sniproxy = 244;
+ nzbget = 245;
+ mosquitto = 246;
+ toxvpn = 247;
+ # squeezelite = 248; # DynamicUser = true
+ turnserver = 249;
+ smokeping = 250;
+ gocd-agent = 251;
+ gocd-server = 252;
+ terraria = 253;
+ mattermost = 254;
+ prometheus = 255;
+ telegraf = 256;
+ gitlab-runner = 257;
+ postgrey = 258;
+ hound = 259;
+ leaps = 260;
+ ipfs = 261;
+ stanchion = 262;
+ riak-cs = 263;
+ infinoted = 264;
+ sickbeard = 265;
+ headphones = 266;
+ couchpotato = 267;
+ gogs = 268;
+ pdns-recursor = 269;
+ kresd = 270;
+ rpc = 271;
+ geoip = 272;
+ fcron = 273;
+ sonarr = 274;
+ radarr = 275;
+ jackett = 276;
+ aria2 = 277;
+ clickhouse = 278;
+ rslsync = 279;
+ minio = 280;
+ kanboard = 281;
+ # pykms = 282; # DynamicUser = true
+ kodi = 283;
+ restya-board = 284;
+ mighttpd2 = 285;
+ hass = 286;
+ monero = 287;
+ ceph = 288;
+ duplicati = 289;
+ monetdb = 290;
+ restic = 291;
+ openvpn = 292;
+ meguca = 293;
+ yarn = 294;
+ hdfs = 295;
+ mapred = 296;
+ hadoop = 297;
+ hydron = 298;
+ cfssl = 299;
+ cassandra = 300;
+ qemu-libvirtd = 301;
+ # 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;
+ paperless = 315;
+ #mailman = 316; # removed 2019-08-30
+
+ # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
+
+ nixbld = 30000; # start of range of uids
+ nobody = 65534;
+ };
+
+ ids.gids = {
+ root = 0;
+ wheel = 1;
+ kmem = 2;
+ tty = 3;
+ messagebus = 4; # D-Bus
+ haldaemon = 5;
+ disk = 6;
+ vsftpd = 7;
+ ftp = 8;
+ bitlbee = 9;
+ #avahi = 10; # removed 2019-05-22
+ #nagios = 11; # unused
+ atd = 12;
+ postfix = 13;
+ postdrop = 14;
+ dovecot = 15;
+ tomcat = 16;
+ audio = 17;
+ floppy = 18;
+ uucp = 19;
+ lp = 20;
+ proc = 21;
+ pulseaudio = 22; # must match `pulseaudio' UID
+ gpsd = 23;
+ cdrom = 24;
+ tape = 25;
+ video = 26;
+ dialout = 27;
+ #polkituser = 28; # currently unused, polkitd doesn't need a group
+ utmp = 29;
+ # ddclient = 30; # converted to DynamicUser = true
+ davfs2 = 31;
+ disnix = 33;
+ osgi = 34;
+ tor = 35;
+ #cups = 36; # unused
+ #foldingathome = 37; # unused
+ #sabnzd = 38; # unused
+ #kdm = 39; # unused, even before 17.03
+ #ghostone = 40; # dropped in 18.03
+ git = 41;
+ fourstore = 42;
+ fourstorehttp = 43;
+ virtuoso = 44;
+ #rtkit = 45; # unused
+ dovecot2 = 46;
+ dovenull2 = 47;
+ prayer = 49;
+ mpd = 50;
+ clamav = 51;
+ fprot = 52;
+ #bind = 53; # unused
+ wwwrun = 54;
+ adm = 55;
+ spamd = 56;
+ networkmanager = 57;
+ nslcd = 58;
+ scanner = 59;
+ nginx = 60;
+ chrony = 61;
+ systemd-journal = 62;
+ smtpd = 63;
+ smtpq = 64;
+ supybot = 65;
+ iodined = 66;
+ libvirtd = 67;
+ graphite = 68;
+ #statsd = 69; # removed 2018-11-14
+ transmission = 70;
+ postgres = 71;
+ vboxusers = 72;
+ vboxsf = 73;
+ smbguest = 74; # unused
+ varnish = 75;
+ datadog = 76;
+ lighttpd = 77;
+ lightdm = 78;
+ freenet = 79;
+ ircd = 80;
+ bacula = 81;
+ #almir = 82; # removed 2018-03-25, the almir package was removed in 30291227f2411abaca097773eedb49b8f259e297 during 2017-08
+ deluge = 83;
+ mysql = 84;
+ rabbitmq = 85;
+ activemq = 86;
+ gnunet = 87;
+ oidentd = 88;
+ quassel = 89;
+ amule = 90;
+ minidlna = 91;
+ elasticsearch = 92;
+ #tcpcryptd = 93; # unused
+ firebird = 95;
+ keys = 96;
+ haproxy = 97;
+ #mongodb = 98; # unused
+ openldap = 99;
+ munin = 102;
+ #logcheck = 103; # unused
+ #nix-ssh = 104; # unused
+ dictd = 105;
+ couchdb = 106;
+ searx = 107;
+ kippo = 108;
+ jenkins = 109;
+ systemd-journal-gateway = 110;
+ #notbit = 111; # unused
+ aerospike = 111;
+ #ngircd = 112; # unused
+ #btsync = 113; # unused
+ #minecraft = 114; # unused
+ vault = 115;
+ #ripped = 116; # unused
+ #murmur = 117; # unused
+ foundationdb = 118;
+ newrelic = 119;
+ starbound = 120;
+ hydra = 122;
+ spiped = 123;
+ teamspeak = 124;
+ influxdb = 125;
+ nsd = 126;
+ gitolite = 127;
+ znc = 128;
+ polipo = 129;
+ mopidy = 130;
+ docker = 131;
+ gdm = 132;
+ #dhcpcd = 133; # unused
+ siproxd = 134;
+ mlmmj = 135;
+ #neo4j = 136; # unused
+ riemann = 137;
+ riemanndash = 138;
+ #radvd = 139; # unused
+ #zookeeper = 140; # unused
+ #dnsmasq = 141; # unused
+ uhub = 142;
+ #yandexdisk = 143; # unused
+ mxisd = 144; # was once collectd
+ #consul = 145; # unused
+ mailpile = 146;
+ redmine = 147;
+ seeks = 148;
+ prosody = 149;
+ i2pd = 150;
+ systemd-network = 152;
+ systemd-resolve = 153;
+ systemd-timesync = 154;
+ liquidsoap = 155;
+ #etcd = 156; # unused
+ hbase = 158;
+ opentsdb = 159;
+ scollector = 160;
+ bosun = 161;
+ kubernetes = 162;
+ #peerflix = 163; # unused
+ #chronos = 164; # unused
+ gitlab = 165;
+ nylon = 168;
+ #panamax = 170; # unused
+ exim = 172;
+ #fleet = 173; # unused
+ input = 174;
+ sddm = 175;
+ tss = 176;
+ #memcached = 177; # unused, removed 2018-01-03
+ #ntp = 179; # unused
+ zabbix = 180;
+ #redis = 181; # unused, removed 2018-01-03
+ #unifi = 183; # unused
+ #uptimed = 184; # unused
+ #zope2 = 185; # unused
+ #ripple-data-api = 186; #unused
+ mediatomb = 187;
+ #rdnssd = 188; # unused
+ ihaskell = 189;
+ i2p = 190;
+ lambdabot = 191;
+ asterisk = 192;
+ plex = 193;
+ sabnzbd = 194;
+ #grafana = 196; #unused
+ #skydns = 197; #unused
+ # ripple-rest = 198; # unused, removed 2017-08-12
+ #nix-serve = 199; #unused
+ #tvheadend = 200; #unused
+ uwsgi = 201;
+ gitit = 202;
+ riemanntools = 203;
+ subsonic = 204;
+ riak = 205;
+ #shout = 206; #unused
+ gateone = 207;
+ namecoin = 208;
+ #dnschain = 209; #unused
+ lxd = 210; # unused
+ #kibana = 211;
+ xtreemfs = 212;
+ calibre-server = 213;
+ bepasty = 215;
+ # pumpio = 216; # unused, removed 2018-02-24
+ nm-openvpn = 217;
+ mathics = 218;
+ ejabberd = 219;
+ postsrsd = 220;
+ opendkim = 221;
+ dspam = 222;
+ gale = 223;
+ matrix-synapse = 224;
+ rspamd = 225;
+ # rmilter = 226; # unused, removed 2019-08-22
+ cfdyndns = 227;
+ pdnsd = 229;
+ octoprint = 230;
+ radicale = 234;
+ syncthing = 237;
+ caddy = 239;
+ taskd = 240;
+ # factorio = 241; # unused
+ # emby = 242; # unused, removed 2019-05-01
+ sniproxy = 244;
+ nzbget = 245;
+ mosquitto = 246;
+ #toxvpn = 247; # unused
+ #squeezelite = 248; #unused
+ turnserver = 249;
+ smokeping = 250;
+ gocd-agent = 251;
+ gocd-server = 252;
+ terraria = 253;
+ mattermost = 254;
+ prometheus = 255;
+ #telegraf = 256; # unused
+ gitlab-runner = 257;
+ postgrey = 258;
+ hound = 259;
+ leaps = 260;
+ ipfs = 261;
+ stanchion = 262;
+ riak-cs = 263;
+ infinoted = 264;
+ sickbeard = 265;
+ headphones = 266;
+ couchpotato = 267;
+ gogs = 268;
+ kresd = 270;
+ #rpc = 271; # unused
+ #geoip = 272; # unused
+ fcron = 273;
+ sonarr = 274;
+ radarr = 275;
+ jackett = 276;
+ aria2 = 277;
+ clickhouse = 278;
+ rslsync = 279;
+ minio = 280;
+ kanboard = 281;
+ # pykms = 282; # DynamicUser = true
+ kodi = 283;
+ restya-board = 284;
+ mighttpd2 = 285;
+ hass = 286;
+ monero = 287;
+ ceph = 288;
+ duplicati = 289;
+ monetdb = 290;
+ restic = 291;
+ openvpn = 292;
+ meguca = 293;
+ yarn = 294;
+ hdfs = 295;
+ mapred = 296;
+ hadoop = 297;
+ hydron = 298;
+ cfssl = 299;
+ cassandra = 300;
+ qemu-libvirtd = 301;
+ 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;
+ paperless = 315;
+ #mailman = 316; # removed 2019-08-30
+
+ # When adding a gid, make sure it doesn't match an existing
+ # uid. Users and groups with the same name should have equal
+ # uids and gids. Also, don't use gids above 399!
+
+ users = 100;
+ nixbld = 30000;
+ nogroup = 65534;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/misc/label.nix b/nixpkgs/nixos/modules/misc/label.nix
new file mode 100644
index 00000000000..02b91555b3c
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/label.nix
@@ -0,0 +1,72 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.system.nixos;
+in
+
+{
+
+ options.system = {
+
+ nixos.label = mkOption {
+ type = types.str;
+ description = ''
+ NixOS version name to be used in the names of generated
+ outputs and boot labels.
+
+ If you ever wanted to influence the labels in your GRUB menu,
+ this is the option for you.
+
+ The default is <option>system.nixos.tags</option> separated by
+ "-" + "-" + <envar>NIXOS_LABEL_VERSION</envar> environment
+ variable (defaults to the value of
+ <option>system.nixos.version</option>).
+
+ Can be overriden by setting <envar>NIXOS_LABEL</envar>.
+
+ Useful for not loosing track of configurations built from different
+ nixos branches/revisions, e.g.:
+
+ <screen>
+ #!/bin/sh
+ today=`date +%Y%m%d`
+ branch=`(cd nixpkgs ; git branch 2>/dev/null | sed -n '/^\* / { s|^\* ||; p; }')`
+ revision=`(cd nixpkgs ; git rev-parse HEAD)`
+ export NIXOS_LABEL_VERSION="$today.$branch-''${revision:0:7}"
+ nixos-rebuild switch</screen>
+ '';
+ };
+
+ nixos.tags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "with-xen" ];
+ description = ''
+ Strings to prefix to the default
+ <option>system.nixos.label</option>.
+
+ Useful for not loosing track of configurations built with
+ different options, e.g.:
+
+ <screen>
+ {
+ system.nixos.tags = [ "with-xen" ];
+ virtualisation.xen.enable = true;
+ }
+ </screen>
+ '';
+ };
+
+ };
+
+ config = {
+ # This is set here rather than up there so that changing it would
+ # not rebuild the manual
+ system.nixos.label = mkDefault (maybeEnv "NIXOS_LABEL"
+ (concatStringsSep "-" ((sort (x: y: x < y) cfg.tags)
+ ++ [ (maybeEnv "NIXOS_LABEL_VERSION" cfg.version) ])));
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/misc/lib.nix b/nixpkgs/nixos/modules/misc/lib.nix
new file mode 100644
index 00000000000..121f396701e
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/lib.nix
@@ -0,0 +1,15 @@
+{ lib, ... }:
+
+{
+ options = {
+ lib = lib.mkOption {
+ default = {};
+
+ type = lib.types.attrsOf lib.types.attrs;
+
+ description = ''
+ This option allows modules to define helper functions, constants, etc.
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/misc/locate.nix b/nixpkgs/nixos/modules/misc/locate.nix
new file mode 100644
index 00000000000..449149e4bb6
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/locate.nix
@@ -0,0 +1,182 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.locate;
+ isMLocate = hasPrefix "mlocate" cfg.locate.name;
+ isFindutils = hasPrefix "findutils" cfg.locate.name;
+in {
+ options.services.locate = with types; {
+ enable = mkOption {
+ type = bool;
+ default = false;
+ description = ''
+ If enabled, NixOS will periodically update the database of
+ files used by the <command>locate</command> command.
+ '';
+ };
+
+ locate = mkOption {
+ type = package;
+ default = pkgs.findutils;
+ defaultText = "pkgs.findutils";
+ example = "pkgs.mlocate";
+ description = ''
+ The locate implementation to use
+ '';
+ };
+
+ interval = mkOption {
+ type = str;
+ default = "02:15";
+ example = "hourly";
+ description = ''
+ Update the locate database at this interval. Updates by
+ default at 2:15 AM every day.
+
+ The format is described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = listOf str;
+ default = [ ];
+ description = ''
+ Extra flags to pass to <command>updatedb</command>.
+ '';
+ };
+
+ output = mkOption {
+ type = path;
+ default = "/var/cache/locatedb";
+ description = ''
+ The database file to build.
+ '';
+ };
+
+ localuser = mkOption {
+ type = nullOr str;
+ default = "nobody";
+ description = ''
+ The user to search non-network directories as, using
+ <command>su</command>.
+ '';
+ };
+
+ pruneFS = mkOption {
+ type = listOf str;
+ default = ["afs" "anon_inodefs" "auto" "autofs" "bdev" "binfmt" "binfmt_misc" "cgroup" "cifs" "coda" "configfs" "cramfs" "cpuset" "debugfs" "devfs" "devpts" "devtmpfs" "ecryptfs" "eventpollfs" "exofs" "futexfs" "ftpfs" "fuse" "fusectl" "gfs" "gfs2" "hostfs" "hugetlbfs" "inotifyfs" "iso9660" "jffs2" "lustre" "misc" "mqueue" "ncpfs" "nnpfs" "ocfs" "ocfs2" "pipefs" "proc" "ramfs" "rpc_pipefs" "securityfs" "selinuxfs" "sfs" "shfs" "smbfs" "sockfs" "spufs" "nfs" "NFS" "nfs4" "nfsd" "sshfs" "subfs" "supermount" "sysfs" "tmpfs" "ubifs" "udf" "usbfs" "vboxsf" "vperfctrfs" ];
+ description = ''
+ Which filesystem types to exclude from indexing
+ '';
+ };
+
+ prunePaths = mkOption {
+ type = listOf path;
+ default = ["/tmp" "/var/tmp" "/var/cache" "/var/lock" "/var/run" "/var/spool" "/nix/store"];
+ description = ''
+ Which paths to exclude from indexing
+ '';
+ };
+
+ pruneNames = mkOption {
+ type = listOf str;
+ default = [];
+ description = ''
+ Directory components which should exclude paths containing them from indexing
+ '';
+ };
+
+ pruneBindMounts = mkOption {
+ type = bool;
+ default = false;
+ description = ''
+ Whether not to index bind mounts
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ users.groups = mkIf isMLocate { mlocate = {}; };
+
+ security.wrappers = mkIf isMLocate {
+ locate = {
+ group = "mlocate";
+ owner = "root";
+ permissions = "u+rx,g+x,o+x";
+ setgid = true;
+ setuid = false;
+ source = "${cfg.locate}/bin/locate";
+ };
+ };
+
+ nixpkgs.config = { locate.dbfile = cfg.output; };
+
+ environment.systemPackages = [ cfg.locate ];
+
+ environment.variables = mkIf (!isMLocate)
+ { LOCATE_PATH = cfg.output;
+ };
+
+ warnings = optional (isMLocate && cfg.localuser != null) "mlocate does not support searching as user other than root"
+ ++ optional (isFindutils && cfg.pruneNames != []) "findutils locate does not support pruning by directory component"
+ ++ optional (isFindutils && cfg.pruneBindMounts) "findutils locate does not support skipping bind mounts";
+
+ # directory creation needs to be separated from main service
+ # because ReadWritePaths fails when the directory doesn't already exist
+ systemd.tmpfiles.rules = [ "d ${dirOf cfg.output} 0755 root root -" ];
+
+ systemd.services.update-locatedb =
+ { description = "Update Locate Database";
+ path = mkIf (!isMLocate) [ pkgs.su ];
+
+ # mlocate's updatedb takes flags via a configuration file or
+ # on the command line, but not by environment variable.
+ script =
+ if isMLocate
+ then let toFlags = x: optional (cfg.${x} != [])
+ "--${lib.toLower x} '${concatStringsSep " " cfg.${x}}'";
+ args = concatLists (map toFlags ["pruneFS" "pruneNames" "prunePaths"]);
+ in ''
+ exec ${cfg.locate}/bin/updatedb \
+ --output ${toString cfg.output} ${concatStringsSep " " args} \
+ --prune-bind-mounts ${if cfg.pruneBindMounts then "yes" else "no"} \
+ ${concatStringsSep " " cfg.extraFlags}
+ ''
+ else ''
+ exec ${cfg.locate}/bin/updatedb \
+ ${optionalString (cfg.localuser != null && ! isMLocate) ''--localuser=${cfg.localuser}''} \
+ --output=${toString cfg.output} ${concatStringsSep " " cfg.extraFlags}
+ '';
+ environment = optionalAttrs (!isMLocate) {
+ PRUNEFS = concatStringsSep " " cfg.pruneFS;
+ PRUNEPATHS = concatStringsSep " " cfg.prunePaths;
+ PRUNENAMES = concatStringsSep " " cfg.pruneNames;
+ PRUNE_BIND_MOUNTS = if cfg.pruneBindMounts then "yes" else "no";
+ };
+ serviceConfig.Nice = 19;
+ serviceConfig.IOSchedulingClass = "idle";
+ serviceConfig.PrivateTmp = "yes";
+ serviceConfig.PrivateNetwork = "yes";
+ serviceConfig.NoNewPrivileges = "yes";
+ serviceConfig.ReadOnlyPaths = "/";
+ # Use dirOf cfg.output because mlocate creates temporary files next to
+ # the actual database. We could specify and create them as well,
+ # but that would make this quite brittle when they change something.
+ # NOTE: If /var/cache does not exist, this leads to the misleading error message:
+ # update-locatedb.service: Failed at step NAMESPACE spawning …/update-locatedb-start: No such file or directory
+ serviceConfig.ReadWritePaths = dirOf cfg.output;
+ };
+
+ systemd.timers.update-locatedb =
+ { description = "Update timer for locate database";
+ partOf = [ "update-locatedb.service" ];
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnCalendar = cfg.interval;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/misc/meta.nix b/nixpkgs/nixos/modules/misc/meta.nix
new file mode 100644
index 00000000000..be3f4cbbcfe
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/meta.nix
@@ -0,0 +1,61 @@
+{ lib, ... }:
+
+with lib;
+
+let
+ maintainer = mkOptionType {
+ name = "maintainer";
+ check = email: elem email (attrValues lib.maintainers);
+ merge = loc: defs: listToAttrs (singleton (nameValuePair (last defs).file (last defs).value));
+ };
+
+ listOfMaintainers = types.listOf maintainer // {
+ # Returns list of
+ # { "module-file" = [
+ # "maintainer1 <first@nixos.org>"
+ # "maintainer2 <second@nixos.org>" ];
+ # }
+ merge = loc: defs:
+ zipAttrs
+ (flatten (imap1 (n: def: imap1 (m: def':
+ maintainer.merge (loc ++ ["[${toString n}-${toString m}]"])
+ [{ inherit (def) file; value = def'; }]) def.value) defs));
+ };
+
+ docFile = types.path // {
+ # Returns tuples of
+ # { file = "module location"; value = <path/to/doc.xml>; }
+ merge = loc: defs: defs;
+ };
+in
+
+{
+ options = {
+ meta = {
+
+ maintainers = mkOption {
+ type = listOfMaintainers;
+ internal = true;
+ default = [];
+ example = [ lib.maintainers.all ];
+ description = ''
+ List of maintainers of each module. This option should be defined at
+ most once per module.
+ '';
+ };
+
+ doc = mkOption {
+ type = docFile;
+ internal = true;
+ example = "./meta.xml";
+ description = ''
+ Documentation prologe for the set of options of each module. This
+ option should be defined at most once per module.
+ '';
+ };
+
+ };
+ };
+
+ meta.maintainers = singleton lib.maintainers.pierron;
+}
diff --git a/nixpkgs/nixos/modules/misc/nixops-autoluks.nix b/nixpkgs/nixos/modules/misc/nixops-autoluks.nix
new file mode 100644
index 00000000000..20c143286af
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/nixops-autoluks.nix
@@ -0,0 +1,43 @@
+{ config, options, lib, ... }:
+let
+ path = [ "deployment" "autoLuks" ];
+ hasAutoLuksConfig = lib.hasAttrByPath path config && (lib.attrByPath path {} config) != {};
+
+ inherit (config.nixops) enableDeprecatedAutoLuks;
+in {
+ options.nixops.enableDeprecatedAutoLuks = lib.mkEnableOption "Enable the deprecated NixOps AutoLuks module";
+
+ config = {
+ assertions = [
+ {
+ assertion = if hasAutoLuksConfig then hasAutoLuksConfig && enableDeprecatedAutoLuks else true;
+ message = ''
+ ⚠️ !!! WARNING !!! ⚠️
+
+ NixOps autoLuks is deprecated. The feature was never widely used and the maintenance did outgrow the benefit.
+ If you still want to use the module:
+ a) Please raise your voice in the issue tracking usage of the module:
+ https://github.com/NixOS/nixpkgs/issues/62211
+ b) make sure you set the `_netdev` option for each of the file
+ systems referring to block devices provided by the autoLuks module.
+
+ ⚠️ If you do not set the option your system will not boot anymore! ⚠️
+
+ {
+ fileSystems."/secret" = { options = [ "_netdev" ]; };
+ }
+
+ b) set the option >nixops.enableDeprecatedAutoLuks = true< to remove this error.
+
+
+ For more details read through the following resources:
+ - https://github.com/NixOS/nixops/pull/1156
+ - https://github.com/NixOS/nixpkgs/issues/47550
+ - https://github.com/NixOS/nixpkgs/issues/62211
+ - https://github.com/NixOS/nixpkgs/pull/61321
+ '';
+ }
+ ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/misc/nixpkgs.nix b/nixpkgs/nixos/modules/misc/nixpkgs.nix
new file mode 100644
index 00000000000..afb74581e23
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/nixpkgs.nix
@@ -0,0 +1,245 @@
+{ config, options, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.nixpkgs;
+ opt = options.nixpkgs;
+
+ isConfig = x:
+ builtins.isAttrs x || lib.isFunction x;
+
+ optCall = f: x:
+ if lib.isFunction f
+ then f x
+ else f;
+
+ mergeConfig = lhs_: rhs_:
+ let
+ lhs = optCall lhs_ { inherit pkgs; };
+ rhs = optCall rhs_ { inherit pkgs; };
+ in
+ recursiveUpdate lhs rhs //
+ optionalAttrs (lhs ? packageOverrides) {
+ packageOverrides = pkgs:
+ optCall lhs.packageOverrides pkgs //
+ optCall (attrByPath ["packageOverrides"] ({}) rhs) pkgs;
+ } //
+ optionalAttrs (lhs ? perlPackageOverrides) {
+ perlPackageOverrides = pkgs:
+ optCall lhs.perlPackageOverrides pkgs //
+ optCall (attrByPath ["perlPackageOverrides"] ({}) rhs) pkgs;
+ };
+
+ configType = mkOptionType {
+ name = "nixpkgs-config";
+ description = "nixpkgs config";
+ check = x:
+ let traceXIfNot = c:
+ if c x then true
+ else lib.traceSeqN 1 x false;
+ in traceXIfNot isConfig;
+ merge = args: fold (def: mergeConfig def.value) {};
+ };
+
+ overlayType = mkOptionType {
+ name = "nixpkgs-overlay";
+ description = "nixpkgs overlay";
+ check = lib.isFunction;
+ merge = lib.mergeOneOption;
+ };
+
+ pkgsType = mkOptionType {
+ name = "nixpkgs";
+ description = "An evaluation of Nixpkgs; the top level attribute set of packages";
+ 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
+
+{
+ options.nixpkgs = {
+
+ pkgs = mkOption {
+ defaultText = literalExample
+ ''import "''${nixos}/.." {
+ inherit (cfg) config overlays localSystem crossSystem;
+ }
+ '';
+ type = pkgsType;
+ example = literalExample ''import <nixpkgs> {}'';
+ description = ''
+ 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
+ relative path. The <code>config</code>, <code>overlays</code>,
+ <code>localSystem</code>, and <code>crossSystem</code> come
+ from this option's siblings.
+
+ This option can be used by applications like NixOps to increase
+ the performance of evaluation, or to create packages that depend
+ on a container that should be built with the exact same evaluation
+ of Nixpkgs, for example. Applications like this should set
+ their default value using <code>lib.mkDefault</code>, so
+ user-provided configuration can override it without using
+ <code>lib</code>.
+
+ Note that using a distinct version of Nixpkgs with NixOS may
+ be an unexpected source of problems. Use this option with care.
+ '';
+ };
+
+ config = mkOption {
+ default = {};
+ example = literalExample
+ ''
+ { allowBroken = true; allowUnfree = true; }
+ '';
+ type = configType;
+ description = ''
+ The configuration of the Nix Packages collection. (For
+ details, see the Nixpkgs documentation.) It allows you to set
+ package configuration options.
+
+ Ignored when <code>nixpkgs.pkgs</code> is set.
+ '';
+ };
+
+ overlays = mkOption {
+ default = [];
+ example = literalExample
+ ''
+ [
+ (self: super: {
+ openssh = super.openssh.override {
+ hpnSupport = true;
+ kerberos = self.libkrb5;
+ };
+ })
+ ]
+ '';
+ type = types.listOf overlayType;
+ description = ''
+ List of overlays to use with the Nix Packages collection.
+ (For details, see the Nixpkgs documentation.) It allows
+ 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.
+
+ 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>.
+ '';
+ };
+
+ localSystem = mkOption {
+ type = types.attrs; # TODO utilize lib.systems.parsedPlatform
+ default = { inherit (cfg) system; };
+ example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+ # Make sure that the final value has all fields for sake of other modules
+ # referring to this. TODO make `lib.systems` itself use the module system.
+ apply = lib.systems.elaborate;
+ defaultText = literalExample
+ ''(import "''${nixos}/../lib").lib.systems.examples.aarch64-multiplatform'';
+ description = ''
+ Specifies the platform on which NixOS should be built. When
+ <code>nixpkgs.crossSystem</code> is unset, it also specifies
+ the platform <emphasis>for</emphasis> which NixOS should be
+ built. If this option is unset, it defaults to the platform
+ type of the machine where evaluation happens. Specifying this
+ option is useful when doing distributed multi-platform
+ deployment, or when building virtual machines. See its
+ description in the Nixpkgs manual for more details.
+
+ Ignored when <code>nixpkgs.pkgs</code> is set.
+ '';
+ };
+
+ crossSystem = mkOption {
+ type = types.nullOr types.attrs; # TODO utilize lib.systems.parsedPlatform
+ default = null;
+ example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+ defaultText = literalExample
+ ''(import "''${nixos}/../lib").lib.systems.examples.aarch64-multiplatform'';
+ description = ''
+ Specifies the platform for which NixOS should be
+ built. Specify this only if it is different from
+ <code>nixpkgs.localSystem</code>, the platform
+ <emphasis>on</emphasis> which NixOS should be built. In other
+ words, specify this to cross-compile NixOS. Otherwise it
+ should be set as null, the default. See its description in the
+ Nixpkgs manual for more details.
+
+ Ignored when <code>nixpkgs.pkgs</code> is set.
+ '';
+ };
+
+ system = mkOption {
+ type = types.str;
+ example = "i686-linux";
+ description = ''
+ Specifies the Nix platform type on which NixOS should be built.
+ It is better to specify <code>nixpkgs.localSystem</code> instead.
+ <programlisting>
+ {
+ nixpkgs.system = ..;
+ }
+ </programlisting>
+ is the same as
+ <programlisting>
+ {
+ nixpkgs.localSystem.system = ..;
+ }
+ </programlisting>
+ See <code>nixpkgs.localSystem</code> for more information.
+
+ Ignored when <code>nixpkgs.localSystem</code> is set.
+ Ignored when <code>nixpkgs.pkgs</code> is set.
+ '';
+ };
+ };
+
+ config = {
+ _module.args = {
+ 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/nixpkgs/nixos/modules/misc/passthru.nix b/nixpkgs/nixos/modules/misc/passthru.nix
new file mode 100644
index 00000000000..4e99631fdd8
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/passthru.nix
@@ -0,0 +1,16 @@
+# This module allows you to export something from configuration
+# Use case: export kernel source expression for ease of configuring
+
+{ lib, ... }:
+
+{
+ options = {
+ passthru = lib.mkOption {
+ visible = false;
+ description = ''
+ This attribute set will be exported as a system attribute.
+ You can put whatever you want here.
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/misc/version.nix b/nixpkgs/nixos/modules/misc/version.nix
new file mode 100644
index 00000000000..773724ffbd5
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/version.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.system.nixos;
+
+ gitRepo = "${toString pkgs.path}/.git";
+ gitCommitId = lib.substring 0 7 (commitIdFromGitRepo gitRepo);
+in
+
+{
+
+ options.system = {
+
+ nixos.version = mkOption {
+ internal = true;
+ type = types.str;
+ description = "The full NixOS version (e.g. <literal>16.03.1160.f2d4ee1</literal>).";
+ };
+
+ nixos.release = mkOption {
+ readOnly = true;
+ type = types.str;
+ default = trivial.release;
+ description = "The NixOS release (e.g. <literal>16.03</literal>).";
+ };
+
+ nixos.versionSuffix = mkOption {
+ internal = true;
+ type = types.str;
+ default = trivial.versionSuffix;
+ description = "The NixOS version suffix (e.g. <literal>1160.f2d4ee1</literal>).";
+ };
+
+ nixos.revision = mkOption {
+ internal = true;
+ type = types.str;
+ 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>).";
+ };
+
+ stateVersion = mkOption {
+ type = types.str;
+ default = cfg.release;
+ description = ''
+ Every once in a while, a new NixOS release may change
+ configuration defaults in a way incompatible with stateful
+ data. For instance, if the default version of PostgreSQL
+ changes, the new version will probably be unable to read your
+ existing databases. To prevent such breakage, you can set the
+ value of this option to the NixOS release with which you want
+ to be compatible. The effect is that NixOS will option
+ defaults corresponding to the specified release (such as using
+ an older version of PostgreSQL).
+ '';
+ };
+
+ defaultChannel = mkOption {
+ internal = true;
+ type = types.str;
+ default = https://nixos.org/channels/nixos-unstable;
+ description = "Default NixOS channel to which the root user is subscribed.";
+ };
+
+ };
+
+ config = {
+
+ system.nixos = {
+ # These defaults are set here rather than up there so that
+ # changing them would not rebuild the manual
+ version = mkDefault (cfg.release + cfg.versionSuffix);
+ revision = mkIf (pathIsDirectory gitRepo) (mkDefault gitCommitId);
+ versionSuffix = mkIf (pathIsDirectory gitRepo) (mkDefault (".git." + gitCommitId));
+ };
+
+ # Generate /etc/os-release. See
+ # https://www.freedesktop.org/software/systemd/man/os-release.html for the
+ # format.
+ environment.etc.os-release.text =
+ ''
+ NAME=NixOS
+ ID=nixos
+ VERSION="${cfg.version} (${cfg.codeName})"
+ VERSION_CODENAME=${toLower cfg.codeName}
+ VERSION_ID="${cfg.version}"
+ PRETTY_NAME="NixOS ${cfg.version} (${cfg.codeName})"
+ LOGO="nix-snowflake"
+ HOME_URL="https://nixos.org/"
+ DOCUMENTATION_URL="https://nixos.org/nixos/manual/index.html"
+ SUPPORT_URL="https://nixos.org/nixos/support.html"
+ BUG_REPORT_URL="https://github.com/NixOS/nixpkgs/issues"
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/module-list.nix b/nixpkgs/nixos/modules/module-list.nix
new file mode 100644
index 00000000000..775cc05aa0a
--- /dev/null
+++ b/nixpkgs/nixos/modules/module-list.nix
@@ -0,0 +1,962 @@
+[
+ ./config/debug-info.nix
+ ./config/fonts/corefonts.nix
+ ./config/fonts/fontconfig.nix
+ ./config/fonts/fontconfig-penultimate.nix
+ ./config/fonts/fontconfig-ultimate.nix
+ ./config/fonts/fontdir.nix
+ ./config/fonts/fonts.nix
+ ./config/fonts/ghostscript.nix
+ ./config/xdg/autostart.nix
+ ./config/xdg/icons.nix
+ ./config/xdg/menus.nix
+ ./config/xdg/mime.nix
+ ./config/xdg/portal.nix
+ ./config/appstream.nix
+ ./config/xdg/sounds.nix
+ ./config/gtk/gtk-icon-cache.nix
+ ./config/gnu.nix
+ ./config/i18n.nix
+ ./config/iproute2.nix
+ ./config/krb5/default.nix
+ ./config/ldap.nix
+ ./config/locale.nix
+ ./config/malloc.nix
+ ./config/networking.nix
+ ./config/no-x-libs.nix
+ ./config/nsswitch.nix
+ ./config/power-management.nix
+ ./config/pulseaudio.nix
+ ./config/qt5.nix
+ ./config/resolvconf.nix
+ ./config/shells-environment.nix
+ ./config/swap.nix
+ ./config/sysctl.nix
+ ./config/system-environment.nix
+ ./config/system-path.nix
+ ./config/terminfo.nix
+ ./config/unix-odbc-drivers.nix
+ ./config/users-groups.nix
+ ./config/vpnc.nix
+ ./config/vte.nix
+ ./config/zram.nix
+ ./hardware/acpilight.nix
+ ./hardware/all-firmware.nix
+ ./hardware/bladeRF.nix
+ ./hardware/brightnessctl.nix
+ ./hardware/ckb-next.nix
+ ./hardware/cpu/amd-microcode.nix
+ ./hardware/cpu/intel-microcode.nix
+ ./hardware/digitalbitbox.nix
+ ./hardware/device-tree.nix
+ ./hardware/sensor/iio.nix
+ ./hardware/ksm.nix
+ ./hardware/ledger.nix
+ ./hardware/logitech.nix
+ ./hardware/mcelog.nix
+ ./hardware/network/b43.nix
+ ./hardware/network/intel-2200bg.nix
+ ./hardware/nitrokey.nix
+ ./hardware/opengl.nix
+ ./hardware/openrazer.nix
+ ./hardware/pcmcia.nix
+ ./hardware/printers.nix
+ ./hardware/raid/hpsa.nix
+ ./hardware/steam-hardware.nix
+ ./hardware/usb-wwan.nix
+ ./hardware/onlykey.nix
+ ./hardware/video/amdgpu.nix
+ ./hardware/video/amdgpu-pro.nix
+ ./hardware/video/ati.nix
+ ./hardware/video/capture/mwprocapture.nix
+ ./hardware/video/bumblebee.nix
+ ./hardware/video/displaylink.nix
+ ./hardware/video/nvidia.nix
+ ./hardware/video/uvcvideo/default.nix
+ ./hardware/video/webcam/facetimehd.nix
+ ./i18n/input-method/default.nix
+ ./i18n/input-method/fcitx.nix
+ ./i18n/input-method/ibus.nix
+ ./i18n/input-method/nabi.nix
+ ./i18n/input-method/uim.nix
+ ./installer/tools/tools.nix
+ ./misc/assertions.nix
+ ./misc/crashdump.nix
+ ./misc/documentation.nix
+ ./misc/extra-arguments.nix
+ ./misc/ids.nix
+ ./misc/lib.nix
+ ./misc/label.nix
+ ./misc/locate.nix
+ ./misc/meta.nix
+ ./misc/nixpkgs.nix
+ ./misc/passthru.nix
+ ./misc/version.nix
+ ./misc/nixops-autoluks.nix
+ ./programs/adb.nix
+ ./programs/atop.nix
+ ./programs/autojump.nix
+ ./programs/bash/bash.nix
+ ./programs/bcc.nix
+ ./programs/blcr.nix
+ ./programs/browserpass.nix
+ ./programs/captive-browser.nix
+ ./programs/ccache.nix
+ ./programs/cdemu.nix
+ ./programs/chromium.nix
+ ./programs/clickshare.nix
+ ./programs/command-not-found/command-not-found.nix
+ ./programs/criu.nix
+ ./programs/dconf.nix
+ ./programs/digitalbitbox/default.nix
+ ./programs/dmrconfig.nix
+ ./programs/environment.nix
+ ./programs/evince.nix
+ ./programs/file-roller.nix
+ ./programs/firejail.nix
+ ./programs/fish.nix
+ ./programs/freetds.nix
+ ./programs/fuse.nix
+ ./programs/gnome-disks.nix
+ ./programs/gnome-documents.nix
+ ./programs/gnome-terminal.nix
+ ./programs/gpaste.nix
+ ./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/screen.nix
+ ./programs/sedutil.nix
+ ./programs/seahorse.nix
+ ./programs/slock.nix
+ ./programs/shadow.nix
+ ./programs/shell.nix
+ ./programs/spacefm.nix
+ ./programs/singularity.nix
+ ./programs/ssh.nix
+ ./programs/ssmtp.nix
+ ./programs/sysdig.nix
+ ./programs/systemtap.nix
+ ./programs/sway.nix
+ ./programs/system-config-printer.nix
+ ./programs/thefuck.nix
+ ./programs/tmux.nix
+ ./programs/tsm-client.nix
+ ./programs/udevil.nix
+ ./programs/usbtop.nix
+ ./programs/venus.nix
+ ./programs/vim.nix
+ ./programs/wavemon.nix
+ ./programs/way-cooler.nix
+ ./programs/waybar.nix
+ ./programs/wireshark.nix
+ ./programs/x2goserver.nix
+ ./programs/xfs_quota.nix
+ ./programs/xonsh.nix
+ ./programs/xss-lock.nix
+ ./programs/yabar.nix
+ ./programs/zmap.nix
+ ./programs/zsh/oh-my-zsh.nix
+ ./programs/zsh/zsh.nix
+ ./programs/zsh/zsh-autoenv.nix
+ ./programs/zsh/zsh-autosuggestions.nix
+ ./programs/zsh/zsh-syntax-highlighting.nix
+ ./rename.nix
+ ./security/acme.nix
+ ./security/apparmor.nix
+ ./security/apparmor-suid.nix
+ ./security/audit.nix
+ ./security/auditd.nix
+ ./security/ca.nix
+ ./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
+ ./security/pam_mount.nix
+ ./security/polkit.nix
+ ./security/prey.nix
+ ./security/rngd.nix
+ ./security/rtkit.nix
+ ./security/wrappers/default.nix
+ ./security/sudo.nix
+ ./security/systemd-confinement.nix
+ ./services/admin/oxidized.nix
+ ./services/admin/salt/master.nix
+ ./services/admin/salt/minion.nix
+ ./services/amqp/activemq/default.nix
+ ./services/amqp/rabbitmq.nix
+ ./services/audio/alsa.nix
+ ./services/audio/jack.nix
+ ./services/audio/icecast.nix
+ ./services/audio/liquidsoap.nix
+ ./services/audio/mpd.nix
+ ./services/audio/mopidy.nix
+ ./services/audio/roon-server.nix
+ ./services/audio/slimserver.nix
+ ./services/audio/snapserver.nix
+ ./services/audio/squeezelite.nix
+ ./services/audio/spotifyd.nix
+ ./services/audio/ympd.nix
+ ./services/backup/automysqlbackup.nix
+ ./services/backup/bacula.nix
+ ./services/backup/borgbackup.nix
+ ./services/backup/duplicati.nix
+ ./services/backup/duplicity.nix
+ ./services/backup/mysql-backup.nix
+ ./services/backup/postgresql-backup.nix
+ ./services/backup/postgresql-wal-receiver.nix
+ ./services/backup/restic.nix
+ ./services/backup/restic-rest-server.nix
+ ./services/backup/rsnapshot.nix
+ ./services/backup/tarsnap.nix
+ ./services/backup/tsm.nix
+ ./services/backup/znapzend.nix
+ ./services/cluster/hadoop/default.nix
+ ./services/cluster/kubernetes/addons/dns.nix
+ ./services/cluster/kubernetes/addons/dashboard.nix
+ ./services/cluster/kubernetes/addon-manager.nix
+ ./services/cluster/kubernetes/apiserver.nix
+ ./services/cluster/kubernetes/controller-manager.nix
+ ./services/cluster/kubernetes/default.nix
+ ./services/cluster/kubernetes/flannel.nix
+ ./services/cluster/kubernetes/kubelet.nix
+ ./services/cluster/kubernetes/pki.nix
+ ./services/cluster/kubernetes/proxy.nix
+ ./services/cluster/kubernetes/scheduler.nix
+ ./services/computing/boinc/client.nix
+ ./services/computing/torque/server.nix
+ ./services/computing/torque/mom.nix
+ ./services/computing/slurm/slurm.nix
+ ./services/continuous-integration/buildbot/master.nix
+ ./services/continuous-integration/buildbot/worker.nix
+ ./services/continuous-integration/buildkite-agent.nix
+ ./services/continuous-integration/hail.nix
+ ./services/continuous-integration/hydra/default.nix
+ ./services/continuous-integration/gitlab-runner.nix
+ ./services/continuous-integration/gocd-agent/default.nix
+ ./services/continuous-integration/gocd-server/default.nix
+ ./services/continuous-integration/jenkins/default.nix
+ ./services/continuous-integration/jenkins/job-builder.nix
+ ./services/continuous-integration/jenkins/slave.nix
+ ./services/databases/4store-endpoint.nix
+ ./services/databases/4store.nix
+ ./services/databases/aerospike.nix
+ ./services/databases/cassandra.nix
+ ./services/databases/clickhouse.nix
+ ./services/databases/cockroachdb.nix
+ ./services/databases/couchdb.nix
+ ./services/databases/firebird.nix
+ ./services/databases/foundationdb.nix
+ ./services/databases/hbase.nix
+ ./services/databases/influxdb.nix
+ ./services/databases/memcached.nix
+ ./services/databases/monetdb.nix
+ ./services/databases/mongodb.nix
+ ./services/databases/mysql.nix
+ ./services/databases/neo4j.nix
+ ./services/databases/openldap.nix
+ ./services/databases/opentsdb.nix
+ ./services/databases/pgmanage.nix
+ ./services/databases/postgresql.nix
+ ./services/databases/redis.nix
+ ./services/databases/riak.nix
+ ./services/databases/riak-cs.nix
+ ./services/databases/stanchion.nix
+ ./services/databases/virtuoso.nix
+ ./services/desktops/accountsservice.nix
+ ./services/desktops/bamf.nix
+ ./services/desktops/blueman.nix
+ ./services/desktops/deepin/deepin.nix
+ ./services/desktops/dleyna-renderer.nix
+ ./services/desktops/dleyna-server.nix
+ ./services/desktops/pantheon/contractor.nix
+ ./services/desktops/pantheon/files.nix
+ ./services/desktops/flatpak.nix
+ ./services/desktops/geoclue2.nix
+ ./services/desktops/gsignond.nix
+ ./services/desktops/gvfs.nix
+ ./services/desktops/pipewire.nix
+ ./services/desktops/gnome3/at-spi2-core.nix
+ ./services/desktops/gnome3/chrome-gnome-shell.nix
+ ./services/desktops/gnome3/evolution-data-server.nix
+ ./services/desktops/gnome3/glib-networking.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-settings-daemon.nix
+ ./services/desktops/gnome3/gnome-user-share.nix
+ ./services/desktops/gnome3/rygel.nix
+ ./services/desktops/gnome3/sushi.nix
+ ./services/desktops/gnome3/tracker.nix
+ ./services/desktops/gnome3/tracker-miners.nix
+ ./services/desktops/profile-sync-daemon.nix
+ ./services/desktops/system-config-printer.nix
+ ./services/desktops/telepathy.nix
+ ./services/desktops/tumbler.nix
+ ./services/desktops/zeitgeist.nix
+ ./services/development/bloop.nix
+ ./services/development/hoogle.nix
+ ./services/development/jupyter/default.nix
+ ./services/editors/emacs.nix
+ ./services/editors/infinoted.nix
+ ./services/games/factorio.nix
+ ./services/games/minecraft-server.nix
+ ./services/games/minetest-server.nix
+ ./services/games/terraria.nix
+ ./services/hardware/acpid.nix
+ ./services/hardware/actkbd.nix
+ ./services/hardware/bluetooth.nix
+ ./services/hardware/bolt.nix
+ ./services/hardware/brltty.nix
+ ./services/hardware/freefall.nix
+ ./services/hardware/fwupd.nix
+ ./services/hardware/illum.nix
+ ./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/sane_extra_backends/dsseries.nix
+ ./services/hardware/tcsd.nix
+ ./services/hardware/tlp.nix
+ ./services/hardware/thinkfan.nix
+ ./services/hardware/throttled.nix
+ ./services/hardware/trezord.nix
+ ./services/hardware/triggerhappy.nix
+ ./services/hardware/u2f.nix
+ ./services/hardware/udev.nix
+ ./services/hardware/udisks2.nix
+ ./services/hardware/upower.nix
+ ./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
+ ./services/logging/logrotate.nix
+ ./services/logging/logstash.nix
+ ./services/logging/rsyslogd.nix
+ ./services/logging/syslog-ng.nix
+ ./services/logging/syslogd.nix
+ ./services/mail/clamsmtp.nix
+ ./services/mail/davmail.nix
+ ./services/mail/dkimproxy-out.nix
+ ./services/mail/dovecot.nix
+ ./services/mail/dspam.nix
+ ./services/mail/exim.nix
+ ./services/mail/freepops.nix
+ ./services/mail/mail.nix
+ ./services/mail/mailcatcher.nix
+ ./services/mail/mailhog.nix
+ ./services/mail/mailman.nix
+ ./services/mail/mlmmj.nix
+ ./services/mail/offlineimap.nix
+ ./services/mail/opendkim.nix
+ ./services/mail/opensmtpd.nix
+ ./services/mail/pfix-srsd.nix
+ ./services/mail/postfix.nix
+ ./services/mail/postsrsd.nix
+ ./services/mail/postgrey.nix
+ ./services/mail/spamassassin.nix
+ ./services/mail/rspamd.nix
+ ./services/mail/rss2email.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/beanstalkd.nix
+ ./services/misc/bees.nix
+ ./services/misc/bepasty.nix
+ ./services/misc/canto-daemon.nix
+ ./services/misc/calibre-server.nix
+ ./services/misc/cfdyndns.nix
+ ./services/misc/clipmenu.nix
+ ./services/misc/cpuminer-cryptonight.nix
+ ./services/misc/cgminer.nix
+ ./services/misc/confd.nix
+ ./services/misc/couchpotato.nix
+ ./services/misc/devmon.nix
+ ./services/misc/dictd.nix
+ ./services/misc/dwm-status.nix
+ ./services/misc/dysnomia.nix
+ ./services/misc/disnix.nix
+ ./services/misc/docker-registry.nix
+ ./services/misc/errbot.nix
+ ./services/misc/etcd.nix
+ ./services/misc/ethminer.nix
+ ./services/misc/exhibitor.nix
+ ./services/misc/felix.nix
+ ./services/misc/folding-at-home.nix
+ ./services/misc/fstrim.nix
+ ./services/misc/gammu-smsd.nix
+ ./services/misc/geoip-updater.nix
+ ./services/misc/gitea.nix
+ #./services/misc/gitit.nix
+ ./services/misc/gitlab.nix
+ ./services/misc/gitolite.nix
+ ./services/misc/gitweb.nix
+ ./services/misc/gogs.nix
+ ./services/misc/gollum.nix
+ ./services/misc/gpsd.nix
+ ./services/misc/headphones.nix
+ ./services/misc/greenclip.nix
+ ./services/misc/home-assistant.nix
+ ./services/misc/ihaskell.nix
+ ./services/misc/irkerd.nix
+ ./services/misc/jackett.nix
+ ./services/misc/jellyfin.nix
+ ./services/misc/logkeys.nix
+ ./services/misc/leaps.nix
+ ./services/misc/lidarr.nix
+ ./services/misc/mathics.nix
+ ./services/misc/matrix-synapse.nix
+ ./services/misc/mbpfan.nix
+ ./services/misc/mediatomb.nix
+ ./services/misc/mesos-master.nix
+ ./services/misc/mesos-slave.nix
+ ./services/misc/metabase.nix
+ ./services/misc/mwlib.nix
+ ./services/misc/nix-daemon.nix
+ ./services/misc/nix-gc.nix
+ ./services/misc/nix-optimise.nix
+ ./services/misc/nixos-manual.nix
+ ./services/misc/nix-ssh-serve.nix
+ ./services/misc/novacomd.nix
+ ./services/misc/nzbget.nix
+ ./services/misc/octoprint.nix
+ ./services/misc/osrm.nix
+ ./services/misc/packagekit.nix
+ ./services/misc/paperless.nix
+ ./services/misc/parsoid.nix
+ ./services/misc/plex.nix
+ ./services/misc/tautulli.nix
+ ./services/misc/pykms.nix
+ ./services/misc/radarr.nix
+ ./services/misc/redmine.nix
+ ./services/misc/rippled.nix
+ ./services/misc/ripple-data-api.nix
+ ./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
+ ./services/misc/spice-vdagentd.nix
+ ./services/misc/ssm-agent.nix
+ ./services/misc/sssd.nix
+ ./services/misc/subsonic.nix
+ ./services/misc/sundtek.nix
+ ./services/misc/svnserve.nix
+ ./services/misc/synergy.nix
+ ./services/misc/sysprof.nix
+ ./services/misc/taskserver
+ ./services/misc/tiddlywiki.nix
+ ./services/misc/tzupdate.nix
+ ./services/misc/uhub.nix
+ ./services/misc/weechat.nix
+ ./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
+ ./services/monitoring/cadvisor.nix
+ ./services/monitoring/collectd.nix
+ ./services/monitoring/das_watchdog.nix
+ ./services/monitoring/datadog-agent.nix
+ ./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/loki.nix
+ ./services/monitoring/longview.nix
+ ./services/monitoring/monit.nix
+ ./services/monitoring/munin.nix
+ ./services/monitoring/nagios.nix
+ ./services/monitoring/netdata.nix
+ ./services/monitoring/osquery.nix
+ ./services/monitoring/prometheus/default.nix
+ ./services/monitoring/prometheus/alertmanager.nix
+ ./services/monitoring/prometheus/exporters.nix
+ ./services/monitoring/prometheus/pushgateway.nix
+ ./services/monitoring/riemann.nix
+ ./services/monitoring/riemann-dash.nix
+ ./services/monitoring/riemann-tools.nix
+ ./services/monitoring/scollector.nix
+ ./services/monitoring/smartd.nix
+ ./services/monitoring/sysstat.nix
+ ./services/monitoring/teamviewer.nix
+ ./services/monitoring/telegraf.nix
+ ./services/monitoring/thanos.nix
+ ./services/monitoring/ups.nix
+ ./services/monitoring/uptime.nix
+ ./services/monitoring/vnstat.nix
+ ./services/monitoring/zabbix-agent.nix
+ ./services/monitoring/zabbix-proxy.nix
+ ./services/monitoring/zabbix-server.nix
+ ./services/network-filesystems/beegfs.nix
+ ./services/network-filesystems/cachefilesd.nix
+ ./services/network-filesystems/davfs2.nix
+ ./services/network-filesystems/drbd.nix
+ ./services/network-filesystems/glusterfs.nix
+ ./services/network-filesystems/kbfs.nix
+ ./services/network-filesystems/ipfs.nix
+ ./services/network-filesystems/netatalk.nix
+ ./services/network-filesystems/nfsd.nix
+ ./services/network-filesystems/openafs/client.nix
+ ./services/network-filesystems/openafs/server.nix
+ ./services/network-filesystems/rsyncd.nix
+ ./services/network-filesystems/samba.nix
+ ./services/network-filesystems/tahoe.nix
+ ./services/network-filesystems/diod.nix
+ ./services/network-filesystems/u9fs.nix
+ ./services/network-filesystems/yandex-disk.nix
+ ./services/network-filesystems/xtreemfs.nix
+ ./services/network-filesystems/ceph.nix
+ ./services/networking/amuled.nix
+ ./services/networking/aria2.nix
+ ./services/networking/asterisk.nix
+ ./services/networking/atftpd.nix
+ ./services/networking/avahi-daemon.nix
+ ./services/networking/babeld.nix
+ ./services/networking/bind.nix
+ ./services/networking/bitcoind.nix
+ ./services/networking/autossh.nix
+ ./services/networking/bird.nix
+ ./services/networking/bitlbee.nix
+ ./services/networking/charybdis.nix
+ ./services/networking/cjdns.nix
+ ./services/networking/cntlm.nix
+ ./services/networking/connman.nix
+ ./services/networking/consul.nix
+ ./services/networking/coredns.nix
+ ./services/networking/coturn.nix
+ ./services/networking/dante.nix
+ ./services/networking/ddclient.nix
+ ./services/networking/dhcpcd.nix
+ ./services/networking/dhcpd.nix
+ ./services/networking/dnscache.nix
+ ./services/networking/dnschain.nix
+ ./services/networking/dnscrypt-proxy.nix
+ ./services/networking/dnscrypt-wrapper.nix
+ ./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
+ ./services/networking/fireqos.nix
+ ./services/networking/firewall.nix
+ ./services/networking/flannel.nix
+ ./services/networking/flashpolicyd.nix
+ ./services/networking/freenet.nix
+ ./services/networking/freeradius.nix
+ ./services/networking/gale.nix
+ ./services/networking/gateone.nix
+ ./services/networking/gdomap.nix
+ ./services/networking/git-daemon.nix
+ ./services/networking/gnunet.nix
+ ./services/networking/gogoclient.nix
+ ./services/networking/gvpe.nix
+ ./services/networking/hans.nix
+ ./services/networking/haproxy.nix
+ ./services/networking/heyefi.nix
+ ./services/networking/hostapd.nix
+ ./services/networking/htpdate.nix
+ ./services/networking/hylafax/default.nix
+ ./services/networking/i2pd.nix
+ ./services/networking/i2p.nix
+ ./services/networking/iodine.nix
+ ./services/networking/iperf3.nix
+ ./services/networking/ircd-hybrid/default.nix
+ ./services/networking/jormungandr.nix
+ ./services/networking/iwd.nix
+ ./services/networking/keepalived/default.nix
+ ./services/networking/keybase.nix
+ ./services/networking/kippo.nix
+ ./services/networking/knot.nix
+ ./services/networking/kresd.nix
+ ./services/networking/lambdabot.nix
+ ./services/networking/libreswan.nix
+ ./services/networking/lldpd.nix
+ ./services/networking/logmein-hamachi.nix
+ ./services/networking/mailpile.nix
+ ./services/networking/matterbridge.nix
+ ./services/networking/mjpg-streamer.nix
+ ./services/networking/minidlna.nix
+ ./services/networking/miniupnpd.nix
+ ./services/networking/mosquitto.nix
+ ./services/networking/monero.nix
+ ./services/networking/morty.nix
+ ./services/networking/miredo.nix
+ ./services/networking/mstpd.nix
+ ./services/networking/mtprotoproxy.nix
+ ./services/networking/murmur.nix
+ ./services/networking/mxisd.nix
+ ./services/networking/namecoind.nix
+ ./services/networking/nat.nix
+ ./services/networking/ndppd.nix
+ ./services/networking/networkmanager.nix
+ ./services/networking/nftables.nix
+ ./services/networking/ngircd.nix
+ ./services/networking/nghttpx/default.nix
+ ./services/networking/nix-serve.nix
+ ./services/networking/nixops-dns.nix
+ ./services/networking/nntp-proxy.nix
+ ./services/networking/nsd.nix
+ ./services/networking/ntopng.nix
+ ./services/networking/ntp/chrony.nix
+ ./services/networking/ntp/ntpd.nix
+ ./services/networking/ntp/openntpd.nix
+ ./services/networking/nullidentdmod.nix
+ ./services/networking/nylon.nix
+ ./services/networking/ocserv.nix
+ ./services/networking/ofono.nix
+ ./services/networking/oidentd.nix
+ ./services/networking/openfire.nix
+ ./services/networking/openvpn.nix
+ ./services/networking/ostinato.nix
+ ./services/networking/owamp.nix
+ ./services/networking/pdnsd.nix
+ ./services/networking/polipo.nix
+ ./services/networking/powerdns.nix
+ ./services/networking/pdns-recursor.nix
+ ./services/networking/pptpd.nix
+ ./services/networking/prayer.nix
+ ./services/networking/privoxy.nix
+ ./services/networking/prosody.nix
+ ./services/networking/quagga.nix
+ ./services/networking/quassel.nix
+ ./services/networking/quicktun.nix
+ ./services/networking/racoon.nix
+ ./services/networking/radicale.nix
+ ./services/networking/radvd.nix
+ ./services/networking/rdnssd.nix
+ ./services/networking/redsocks.nix
+ ./services/networking/resilio.nix
+ ./services/networking/rpcbind.nix
+ ./services/networking/rxe.nix
+ ./services/networking/sabnzbd.nix
+ ./services/networking/searx.nix
+ ./services/networking/seeks.nix
+ ./services/networking/skydns.nix
+ ./services/networking/shadowsocks.nix
+ ./services/networking/shairport-sync.nix
+ ./services/networking/shout.nix
+ ./services/networking/sniproxy.nix
+ ./services/networking/smokeping.nix
+ ./services/networking/softether.nix
+ ./services/networking/spiped.nix
+ ./services/networking/squid.nix
+ ./services/networking/sslh.nix
+ ./services/networking/ssh/lshd.nix
+ ./services/networking/ssh/sshd.nix
+ ./services/networking/strongswan.nix
+ ./services/networking/strongswan-swanctl/module.nix
+ ./services/networking/stunnel.nix
+ ./services/networking/stubby.nix
+ ./services/networking/supplicant.nix
+ ./services/networking/supybot.nix
+ ./services/networking/syncthing.nix
+ ./services/networking/syncthing-relay.nix
+ ./services/networking/syncplay.nix
+ ./services/networking/tcpcrypt.nix
+ ./services/networking/teamspeak3.nix
+ ./services/networking/tedicross.nix
+ ./services/networking/thelounge.nix
+ ./services/networking/tinc.nix
+ ./services/networking/tinydns.nix
+ ./services/networking/tftpd.nix
+ ./services/networking/tox-bootstrapd.nix
+ ./services/networking/tox-node.nix
+ ./services/networking/toxvpn.nix
+ ./services/networking/tvheadend.nix
+ ./services/networking/unbound.nix
+ ./services/networking/unifi.nix
+ ./services/networking/vsftpd.nix
+ ./services/networking/wakeonlan.nix
+ ./services/networking/websockify.nix
+ ./services/networking/wg-quick.nix
+ ./services/networking/wicd.nix
+ ./services/networking/wireguard.nix
+ ./services/networking/wpa_supplicant.nix
+ ./services/networking/xinetd.nix
+ ./services/networking/xl2tpd.nix
+ ./services/networking/xrdp.nix
+ ./services/networking/zerobin.nix
+ ./services/networking/zeronet.nix
+ ./services/networking/zerotierone.nix
+ ./services/networking/znc/default.nix
+ ./services/printing/cupsd.nix
+ ./services/scheduling/atd.nix
+ ./services/scheduling/chronos.nix
+ ./services/scheduling/cron.nix
+ ./services/scheduling/fcron.nix
+ ./services/scheduling/marathon.nix
+ ./services/search/elasticsearch.nix
+ ./services/search/elasticsearch-curator.nix
+ ./services/search/hound.nix
+ ./services/search/kibana.nix
+ ./services/search/solr.nix
+ ./services/security/bitwarden_rs/default.nix
+ ./services/security/certmgr.nix
+ ./services/security/cfssl.nix
+ ./services/security/clamav.nix
+ ./services/security/fail2ban.nix
+ ./services/security/fprintd.nix
+ ./services/security/fprot.nix
+ ./services/security/haka.nix
+ ./services/security/haveged.nix
+ ./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
+ ./services/security/shibboleth-sp.nix
+ ./services/security/sks.nix
+ ./services/security/sshguard.nix
+ ./services/security/tor.nix
+ ./services/security/torify.nix
+ ./services/security/torsocks.nix
+ ./services/security/usbguard.nix
+ ./services/security/vault.nix
+ ./services/system/cgmanager.nix
+ ./services/system/cloud-init.nix
+ ./services/system/dbus.nix
+ ./services/system/earlyoom.nix
+ ./services/system/localtime.nix
+ ./services/system/kerberos/default.nix
+ ./services/system/nscd.nix
+ ./services/system/saslauthd.nix
+ ./services/system/uptimed.nix
+ ./services/torrent/deluge.nix
+ ./services/torrent/flexget.nix
+ ./services/torrent/magnetico.nix
+ ./services/torrent/opentracker.nix
+ ./services/torrent/peerflix.nix
+ ./services/torrent/transmission.nix
+ ./services/ttys/agetty.nix
+ ./services/ttys/gpm.nix
+ ./services/ttys/kmscon.nix
+ ./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/cryptpad.nix
+ ./services/web-apps/documize.nix
+ ./services/web-apps/frab.nix
+ ./services/web-apps/icingaweb2/icingaweb2.nix
+ ./services/web-apps/icingaweb2/module-monitoring.nix
+ ./services/web-apps/limesurvey.nix
+ ./services/web-apps/mattermost.nix
+ ./services/web-apps/mediawiki.nix
+ ./services/web-apps/miniflux.nix
+ ./services/web-apps/moodle.nix
+ ./services/web-apps/nextcloud.nix
+ ./services/web-apps/nexus.nix
+ ./services/web-apps/pgpkeyserver-lite.nix
+ ./services/web-apps/matomo.nix
+ ./services/web-apps/restya-board.nix
+ ./services/web-apps/tt-rss.nix
+ ./services/web-apps/selfoss.nix
+ ./services/web-apps/virtlyst.nix
+ ./services/web-apps/wordpress.nix
+ ./services/web-apps/youtrack.nix
+ ./services/web-apps/zabbix.nix
+ ./services/web-servers/apache-httpd/default.nix
+ ./services/web-servers/caddy.nix
+ ./services/web-servers/darkhttpd.nix
+ ./services/web-servers/fcgiwrap.nix
+ ./services/web-servers/hitch/default.nix
+ ./services/web-servers/hydron.nix
+ ./services/web-servers/jboss/default.nix
+ ./services/web-servers/lighttpd/cgit.nix
+ ./services/web-servers/lighttpd/collectd.nix
+ ./services/web-servers/lighttpd/default.nix
+ ./services/web-servers/lighttpd/gitweb.nix
+ ./services/web-servers/meguca.nix
+ ./services/web-servers/mighttpd2.nix
+ ./services/web-servers/minio.nix
+ ./services/web-servers/nginx/default.nix
+ ./services/web-servers/nginx/gitweb.nix
+ ./services/web-servers/phpfpm/default.nix
+ ./services/web-servers/unit/default.nix
+ ./services/web-servers/shellinabox.nix
+ ./services/web-servers/tomcat.nix
+ ./services/web-servers/traefik.nix
+ ./services/web-servers/uwsgi.nix
+ ./services/web-servers/varnish/default.nix
+ ./services/web-servers/zope2.nix
+ ./services/x11/extra-layouts.nix
+ ./services/x11/clight.nix
+ ./services/x11/colord.nix
+ ./services/x11/compton.nix
+ ./services/x11/unclutter.nix
+ ./services/x11/unclutter-xfixes.nix
+ ./services/x11/desktop-managers/default.nix
+ ./services/x11/display-managers/auto.nix
+ ./services/x11/display-managers/default.nix
+ ./services/x11/display-managers/gdm.nix
+ ./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/hardware/cmt.nix
+ ./services/x11/gdk-pixbuf.nix
+ ./services/x11/redshift.nix
+ ./services/x11/urxvtd.nix
+ ./services/x11/window-managers/awesome.nix
+ ./services/x11/window-managers/default.nix
+ ./services/x11/window-managers/fluxbox.nix
+ ./services/x11/window-managers/icewm.nix
+ ./services/x11/window-managers/bspwm.nix
+ ./services/x11/window-managers/metacity.nix
+ ./services/x11/window-managers/none.nix
+ ./services/x11/window-managers/twm.nix
+ ./services/x11/window-managers/windowlab.nix
+ ./services/x11/window-managers/wmii.nix
+ ./services/x11/window-managers/xmonad.nix
+ ./services/x11/xautolock.nix
+ ./services/x11/xbanish.nix
+ ./services/x11/xfs.nix
+ ./services/x11/xserver.nix
+ ./system/activation/activation-script.nix
+ ./system/activation/top-level.nix
+ ./system/boot/binfmt.nix
+ ./system/boot/emergency-mode.nix
+ ./system/boot/grow-partition.nix
+ ./system/boot/initrd-network.nix
+ ./system/boot/initrd-ssh.nix
+ ./system/boot/kernel.nix
+ ./system/boot/kexec.nix
+ ./system/boot/loader/efi.nix
+ ./system/boot/loader/generations-dir/generations-dir.nix
+ ./system/boot/loader/generic-extlinux-compatible
+ ./system/boot/loader/grub/grub.nix
+ ./system/boot/loader/grub/ipxe.nix
+ ./system/boot/loader/grub/memtest.nix
+ ./system/boot/loader/init-script/init-script.nix
+ ./system/boot/loader/loader.nix
+ ./system/boot/loader/raspberrypi/raspberrypi.nix
+ ./system/boot/loader/systemd-boot/systemd-boot.nix
+ ./system/boot/luksroot.nix
+ ./system/boot/modprobe.nix
+ ./system/boot/networkd.nix
+ ./system/boot/plymouth.nix
+ ./system/boot/resolved.nix
+ ./system/boot/shutdown.nix
+ ./system/boot/stage-1.nix
+ ./system/boot/stage-2.nix
+ ./system/boot/systemd.nix
+ ./system/boot/systemd-nspawn.nix
+ ./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
+ ./tasks/filesystems.nix
+ ./tasks/filesystems/bcachefs.nix
+ ./tasks/filesystems/btrfs.nix
+ ./tasks/filesystems/cifs.nix
+ ./tasks/filesystems/ecryptfs.nix
+ ./tasks/filesystems/exfat.nix
+ ./tasks/filesystems/ext.nix
+ ./tasks/filesystems/f2fs.nix
+ ./tasks/filesystems/jfs.nix
+ ./tasks/filesystems/nfs.nix
+ ./tasks/filesystems/ntfs.nix
+ ./tasks/filesystems/reiserfs.nix
+ ./tasks/filesystems/unionfs-fuse.nix
+ ./tasks/filesystems/vboxsf.nix
+ ./tasks/filesystems/vfat.nix
+ ./tasks/filesystems/xfs.nix
+ ./tasks/filesystems/zfs.nix
+ ./tasks/kbd.nix
+ ./tasks/lvm.nix
+ ./tasks/network-interfaces.nix
+ ./tasks/network-interfaces-systemd.nix
+ ./tasks/network-interfaces-scripted.nix
+ ./tasks/scsi-link-power-management.nix
+ ./tasks/swraid.nix
+ ./tasks/trackpoint.nix
+ ./tasks/powertop.nix
+ ./testing/service-runner.nix
+ ./virtualisation/anbox.nix
+ ./virtualisation/container-config.nix
+ ./virtualisation/containers.nix
+ ./virtualisation/cri-o.nix
+ ./virtualisation/docker.nix
+ ./virtualisation/docker-containers.nix
+ ./virtualisation/ecs-agent.nix
+ ./virtualisation/libvirtd.nix
+ ./virtualisation/lxc.nix
+ ./virtualisation/lxcfs.nix
+ ./virtualisation/lxd.nix
+ ./virtualisation/amazon-options.nix
+ ./virtualisation/hyperv-guest.nix
+ ./virtualisation/kvmgt.nix
+ ./virtualisation/openvswitch.nix
+ ./virtualisation/parallels-guest.nix
+ ./virtualisation/qemu-guest-agent.nix
+ ./virtualisation/railcar.nix
+ ./virtualisation/rkt.nix
+ ./virtualisation/virtualbox-guest.nix
+ ./virtualisation/virtualbox-host.nix
+ ./virtualisation/vmware-guest.nix
+ ./virtualisation/xen-dom0.nix
+ ./virtualisation/xe-guest-utilities.nix
+]
diff --git a/nixpkgs/nixos/modules/profiles/all-hardware.nix b/nixpkgs/nixos/modules/profiles/all-hardware.nix
new file mode 100644
index 00000000000..19f821ae17f
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/all-hardware.nix
@@ -0,0 +1,57 @@
+# This module 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.
+
+{ ... }:
+
+{
+
+ # The initrd has to contain any module that might be necessary for
+ # supporting the most important parts of HW like drives.
+ boot.initrd.availableKernelModules =
+ [ # SATA/PATA support.
+ "ahci"
+
+ "ata_piix"
+
+ "sata_inic162x" "sata_nv" "sata_promise" "sata_qstor"
+ "sata_sil" "sata_sil24" "sata_sis" "sata_svw" "sata_sx4"
+ "sata_uli" "sata_via" "sata_vsc"
+
+ "pata_ali" "pata_amd" "pata_artop" "pata_atiixp" "pata_efar"
+ "pata_hpt366" "pata_hpt37x" "pata_hpt3x2n" "pata_hpt3x3"
+ "pata_it8213" "pata_it821x" "pata_jmicron" "pata_marvell"
+ "pata_mpiix" "pata_netcell" "pata_ns87410" "pata_oldpiix"
+ "pata_pcmcia" "pata_pdc2027x" "pata_qdi" "pata_rz1000"
+ "pata_serverworks" "pata_sil680" "pata_sis"
+ "pata_sl82c105" "pata_triflex" "pata_via"
+ "pata_winbond"
+
+ # SCSI support (incomplete).
+ "3w-9xxx" "3w-xxxx" "aic79xx" "aic7xxx" "arcmsr"
+
+ # USB support, especially for booting from USB CD-ROM
+ # drives.
+ "uas"
+
+ # Firewire support. Not tested.
+ "ohci1394" "sbp2"
+
+ # Virtio (QEMU, KVM etc.) support.
+ "virtio_net" "virtio_pci" "virtio_blk" "virtio_scsi" "virtio_balloon" "virtio_console"
+
+ # VMware support.
+ "mptspi" "vmw_balloon" "vmwgfx" "vmw_vmci" "vmw_vsock_vmci_transport" "vmxnet3" "vsock"
+
+ # Hyper-V support.
+ "hv_storvsc"
+ ];
+
+ # Include lots of firmware.
+ hardware.enableRedistributableFirmware = true;
+
+ imports =
+ [ ../hardware/network/zydas-zd1211.nix ];
+
+}
diff --git a/nixpkgs/nixos/modules/profiles/base.nix b/nixpkgs/nixos/modules/profiles/base.nix
new file mode 100644
index 00000000000..2a2fe119d30
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/base.nix
@@ -0,0 +1,56 @@
+# This module defines the software packages included in the "minimal"
+# installation CD. It might be useful elsewhere.
+
+{ lib, pkgs, ... }:
+
+{
+ # Include some utilities that are useful for installing or repairing
+ # the system.
+ environment.systemPackages = [
+ 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
+ pkgs.efivar
+ pkgs.parted
+ pkgs.gptfdisk
+ pkgs.ddrescue
+ pkgs.ccrypt
+ 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
+ pkgs.sshfs-fuse
+ pkgs.socat
+ pkgs.screen
+
+ # Hardware-related tools.
+ pkgs.sdparm
+ pkgs.hdparm
+ pkgs.smartmontools # for diagnosing hard disks
+ pkgs.pciutils
+ pkgs.usbutils
+
+ # Tools to create / manipulate filesystems.
+ pkgs.ntfsprogs # for resizing NTFS partitions
+ pkgs.dosfstools
+ pkgs.xfsprogs.bin
+ pkgs.jfsutils
+ pkgs.f2fs-tools
+
+ # Some compression/archiver tools.
+ pkgs.unzip
+ pkgs.zip
+ ];
+
+ # Include support for various filesystems.
+ 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/nixpkgs/nixos/modules/profiles/clone-config.nix b/nixpkgs/nixos/modules/profiles/clone-config.nix
new file mode 100644
index 00000000000..3f669ba7d2e
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/clone-config.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, modules, ... }:
+
+with lib;
+
+let
+
+ # Location of the repository on the harddrive
+ nixosPath = toString ../..;
+
+ # Check if the path is from the NixOS repository
+ isNixOSFile = path:
+ let s = toString path; in
+ removePrefix nixosPath s != s;
+
+ # Copy modules given as extra configuration files. Unfortunately, we
+ # cannot serialized attribute set given in the list of modules (that's why
+ # you should use files).
+ moduleFiles =
+ # FIXME: use typeOf (Nix 1.6.1).
+ filter (x: !isAttrs x && !lib.isFunction x) modules;
+
+ # Partition module files because between NixOS and non-NixOS files. NixOS
+ # files may change if the repository is updated.
+ partitionedModuleFiles =
+ let p = partition isNixOSFile moduleFiles; in
+ { nixos = p.right; others = p.wrong; };
+
+ # Path transformed to be valid on the installation device. Thus the
+ # device configuration could be rebuild.
+ relocatedModuleFiles =
+ let
+ relocateNixOS = path:
+ "<nixpkgs/nixos" + removePrefix nixosPath (toString path) + ">";
+ in
+ { nixos = map relocateNixOS partitionedModuleFiles.nixos;
+ others = []; # TODO: copy the modules to the install-device repository.
+ };
+
+ # A dummy /etc/nixos/configuration.nix in the booted CD that
+ # rebuilds the CD's configuration (and allows the configuration to
+ # be modified, of course, providing a true live CD). Problem is
+ # that we don't really know how the CD was built - the Nix
+ # expression language doesn't allow us to query the expression being
+ # evaluated. So we'll just hope for the best.
+ configClone = pkgs.writeText "configuration.nix"
+ ''
+ { config, pkgs, ... }:
+
+ {
+ imports = [ ${toString config.installer.cloneConfigIncludes} ];
+
+ ${config.installer.cloneConfigExtra}
+ }
+ '';
+
+in
+
+{
+
+ options = {
+
+ installer.cloneConfig = mkOption {
+ default = true;
+ description = ''
+ Try to clone the installation-device configuration by re-using it's
+ profile from the list of imported modules.
+ '';
+ };
+
+ installer.cloneConfigIncludes = mkOption {
+ default = [];
+ example = [ "./nixos/modules/hardware/network/rt73.nix" ];
+ description = ''
+ List of modules used to re-build this installation device profile.
+ '';
+ };
+
+ installer.cloneConfigExtra = mkOption {
+ default = "";
+ description = ''
+ Extra text to include in the cloned configuration.nix included in this
+ installer.
+ '';
+ };
+ };
+
+ config = {
+
+ installer.cloneConfigIncludes =
+ relocatedModuleFiles.nixos ++ relocatedModuleFiles.others;
+
+ boot.postBootCommands =
+ ''
+ # Provide a mount point for nixos-install.
+ mkdir -p /mnt
+
+ ${optionalString config.installer.cloneConfig ''
+ # Provide a configuration for the CD/DVD itself, to allow users
+ # to run nixos-rebuild to change the configuration of the
+ # running system on the CD/DVD.
+ if ! [ -e /etc/nixos/configuration.nix ]; then
+ cp ${configClone} /etc/nixos/configuration.nix
+ fi
+ ''}
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/profiles/demo.nix b/nixpkgs/nixos/modules/profiles/demo.nix
new file mode 100644
index 00000000000..18f190071ba
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/demo.nix
@@ -0,0 +1,19 @@
+{ ... }:
+
+{
+ imports = [ ./graphical.nix ];
+
+ users.users.demo =
+ { isNormalUser = true;
+ description = "Demo user account";
+ extraGroups = [ "wheel" ];
+ password = "demo";
+ uid = 1000;
+ };
+
+ services.xserver.displayManager.sddm.autoLogin = {
+ enable = true;
+ relogin = true;
+ user = "demo";
+ };
+}
diff --git a/nixpkgs/nixos/modules/profiles/docker-container.nix b/nixpkgs/nixos/modules/profiles/docker-container.nix
new file mode 100644
index 00000000000..5d6b11498b5
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/docker-container.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ pkgs2storeContents = l : map (x: { object = x; symlink = "none"; }) l;
+
+in {
+ # Docker image config.
+ imports = [
+ ../installer/cd-dvd/channel.nix
+ ./minimal.nix
+ ./clone-config.nix
+ ];
+
+ # Create the tarball
+ system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
+ contents = [
+ {
+ source = "${config.system.build.toplevel}/.";
+ target = "./";
+ }
+ ];
+ extraArgs = "--owner=0";
+
+ # Add init script to image
+ storeContents = pkgs2storeContents [
+ config.system.build.toplevel
+ pkgs.stdenv
+ ];
+
+ # Some container managers like lxc need these
+ extraCommands = "mkdir -p proc sys dev";
+ };
+
+ boot.isContainer = true;
+ boot.postBootCommands =
+ ''
+ # After booting, register the contents of the Nix store in the Nix
+ # database.
+ if [ -f /nix-path-registration ]; then
+ ${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration &&
+ rm /nix-path-registration
+ fi
+
+ # nixos-rebuild also requires a "system" profile
+ ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
+ '';
+
+ # Install new init script
+ system.activationScripts.installInitScript = ''
+ ln -fs $systemConfig/init /init
+ '';
+}
diff --git a/nixpkgs/nixos/modules/profiles/graphical.nix b/nixpkgs/nixos/modules/profiles/graphical.nix
new file mode 100644
index 00000000000..649f5564ac6
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/graphical.nix
@@ -0,0 +1,22 @@
+# This module defines a NixOS configuration with the Plasma 5 desktop.
+# It's used by the graphical installation CD.
+
+{ pkgs, ... }:
+
+{
+ services.xserver = {
+ enable = true;
+ displayManager.sddm.enable = true;
+ desktopManager.plasma5 = {
+ enable = true;
+ enableQt4Support = false;
+ };
+ libinput.enable = true; # for touchpad support on many laptops
+ };
+
+ # Enable sound in virtualbox appliances.
+ hardware.pulseaudio.enable = true;
+ hardware.pulseaudio.systemWide = true; # Needed since we run plasma as root.
+
+ environment.systemPackages = [ pkgs.glxinfo pkgs.firefox ];
+}
diff --git a/nixpkgs/nixos/modules/profiles/hardened.nix b/nixpkgs/nixos/modules/profiles/hardened.nix
new file mode 100644
index 00000000000..626d8b1d2bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/hardened.nix
@@ -0,0 +1,122 @@
+# A profile with most (vanilla) hardening options enabled by default,
+# potentially at the cost of features and performance.
+
+{ lib, pkgs, ... }:
+
+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.forcePageTableIsolation = mkDefault true;
+
+ security.virtualisation.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"
+
+ # Enable page allocator randomization
+ "page_alloc.shuffle=1"
+ ];
+
+ boot.blacklistedKernelModules = [
+ # Obscure network protocols
+ "ax25"
+ "netrom"
+ "rose"
+ ];
+
+ # Restrict ptrace() usage to processes with a pre-defined relationship
+ # (e.g., parent/child)
+ boot.kernel.sysctl."kernel.yama.ptrace_scope" = mkOverride 500 1;
+
+ # Restrict access to kernel ring buffer (information leaks)
+ boot.kernel.sysctl."kernel.dmesg_restrict" = mkDefault true;
+
+ # Hide kptrs even for processes with CAP_SYSLOG
+ boot.kernel.sysctl."kernel.kptr_restrict" = mkOverride 500 2;
+
+ # Unprivileged access to bpf() has been used for privilege escalation in
+ # the past
+ boot.kernel.sysctl."kernel.unprivileged_bpf_disabled" = mkDefault true;
+
+ # Disable bpf() JIT (to eliminate spray attacks)
+ boot.kernel.sysctl."net.core.bpf_jit_enable" = mkDefault false;
+
+ # ... or at least apply some hardening to it
+ boot.kernel.sysctl."net.core.bpf_jit_harden" = mkDefault true;
+
+ # Raise ASLR entropy for 64bit & 32bit, respectively.
+ #
+ # Note: mmap_rnd_compat_bits may not exist on 64bit.
+ boot.kernel.sysctl."vm.mmap_rnd_bits" = mkDefault 32;
+ boot.kernel.sysctl."vm.mmap_rnd_compat_bits" = mkDefault 16;
+
+ # Allowing users to mmap() memory starting at virtual address 0 can turn a
+ # NULL dereference bug in the kernel into code execution with elevated
+ # privilege. Mitigate by enforcing a minimum base addr beyond the NULL memory
+ # space. This breaks applications that require mapping the 0 page, such as
+ # dosemu or running 16bit applications under wine. It also breaks older
+ # versions of qemu.
+ #
+ # The value is taken from the KSPP recommendations (Debian uses 4096).
+ boot.kernel.sysctl."vm.mmap_min_addr" = mkDefault 65536;
+
+ # Disable ftrace debugging
+ boot.kernel.sysctl."kernel.ftrace_enabled" = mkDefault false;
+
+ # Enable strict reverse path filtering (that is, do not attempt to route
+ # packets that "obviously" do not belong to the iface's network; dropped
+ # packets are logged as martians).
+ boot.kernel.sysctl."net.ipv4.conf.all.log_martians" = mkDefault true;
+ boot.kernel.sysctl."net.ipv4.conf.all.rp_filter" = mkDefault "1";
+ boot.kernel.sysctl."net.ipv4.conf.default.log_martians" = mkDefault true;
+ boot.kernel.sysctl."net.ipv4.conf.default.rp_filter" = mkDefault "1";
+
+ # Ignore broadcast ICMP (mitigate SMURF)
+ boot.kernel.sysctl."net.ipv4.icmp_echo_ignore_broadcasts" = mkDefault true;
+
+ # Ignore incoming ICMP redirects (note: default is needed to ensure that the
+ # setting is applied to interfaces added after the sysctls are set)
+ boot.kernel.sysctl."net.ipv4.conf.all.accept_redirects" = mkDefault false;
+ boot.kernel.sysctl."net.ipv4.conf.all.secure_redirects" = mkDefault false;
+ boot.kernel.sysctl."net.ipv4.conf.default.accept_redirects" = mkDefault false;
+ boot.kernel.sysctl."net.ipv4.conf.default.secure_redirects" = mkDefault false;
+ boot.kernel.sysctl."net.ipv6.conf.all.accept_redirects" = mkDefault false;
+ boot.kernel.sysctl."net.ipv6.conf.default.accept_redirects" = mkDefault false;
+
+ # Ignore outgoing ICMP redirects (this is ipv4 only)
+ boot.kernel.sysctl."net.ipv4.conf.all.send_redirects" = mkDefault false;
+ boot.kernel.sysctl."net.ipv4.conf.default.send_redirects" = mkDefault false;
+
+ # Restrict userfaultfd syscalls to processes with the SYS_PTRACE capability
+ boot.kernel.sysctl."vm.unprivileged_userfaultfd" = mkDefault false;
+}
diff --git a/nixpkgs/nixos/modules/profiles/headless.nix b/nixpkgs/nixos/modules/profiles/headless.nix
new file mode 100644
index 00000000000..46a9b6a7d8d
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/headless.nix
@@ -0,0 +1,25 @@
+# Common configuration for headless machines (e.g., Amazon EC2
+# instances).
+
+{ lib, ... }:
+
+with lib;
+
+{
+ boot.vesa = false;
+
+ # Don't start a tty on the serial consoles.
+ systemd.services."serial-getty@ttyS0".enable = false;
+ systemd.services."serial-getty@hvc0".enable = false;
+ systemd.services."getty@tty1".enable = false;
+ systemd.services."autovt@".enable = false;
+
+ # Since we can't manually respond to a panic, just reboot.
+ boot.kernelParams = [ "panic=1" "boot.panic_on_fail" ];
+
+ # Don't allow emergency mode, because we don't have a console.
+ systemd.enableEmergencyMode = false;
+
+ # Being headless, we don't need a GRUB splash image.
+ boot.loader.grub.splashImage = null;
+}
diff --git a/nixpkgs/nixos/modules/profiles/installation-device.nix b/nixpkgs/nixos/modules/profiles/installation-device.nix
new file mode 100644
index 00000000000..fd30220ce1c
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/installation-device.nix
@@ -0,0 +1,109 @@
+# Provide a basic configuration for installation devices like CDs.
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+ imports =
+ [ # Enable devices which are usually scanned, because we don't know the
+ # target system.
+ ../installer/scan/detected.nix
+ ../installer/scan/not-detected.nix
+
+ # Allow "nixos-rebuild" to work properly by providing
+ # /etc/nixos/configuration.nix.
+ ./clone-config.nix
+
+ # Include a copy of Nixpkgs so that nixos-install works out of
+ # the box.
+ ../installer/cd-dvd/channel.nix
+ ];
+
+ config = {
+
+ # Enable in installer, even if the minimal profile disables it.
+ 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.
+ #services.rogue.enable = true;
+
+ # Disable some other stuff we don't need.
+ services.udisks2.enable = mkDefault false;
+
+ # Use less privileged nixos user
+ users.users.nixos = {
+ isNormalUser = true;
+ extraGroups = [ "wheel" "networkmanager" "video" ];
+ # Allow the graphical user to login without password
+ initialHashedPassword = "";
+ };
+
+ # Allow the user to log in as root without a password.
+ users.users.root.initialHashedPassword = "";
+
+ # Allow passwordless sudo from nixos user
+ security.sudo = {
+ enable = mkDefault true;
+ wheelNeedsPassword = mkForce false;
+ };
+
+ # Automatically log in at the virtual consoles.
+ services.mingetty.autologinUser = "nixos";
+
+ # Some more help text.
+ services.mingetty.helpLine = ''
+ The "nixos" and "root" accounts have empty passwords.
+
+ Type `sudo systemctl start sshd` to start the SSH daemon.
+ You then must set a password for either "root" or "nixos"
+ with `passwd` to be able to login.
+ '' + optionalString config.services.xserver.enable ''
+ Type `sudo systemctl start display-manager' to
+ start the graphical user interface.
+ '';
+
+ # Allow sshd to be started manually through "systemctl start sshd".
+ services.openssh = {
+ enable = true;
+ # Allow password login to the installation, if the user sets a password via "passwd"
+ # It is safe as root doesn't have a password by default and SSH is disabled by default
+ permitRootLogin = "yes";
+ };
+ systemd.services.sshd.wantedBy = mkOverride 50 [];
+
+ # Enable wpa_supplicant, but don't start it by default.
+ networking.wireless.enable = mkDefault true;
+ systemd.services.wpa_supplicant.wantedBy = mkOverride 50 [];
+
+ # 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 = "1M";
+
+ # Make the installer more likely to succeed in low memory
+ # environments. The kernel's overcommit heustistics bite us
+ # fairly often, preventing processes such as nix-worker or
+ # download-using-manifests.pl from forking even if there is
+ # plenty of free memory.
+ boot.kernel.sysctl."vm.overcommit_memory" = "1";
+
+ # To speed up installation a little bit, include the complete
+ # stdenv in the Nix store on the CD.
+ system.extraDependencies = with pkgs;
+ [
+ stdenv
+ stdenvNoCC # for runCommand
+ busybox
+ jq # for closureInfo
+ ];
+
+ # Show all debug messages from the kernel but don't log refused packets
+ # because we have the firewall enabled. This makes installs from the
+ # console less cumbersome if the machine has a public IP.
+ networking.firewall.logRefusedConnections = mkDefault false;
+ };
+}
diff --git a/nixpkgs/nixos/modules/profiles/minimal.nix b/nixpkgs/nixos/modules/profiles/minimal.nix
new file mode 100644
index 00000000000..f044e6f39ea
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/minimal.nix
@@ -0,0 +1,17 @@
+# This module defines a small NixOS configuration. It does not
+# contain any graphical stuff.
+
+{ config, lib, ... }:
+
+with lib;
+
+{
+ environment.noXlibs = mkDefault true;
+
+ # This isn't perfect, but let's expect the user specifies an UTF-8 defaultLocale
+ i18n.supportedLocales = [ (config.i18n.defaultLocale + "/UTF-8") ];
+
+ documentation.enable = mkDefault false;
+
+ documentation.nixos.enable = mkDefault false;
+}
diff --git a/nixpkgs/nixos/modules/profiles/qemu-guest.nix b/nixpkgs/nixos/modules/profiles/qemu-guest.nix
new file mode 100644
index 00000000000..0ea70107f71
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/qemu-guest.nix
@@ -0,0 +1,19 @@
+# Common configuration for virtual machines running under QEMU (using
+# virtio).
+
+{ lib, ... }:
+
+{
+ boot.initrd.availableKernelModules = [ "virtio_net" "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_scsi" "9p" "9pnet_virtio" ];
+ boot.initrd.kernelModules = [ "virtio_balloon" "virtio_console" "virtio_rng" ];
+
+ boot.initrd.postDeviceCommands =
+ ''
+ # Set the system time from the hardware clock to work around a
+ # bug in qemu-kvm > 1.5.2 (where the VM clock is initialised
+ # to the *boot time* of the host).
+ hwclock -s
+ '';
+
+ security.rngd.enable = lib.mkDefault false;
+}
diff --git a/nixpkgs/nixos/modules/programs/adb.nix b/nixpkgs/nixos/modules/programs/adb.nix
new file mode 100644
index 00000000000..250d8c252a3
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/adb.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.mic92 ];
+
+ ###### interface
+ options = {
+ programs.adb = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to configure system to use Android Debug Bridge (adb).
+ To grant access to a user, it must be part of adbusers group:
+ <code>users.users.alice.extraGroups = ["adbusers"];</code>
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkIf config.programs.adb.enable {
+ services.udev.packages = [ pkgs.android-udev-rules ];
+ environment.systemPackages = [ pkgs.androidenv.androidPkgs_9_0.platform-tools ];
+ users.groups.adbusers = {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/atop.nix b/nixpkgs/nixos/modules/programs/atop.nix
new file mode 100644
index 00000000000..7ef8d687ca1
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/atop.nix
@@ -0,0 +1,36 @@
+# Global configuration for atop.
+
+{ config, lib, ... }:
+
+with lib;
+
+let cfg = config.programs.atop;
+
+in
+{
+ ###### interface
+
+ options = {
+
+ programs.atop = {
+
+ settings = mkOption {
+ type = types.attrs;
+ default = {};
+ example = {
+ flags = "a1f";
+ interval = 5;
+ };
+ description = ''
+ Parameters to be written to <filename>/etc/atoprc</filename>.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf (cfg.settings != {}) {
+ environment.etc.atoprc.text =
+ concatStrings (mapAttrsToList (n: v: "${n} ${toString v}\n") cfg.settings);
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/autojump.nix b/nixpkgs/nixos/modules/programs/autojump.nix
new file mode 100644
index 00000000000..3a8feec4bb4
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/autojump.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.autojump;
+ prg = config.programs;
+in
+{
+ options = {
+ programs.autojump = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable autojump.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ environment.pathsToLink = [ "/share/autojump" ];
+ environment.systemPackages = [ pkgs.autojump ];
+
+ programs.bash.interactiveShellInit = "source ${pkgs.autojump}/share/autojump/autojump.bash";
+ programs.zsh.interactiveShellInit = mkIf prg.zsh.enable "source ${pkgs.autojump}/share/autojump/autojump.zsh";
+ programs.fish.interactiveShellInit = mkIf prg.fish.enable "source ${pkgs.autojump}/share/autojump/autojump.fish";
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/bash/bash.nix b/nixpkgs/nixos/modules/programs/bash/bash.nix
new file mode 100644
index 00000000000..548babac38c
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/bash/bash.nix
@@ -0,0 +1,236 @@
+# This module defines global configuration for the Bash shell, in
+# particular /etc/bashrc and /etc/profile.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfge = config.environment;
+
+ cfg = config.programs.bash;
+
+ bashCompletion = optionalString cfg.enableCompletion ''
+ # Check whether we're running a version of Bash that has support for
+ # 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/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"
+ nullglobStatus=$(shopt -p nullglob)
+ shopt -s nullglob
+ for p in $NIX_PROFILES; do
+ for m in "$p/etc/bash_completion.d/"*; do
+ . $m
+ done
+ done
+ eval "$nullglobStatus"
+ unset nullglobStatus p m
+ fi
+ '';
+
+ bashAliases = concatStringsSep "\n" (
+ mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
+ (filterAttrs (k: v: v != null) cfg.shellAliases)
+ );
+
+in
+
+{
+ options = {
+
+ programs.bash = {
+
+ /*
+ enable = mkOption {
+ default = true;
+ description = ''
+ Whenever to configure Bash as an interactive shell.
+ Note that this tries to make Bash the default
+ <option>users.defaultUserShell</option>,
+ which in turn means that you might need to explicitly
+ set this variable if you have another shell configured
+ with NixOS.
+ '';
+ type = types.bool;
+ };
+ */
+
+ shellAliases = mkOption {
+ default = {};
+ description = ''
+ Set of aliases for bash shell, which overrides <option>environment.shellAliases</option>.
+ See <option>environment.shellAliases</option> for an option format description.
+ '';
+ type = with types; attrsOf (nullOr (either str path));
+ };
+
+ shellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during bash shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ loginShellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during login bash shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ interactiveShellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during interactive bash shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ promptInit = mkOption {
+ default = ''
+ # Provide a nice prompt if the terminal supports it.
+ if [ "$TERM" != "dumb" -o -n "$INSIDE_EMACS" ]; then
+ PROMPT_COLOR="1;31m"
+ let $UID && PROMPT_COLOR="1;32m"
+ if [ -n "$INSIDE_EMACS" -o "$TERM" == "eterm" -o "$TERM" == "eterm-color" ]; then
+ # Emacs term mode doesn't support xterm title escape sequence (\e]0;)
+ PS1="\n\[\033[$PROMPT_COLOR\][\u@\h:\w]\\$\[\033[0m\] "
+ else
+ 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
+ fi
+ '';
+ description = ''
+ Shell script code used to initialise the bash prompt.
+ '';
+ type = types.lines;
+ };
+
+ enableCompletion = mkOption {
+ default = true;
+ description = ''
+ Enable Bash completion for all interactive bash shells.
+ '';
+ type = types.bool;
+ };
+
+ };
+
+ };
+
+ config = /* mkIf cfg.enable */ {
+
+ programs.bash = {
+
+ shellAliases = mapAttrs (name: mkDefault) cfge.shellAliases;
+
+ shellInit = ''
+ if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then
+ . ${config.system.build.setEnvironment}
+ fi
+
+ ${cfge.shellInit}
+ '';
+
+ loginShellInit = cfge.loginShellInit;
+
+ interactiveShellInit = ''
+ # Check the window size after every command.
+ shopt -s checkwinsize
+
+ # Disable hashing (i.e. caching) of command lookups.
+ set +h
+
+ ${cfg.promptInit}
+ ${bashCompletion}
+ ${bashAliases}
+
+ ${cfge.interactiveShellInit}
+ '';
+
+ };
+
+ environment.etc.profile.text =
+ ''
+ # /etc/profile: DO NOT EDIT -- this file has been generated automatically.
+ # This file is read for login shells.
+
+ # Only execute this file once per shell.
+ if [ -n "$__ETC_PROFILE_SOURCED" ]; then return; fi
+ __ETC_PROFILE_SOURCED=1
+
+ # Prevent this file from being sourced by interactive non-login child shells.
+ export __ETC_PROFILE_DONE=1
+
+ ${cfg.shellInit}
+ ${cfg.loginShellInit}
+
+ # Read system-wide modifications.
+ if test -f /etc/profile.local; then
+ . /etc/profile.local
+ fi
+
+ if [ -n "''${BASH_VERSION:-}" ]; then
+ . /etc/bashrc
+ fi
+ '';
+
+ environment.etc.bashrc.text =
+ ''
+ # /etc/bashrc: DO NOT EDIT -- this file has been generated automatically.
+
+ # Only execute this file once per shell.
+ if [ -n "$__ETC_BASHRC_SOURCED" -o -n "$NOSYSBASHRC" ]; then return; fi
+ __ETC_BASHRC_SOURCED=1
+
+ # If the profile was not loaded in a parent process, source
+ # it. But otherwise don't do it because we don't want to
+ # clobber overridden values of $PATH, etc.
+ if [ -z "$__ETC_PROFILE_DONE" ]; then
+ . /etc/profile
+ fi
+
+ # We are not always an interactive shell.
+ if [ -n "$PS1" ]; then
+ ${cfg.interactiveShellInit}
+ fi
+
+ # Read system-wide modifications.
+ if test -f /etc/bashrc.local; then
+ . /etc/bashrc.local
+ fi
+ '';
+
+ # Configuration for readline in bash. We use "option default"
+ # priority to allow user override using both .text and .source.
+ environment.etc.inputrc.source = mkOptionDefault ./inputrc;
+
+ users.defaultUserShell = mkDefault pkgs.bashInteractive;
+
+ environment.pathsToLink = optionals cfg.enableCompletion [
+ "/etc/bash_completion.d"
+ "/share/bash-completion"
+ ];
+
+ environment.systemPackages = optional cfg.enableCompletion
+ pkgs.nix-bash-completions;
+
+ environment.shells =
+ [ "/run/current-system/sw/bin/bash"
+ "/run/current-system/sw/bin/sh"
+ "${pkgs.bashInteractive}/bin/bash"
+ "${pkgs.bashInteractive}/bin/sh"
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/bash/inputrc b/nixpkgs/nixos/modules/programs/bash/inputrc
new file mode 100644
index 00000000000..f339eb649ed
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/bash/inputrc
@@ -0,0 +1,37 @@
+# inputrc borrowed from CentOS (RHEL).
+
+set bell-style none
+
+set meta-flag on
+set input-meta on
+set convert-meta off
+set output-meta on
+set colored-stats on
+
+#set mark-symlinked-directories on
+
+$if mode=emacs
+
+# for linux console and RH/Debian xterm
+"\e[1~": beginning-of-line
+"\e[4~": end-of-line
+"\e[5~": beginning-of-history
+"\e[6~": end-of-history
+"\e[3~": delete-char
+"\e[2~": quoted-insert
+"\e[5C": forward-word
+"\e[5D": backward-word
+"\e[1;5C": forward-word
+"\e[1;5D": backward-word
+
+# for rxvt
+"\e[8~": end-of-line
+
+# for non RH/Debian xterm, can't hurt for RH/DEbian xterm
+"\eOH": beginning-of-line
+"\eOF": end-of-line
+
+# for freebsd console
+"\e[H": beginning-of-line
+"\e[F": end-of-line
+$endif
diff --git a/nixpkgs/nixos/modules/programs/bcc.nix b/nixpkgs/nixos/modules/programs/bcc.nix
new file mode 100644
index 00000000000..d76249bb5ca
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/bcc.nix
@@ -0,0 +1,9 @@
+{ config, lib, ... }:
+{
+ options.programs.bcc.enable = lib.mkEnableOption "bcc";
+
+ config = lib.mkIf config.programs.bcc.enable {
+ environment.systemPackages = [ config.boot.kernelPackages.bcc ];
+ boot.extraModulePackages = [ config.boot.kernelPackages.bcc ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/blcr.nix b/nixpkgs/nixos/modules/programs/blcr.nix
new file mode 100644
index 00000000000..804e1d01f12
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/blcr.nix
@@ -0,0 +1,27 @@
+{ config, lib, ... }:
+
+let
+ inherit (lib) mkOption mkIf;
+ cfg = config.environment.blcr;
+ blcrPkg = config.boot.kernelPackages.blcr;
+in
+
+{
+ ###### interface
+
+ options = {
+ environment.blcr.enable = mkOption {
+ default = false;
+ description =
+ "Whether to enable support for the BLCR checkpointing tool.";
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ boot.kernelModules = [ "blcr" "blcr_imports" ];
+ boot.extraModulePackages = [ blcrPkg ];
+ environment.systemPackages = [ blcrPkg ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/browserpass.nix b/nixpkgs/nixos/modules/programs/browserpass.nix
new file mode 100644
index 00000000000..e1456d3c184
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/browserpass.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ options.programs.browserpass.enable = mkEnableOption "Browserpass native messaging host";
+
+ config = mkIf config.programs.browserpass.enable {
+ environment.etc = let
+ appId = "com.github.browserpass.native.json";
+ source = part: "${pkgs.browserpass}/lib/browserpass/${part}/${appId}";
+ in {
+ # chromium
+ "chromium/native-messaging-hosts/${appId}".source = source "hosts/chromium";
+ "chromium/policies/managed/${appId}".source = source "policies/chromium";
+
+ # chrome
+ "opt/chrome/native-messaging-hosts/${appId}".source = source "hosts/chromium";
+ "opt/chrome/policies/managed/${appId}".source = source "policies/chromium";
+
+ # vivaldi
+ "opt/vivaldi/native-messaging-hosts/${appId}".source = source "hosts/chromium";
+ "opt/vivaldi/policies/managed/${appId}".source = source "policies/chromium";
+
+ # brave
+ "opt/brave/native-messaging-hosts/${appId}".source = source "hosts/chromium";
+ "opt/brave/policies/managed/${appId}".source = source "policies/chromium";
+ };
+ nixpkgs.config.firefox.enableBrowserpass = true;
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/captive-browser.nix b/nixpkgs/nixos/modules/programs/captive-browser.nix
new file mode 100644
index 00000000000..55d474e5c9d
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/captive-browser.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.captive-browser;
+in
+{
+ ###### interface
+
+ options = {
+ programs.captive-browser = {
+ enable = mkEnableOption "captive browser";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.captive-browser;
+ defaultText = "pkgs.captive-browser";
+ description = "Which package to use for captive-browser";
+ };
+
+ interface = mkOption {
+ type = types.str;
+ description = "your public network interface (wlp3s0, wlan0, eth0, ...)";
+ };
+
+ # the options below are the same as in "captive-browser.toml"
+ browser = mkOption {
+ type = types.str;
+ default = concatStringsSep " " [ ''${pkgs.chromium}/bin/chromium''
+ ''--user-data-dir=$HOME/.chromium-captive''
+ ''--proxy-server="socks5://$PROXY"''
+ ''--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE localhost"''
+ ''--no-first-run''
+ ''--new-window''
+ ''--incognito''
+ ''http://cache.nixos.org/''
+ ];
+ description = ''
+ The shell (/bin/sh) command executed once the proxy starts.
+ When browser exits, the proxy exits. An extra env var PROXY is available.
+
+ Here, we use a separate Chrome instance in Incognito mode, so that
+ it can run (and be waited for) alongside the default one, and that
+ it maintains no state across runs. To configure this browser open a
+ normal window in it, settings will be preserved.
+
+ @volth: chromium is to open a plain HTTP (not HTTPS nor redirect to HTTPS!) website.
+ upstream uses http://example.com but I have seen captive portals whose DNS server resolves "example.com" to 127.0.0.1
+ '';
+ };
+
+ dhcp-dns = mkOption {
+ type = types.str;
+ description = ''
+ The shell (/bin/sh) command executed to obtain the DHCP
+ DNS server address. The first match of an IPv4 regex is used.
+ IPv4 only, because let's be real, it's a captive portal.
+ '';
+ };
+
+ socks5-addr = mkOption {
+ type = types.str;
+ default = "localhost:1666";
+ description = ''the listen address for the SOCKS5 proxy server'';
+ };
+
+ bindInterface = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Binds <package>captive-browser</package> to the network interface declared in
+ <literal>cfg.interface</literal>. This can be used to avoid collisions
+ with private subnets.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ programs.captive-browser.dhcp-dns = mkOptionDefault (
+ if config.networking.networkmanager.enable then
+ "${pkgs.networkmanager}/bin/nmcli dev show ${escapeShellArg cfg.interface} | ${pkgs.gnugrep}/bin/fgrep IP4.DNS"
+ else if config.networking.dhcpcd.enable then
+ "${pkgs.dhcpcd}/bin/dhcpcd -U ${escapeShellArg cfg.interface} | ${pkgs.gnugrep}/bin/fgrep domain_name_servers"
+ else if config.networking.useNetworkd then
+ "${cfg.package}/bin/systemd-networkd-dns ${escapeShellArg cfg.interface}"
+ else
+ "${config.security.wrapperDir}/udhcpc --quit --now -f -i ${escapeShellArg cfg.interface} -O dns --script ${
+ pkgs.writeScript "udhcp-script" ''
+ #!/bin/sh
+ if [ "$1" = bound ]; then
+ echo "$dns"
+ fi
+ ''}"
+ );
+
+ security.wrappers.udhcpc = {
+ capabilities = "cap_net_raw+p";
+ source = "${pkgs.busybox}/bin/udhcpc";
+ };
+
+ security.wrappers.captive-browser = {
+ capabilities = "cap_net_raw+p";
+ source = pkgs.writeScript "captive-browser" ''
+ #!${pkgs.bash}/bin/bash
+ export XDG_CONFIG_HOME=${pkgs.writeTextDir "captive-browser.toml" ''
+ browser = """${cfg.browser}"""
+ dhcp-dns = """${cfg.dhcp-dns}"""
+ socks5-addr = """${cfg.socks5-addr}"""
+ ${optionalString cfg.bindInterface ''
+ bind-device = """${cfg.interface}"""
+ ''}
+ ''}
+ exec ${cfg.package}/bin/captive-browser
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/ccache.nix b/nixpkgs/nixos/modules/programs/ccache.nix
new file mode 100644
index 00000000000..874774c72b4
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/ccache.nix
@@ -0,0 +1,83 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+ cfg = config.programs.ccache;
+in {
+ options.programs.ccache = {
+ # host configuration
+ enable = mkEnableOption "CCache";
+ cacheDir = mkOption {
+ type = types.path;
+ description = "CCache directory";
+ default = "/var/cache/ccache";
+ };
+ # target configuration
+ packageNames = mkOption {
+ type = types.listOf types.str;
+ description = "Nix top-level packages to be compiled using CCache";
+ default = [];
+ example = [ "wxGTK30" "qt48" "ffmpeg_3_3" "libav_all" ];
+ };
+ };
+
+ config = mkMerge [
+ # host configuration
+ (mkIf cfg.enable {
+ systemd.tmpfiles.rules = [ "d ${cfg.cacheDir} 0770 root nixbld -" ];
+
+ # "nix-ccache --show-stats" and "nix-ccache --clear"
+ security.wrappers.nix-ccache = {
+ group = "nixbld";
+ setgid = true;
+ source = pkgs.writeScript "nix-ccache.pl" ''
+ #!${pkgs.perl}/bin/perl
+
+ %ENV=( CCACHE_DIR => '${cfg.cacheDir}' );
+ sub untaint {
+ my $v = shift;
+ return '-C' if $v eq '-C' || $v eq '--clear';
+ return '-V' if $v eq '-V' || $v eq '--version';
+ return '-s' if $v eq '-s' || $v eq '--show-stats';
+ return '-z' if $v eq '-z' || $v eq '--zero-stats';
+ exec('${pkgs.ccache}/bin/ccache', '-h');
+ }
+ exec('${pkgs.ccache}/bin/ccache', map { untaint $_ } @ARGV);
+ '';
+ };
+ })
+
+ # target configuration
+ (mkIf (cfg.packageNames != []) {
+ nixpkgs.overlays = [
+ (self: super: genAttrs cfg.packageNames (pn: super.${pn}.override { stdenv = builtins.trace "with ccache: ${pn}" self.ccacheStdenv; }))
+
+ (self: super: {
+ ccacheWrapper = super.ccacheWrapper.override {
+ extraConfig = ''
+ export CCACHE_COMPRESS=1
+ export CCACHE_DIR="${cfg.cacheDir}"
+ export CCACHE_UMASK=007
+ if [ ! -d "$CCACHE_DIR" ]; then
+ echo "====="
+ echo "Directory '$CCACHE_DIR' does not exist"
+ echo "Please create it with:"
+ echo " sudo mkdir -m0770 '$CCACHE_DIR'"
+ echo " sudo chown root:nixbld '$CCACHE_DIR'"
+ echo "====="
+ exit 1
+ fi
+ if [ ! -w "$CCACHE_DIR" ]; then
+ echo "====="
+ echo "Directory '$CCACHE_DIR' is not accessible for user $(whoami)"
+ echo "Please verify its access permissions"
+ echo "====="
+ exit 1
+ fi
+ '';
+ };
+ })
+ ];
+ })
+ ];
+} \ No newline at end of file
diff --git a/nixpkgs/nixos/modules/programs/cdemu.nix b/nixpkgs/nixos/modules/programs/cdemu.nix
new file mode 100644
index 00000000000..6a0185d362c
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/cdemu.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.programs.cdemu;
+in {
+
+ options = {
+ programs.cdemu = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ <command>cdemu</command> for members of
+ <option>programs.cdemu.group</option>.
+ '';
+ };
+ group = mkOption {
+ default = "cdrom";
+ description = ''
+ Group that users must be in to use <command>cdemu</command>.
+ '';
+ };
+ gui = mkOption {
+ default = true;
+ description = ''
+ Whether to install the <command>cdemu</command> GUI (gCDEmu).
+ '';
+ };
+ image-analyzer = mkOption {
+ default = true;
+ description = ''
+ Whether to install the image analyzer.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ boot = {
+ extraModulePackages = [ config.boot.kernelPackages.vhba ];
+ kernelModules = [ "vhba" ];
+ };
+
+ services = {
+ udev.extraRules = ''
+ KERNEL=="vhba_ctl", MODE="0660", OWNER="root", GROUP="${cfg.group}"
+ '';
+ dbus.packages = [ pkgs.cdemu-daemon ];
+ };
+
+ environment.systemPackages =
+ [ pkgs.cdemu-daemon pkgs.cdemu-client ]
+ ++ optional cfg.gui pkgs.gcdemu
+ ++ optional cfg.image-analyzer pkgs.image-analyzer;
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/chromium.nix b/nixpkgs/nixos/modules/programs/chromium.nix
new file mode 100644
index 00000000000..41c49db8c71
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/chromium.nix
@@ -0,0 +1,89 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.chromium;
+
+ defaultProfile = filterAttrs (k: v: v != null) {
+ HomepageLocation = cfg.homepageLocation;
+ DefaultSearchProviderSearchURL = cfg.defaultSearchProviderSearchURL;
+ DefaultSearchProviderSuggestURL = cfg.defaultSearchProviderSuggestURL;
+ ExtensionInstallForcelist = map (extension:
+ "${extension};https://clients2.google.com/service/update2/crx"
+ ) cfg.extensions;
+ };
+in
+
+{
+ ###### interface
+
+ options = {
+ programs.chromium = {
+ enable = mkEnableOption "<command>chromium</command> policies";
+
+ extensions = mkOption {
+ type = types.listOf types.str;
+ description = ''
+ List of chromium extensions to install.
+ For list of plugins ids see id in url of extensions on
+ <link xlink:href="https://chrome.google.com/webstore/category/extensions">chrome web store</link>
+ page.
+ '';
+ default = [];
+ example = literalExample ''
+ [
+ "chlffgpmiacpedhhbkiomidkjlcfhogd" # pushbullet
+ "mbniclmhobmnbdlbpiphghaielnnpgdp" # lightshot
+ "gcbommkclmclpchllfjekcdonpmejbdp" # https everywhere
+ "cjpalhdlnbpafiamejdnhcphjbkeiagm" # ublock origin
+ ]
+ '';
+ };
+
+ homepageLocation = mkOption {
+ type = types.nullOr types.str;
+ description = "Chromium default homepage";
+ default = null;
+ example = "https://nixos.org";
+ };
+
+ defaultSearchProviderSearchURL = mkOption {
+ type = types.nullOr types.str;
+ description = "Chromium default search provider url.";
+ default = null;
+ example =
+ "https://encrypted.google.com/search?q={searchTerms}&{google:RLZ}{google:originalQueryForSuggestion}{google:assistedQueryStats}{google:searchFieldtrialParameter}{google:searchClient}{google:sourceId}{google:instantExtendedEnabledParameter}ie={inputEncoding}";
+ };
+
+ defaultSearchProviderSuggestURL = mkOption {
+ type = types.nullOr types.str;
+ description = "Chromium default search provider url for suggestions.";
+ default = null;
+ example =
+ "https://encrypted.google.com/complete/search?output=chrome&q={searchTerms}";
+ };
+
+ extraOpts = mkOption {
+ type = types.attrs;
+ description = ''
+ Extra chromium policy options, see
+ <link xlink:href="https://www.chromium.org/administrators/policy-list-3">https://www.chromium.org/administrators/policy-list-3</link>
+ for a list of avalible options
+ '';
+ default = {};
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = lib.mkIf cfg.enable {
+ # for chromium
+ environment.etc."chromium/policies/managed/default.json".text = builtins.toJSON defaultProfile;
+ environment.etc."chromium/policies/managed/extra.json".text = builtins.toJSON cfg.extraOpts;
+ # for google-chrome https://www.chromium.org/administrators/linux-quick-start
+ environment.etc."opt/chrome/policies/managed/default.json".text = builtins.toJSON defaultProfile;
+ environment.etc."opt/chrome/policies/managed/extra.json".text = builtins.toJSON cfg.extraOpts;
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/clickshare.nix b/nixpkgs/nixos/modules/programs/clickshare.nix
new file mode 100644
index 00000000000..9980a7daf52
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/clickshare.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+
+{
+
+ options.programs.clickshare-csc1.enable =
+ lib.options.mkEnableOption ''
+ Barco ClickShare CSC-1 driver/client.
+ This allows users in the <literal>clickshare</literal>
+ group to access and use a ClickShare USB dongle
+ that is connected to the machine
+ '';
+
+ config = lib.modules.mkIf config.programs.clickshare-csc1.enable {
+ environment.systemPackages = [ pkgs.clickshare-csc1 ];
+ services.udev.packages = [ pkgs.clickshare-csc1 ];
+ users.groups.clickshare = {};
+ };
+
+ meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixpkgs/nixos/modules/programs/command-not-found/command-not-found.nix b/nixpkgs/nixos/modules/programs/command-not-found/command-not-found.nix
new file mode 100644
index 00000000000..656c255fcb1
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/command-not-found/command-not-found.nix
@@ -0,0 +1,95 @@
+# This module provides suggestions of packages to install if the user
+# tries to run a missing command in Bash. This is implemented using a
+# SQLite database that maps program names to Nix package names (e.g.,
+# "pdflatex" is mapped to "tetex").
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.command-not-found;
+ commandNotFound = pkgs.substituteAll {
+ name = "command-not-found";
+ dir = "bin";
+ src = ./command-not-found.pl;
+ isExecutable = true;
+ inherit (pkgs) perl;
+ inherit (cfg) dbPath;
+ perlFlags = concatStrings (map (path: "-I ${path}/${pkgs.perl.libPrefix} ")
+ [ pkgs.perlPackages.DBI pkgs.perlPackages.DBDSQLite pkgs.perlPackages.StringShellQuote ]);
+ };
+
+in
+
+{
+ options.programs.command-not-found = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether interactive shells should show which Nix package (if
+ any) provides a missing command.
+ '';
+ };
+
+ dbPath = mkOption {
+ default = "/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite" ;
+ description = ''
+ Absolute path to programs.sqlite.
+
+ By default this file will be provided by your channel
+ (nixexprs.tar.xz).
+ '';
+ type = types.path;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.bash.interactiveShellInit =
+ ''
+ # This function is called whenever a command is not found.
+ command_not_found_handle() {
+ local p=${commandNotFound}/bin/command-not-found
+ if [ -x $p -a -f ${cfg.dbPath} ]; then
+ # Run the helper program.
+ $p "$@"
+ # Retry the command if we just installed it.
+ if [ $? = 126 ]; then
+ "$@"
+ else
+ return 127
+ fi
+ else
+ echo "$1: command not found" >&2
+ return 127
+ fi
+ }
+ '';
+
+ programs.zsh.interactiveShellInit =
+ ''
+ # This function is called whenever a command is not found.
+ command_not_found_handler() {
+ local p=${commandNotFound}/bin/command-not-found
+ if [ -x $p -a -f ${cfg.dbPath} ]; then
+ # Run the helper program.
+ $p "$@"
+
+ # Retry the command if we just installed it.
+ if [ $? = 126 ]; then
+ "$@"
+ fi
+ else
+ # Indicate than there was an error so ZSH falls back to its default handler
+ echo "$1: command not found" >&2
+ return 127
+ fi
+ }
+ '';
+
+ environment.systemPackages = [ commandNotFound ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/command-not-found/command-not-found.pl b/nixpkgs/nixos/modules/programs/command-not-found/command-not-found.pl
new file mode 100644
index 00000000000..ab7aa204653
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/command-not-found/command-not-found.pl
@@ -0,0 +1,51 @@
+#! @perl@/bin/perl -w @perlFlags@
+
+use strict;
+use DBI;
+use DBD::SQLite;
+use String::ShellQuote;
+use Config;
+
+my $program = $ARGV[0];
+
+my $dbPath = "@dbPath@";
+
+my $dbh = DBI->connect("dbi:SQLite:dbname=$dbPath", "", "")
+ or die "cannot open database `$dbPath'";
+$dbh->{RaiseError} = 0;
+$dbh->{PrintError} = 0;
+
+my $system = $ENV{"NIX_SYSTEM"} // $Config{myarchname};
+
+my $res = $dbh->selectall_arrayref(
+ "select package from Programs where system = ? and name = ?",
+ { Slice => {} }, $system, $program);
+
+if (!defined $res || scalar @$res == 0) {
+ print STDERR "$program: command not found\n";
+} elsif (scalar @$res == 1) {
+ my $package = @$res[0]->{package};
+ if ($ENV{"NIX_AUTO_INSTALL"} // "") {
+ print STDERR <<EOF;
+The program ‘$program’ is currently not installed. It is provided by
+the package ‘$package’, which I will now install for you.
+EOF
+ ;
+ exit 126 if system("nix-env", "-iA", "nixos.$package") == 0;
+ } elsif ($ENV{"NIX_AUTO_RUN"} // "") {
+ exec("nix-shell", "-p", $package, "--run", shell_quote("exec", @ARGV));
+ } else {
+ print STDERR <<EOF;
+The program ‘$program’ is currently not installed. You can install it by typing:
+ nix-env -iA nixos.$package
+EOF
+ }
+} else {
+ print STDERR <<EOF;
+The program ‘$program’ is currently not installed. It is provided by
+several packages. You can install it by typing one of the following:
+EOF
+ print STDERR " nix-env -iA nixos.$_->{package}\n" foreach @$res;
+}
+
+exit 127;
diff --git a/nixpkgs/nixos/modules/programs/criu.nix b/nixpkgs/nixos/modules/programs/criu.nix
new file mode 100644
index 00000000000..48cf5c88a9f
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/criu.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.programs.criu;
+in {
+
+ options = {
+ programs.criu = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Install <command>criu</command> along with necessary kernel options.
+ '';
+ };
+ };
+ };
+ config = mkIf cfg.enable {
+ system.requiredKernelConfig = with config.lib.kernelConfig; [
+ (isYes "CHECKPOINT_RESTORE")
+ ];
+ boot.kernel.features.criu = true;
+ environment.systemPackages = [ pkgs.criu ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/dconf.nix b/nixpkgs/nixos/modules/programs/dconf.nix
new file mode 100644
index 00000000000..eeebc3558bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/dconf.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.dconf;
+
+ mkDconfProfile = name: path:
+ { source = path; target = "dconf/profile/${name}"; };
+
+in
+{
+ ###### interface
+
+ options = {
+ programs.dconf = {
+ enable = mkEnableOption "dconf";
+
+ profiles = mkOption {
+ type = types.attrsOf types.path;
+ default = {};
+ description = "Set of dconf profile files.";
+ internal = true;
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf (cfg.profiles != {} || cfg.enable) {
+ environment.etc = optionals (cfg.profiles != {})
+ (mapAttrsToList mkDconfProfile cfg.profiles);
+
+ services.dbus.packages = [ pkgs.gnome3.dconf ];
+
+ # For dconf executable
+ environment.systemPackages = [ pkgs.gnome3.dconf ];
+
+ # Needed for unwrapped applications
+ environment.variables.GIO_EXTRA_MODULES = mkIf cfg.enable [ "${pkgs.gnome3.dconf.lib}/lib/gio/modules" ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/digitalbitbox/default.nix b/nixpkgs/nixos/modules/programs/digitalbitbox/default.nix
new file mode 100644
index 00000000000..2fe0a14412c
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/digitalbitbox/default.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.digitalbitbox;
+in
+
+{
+ options.programs.digitalbitbox = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Installs the Digital Bitbox application and enables the complementary hardware module.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.digitalbitbox;
+ defaultText = "pkgs.digitalbitbox";
+ description = "The Digital Bitbox package to use. This can be used to install a package with udev rules that differ from the defaults.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ cfg.package ];
+ hardware.digitalbitbox = {
+ enable = true;
+ package = cfg.package;
+ };
+ };
+
+ meta = {
+ doc = ./doc.xml;
+ maintainers = with lib.maintainers; [ vidbina ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/digitalbitbox/doc.xml b/nixpkgs/nixos/modules/programs/digitalbitbox/doc.xml
new file mode 100644
index 00000000000..c63201628db
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/digitalbitbox/doc.xml
@@ -0,0 +1,74 @@
+<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-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
+<programlisting>
+<xref linkend="opt-programs.digitalbitbox.enable"/> = true;
+</programlisting>
+ 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
+ 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>
+ <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:
+<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:
+<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:
+<programlisting>
+programs.digitalbitbox = {
+ <link linkend="opt-programs.digitalbitbox.enable">enable</link> = true;
+ <link linkend="opt-programs.digitalbitbox.package">package</link> = pkgs.digitalbitbox.override {
+ udevRule51 = "something else";
+ };
+};
+</programlisting>
+ </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/programs/dmrconfig.nix b/nixpkgs/nixos/modules/programs/dmrconfig.nix
new file mode 100644
index 00000000000..e48a4f31837
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/programs/environment.nix b/nixpkgs/nixos/modules/programs/environment.nix
new file mode 100644
index 00000000000..fcffb213498
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/environment.nix
@@ -0,0 +1,63 @@
+# This module defines a standard configuration for NixOS global environment.
+
+# Most of the stuff here should probably be moved elsewhere sometime.
+
+{ config, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.environment;
+
+in
+
+{
+
+ config = {
+
+ environment.variables =
+ { NIXPKGS_CONFIG = "/etc/nix/nixpkgs-config.nix";
+ PAGER = mkDefault "less -R";
+ EDITOR = mkDefault "nano";
+ XDG_CONFIG_DIRS = [ "/etc/xdg" ]; # needs to be before profile-relative paths to allow changes through environment.etc
+ GTK_DATA_PREFIX = "${config.system.path}"; # needed for gtk2 apps to find themes
+ GTK_EXE_PREFIX = "${config.system.path}";
+ };
+
+ environment.profiles = mkAfter
+ [ "/nix/var/nix/profiles/default"
+ "/run/current-system/sw"
+ ];
+
+ # TODO: move most of these elsewhere
+ environment.profileRelativeSessionVariables =
+ { PATH = [ "/bin" ];
+ INFOPATH = [ "/info" "/share/info" ];
+ KDEDIRS = [ "" ];
+ STRIGI_PLUGIN_PATH = [ "/lib/strigi/" ];
+ QT_PLUGIN_PATH = [ "/lib/qt4/plugins" "/lib/kde4/plugins" ];
+ QTWEBKIT_PLUGIN_PATH = [ "/lib/mozilla/plugins/" ];
+ GTK_PATH = [ "/lib/gtk-2.0" "/lib/gtk-3.0" ];
+ XDG_CONFIG_DIRS = [ "/etc/xdg" ];
+ XDG_DATA_DIRS = [ "/share" ];
+ MOZ_PLUGIN_PATH = [ "/lib/mozilla/plugins" ];
+ LIBEXEC_PATH = [ "/lib/libexec" ];
+ };
+
+ environment.extraInit =
+ ''
+ unset ASPELL_CONF
+ for i in ${concatStringsSep " " (reverseList cfg.profiles)} ; do
+ if [ -d "$i/lib/aspell" ]; then
+ export ASPELL_CONF="dict-dir $i/lib/aspell"
+ fi
+ done
+
+ export NIX_USER_PROFILE_DIR="/nix/var/nix/profiles/per-user/$USER"
+ export NIX_PROFILES="${concatStringsSep " " (reverseList cfg.profiles)}"
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/evince.nix b/nixpkgs/nixos/modules/programs/evince.nix
new file mode 100644
index 00000000000..473fddb09d0
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/evince.nix
@@ -0,0 +1,42 @@
+# Evince.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ # Added 2019-08-09
+ imports = [
+ (mkRenamedOptionModule
+ [ "services" "gnome3" "evince" "enable" ]
+ [ "programs" "evince" "enable" ])
+ ];
+
+ ###### interface
+
+ options = {
+
+ programs.evince = {
+
+ enable = mkEnableOption
+ "Evince, the GNOME document viewer";
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.programs.evince.enable {
+
+ environment.systemPackages = [ pkgs.evince ];
+
+ services.dbus.packages = [ pkgs.evince ];
+
+ systemd.packages = [ pkgs.evince ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/file-roller.nix b/nixpkgs/nixos/modules/programs/file-roller.nix
new file mode 100644
index 00000000000..64f6a94e764
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/file-roller.nix
@@ -0,0 +1,39 @@
+# File Roller.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ # Added 2019-08-09
+ imports = [
+ (mkRenamedOptionModule
+ [ "services" "gnome3" "file-roller" "enable" ]
+ [ "programs" "file-roller" "enable" ])
+ ];
+
+ ###### interface
+
+ options = {
+
+ programs.file-roller = {
+
+ enable = mkEnableOption "File Roller, an archive manager for GNOME";
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.programs.file-roller.enable {
+
+ environment.systemPackages = [ pkgs.gnome3.file-roller ];
+
+ services.dbus.packages = [ pkgs.gnome3.file-roller ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/firejail.nix b/nixpkgs/nixos/modules/programs/firejail.nix
new file mode 100644
index 00000000000..74c3e4425a7
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/firejail.nix
@@ -0,0 +1,48 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.firejail;
+
+ wrappedBins = pkgs.stdenv.mkDerivation {
+ name = "firejail-wrapped-binaries";
+ nativeBuildInputs = with pkgs; [ makeWrapper ];
+ buildCommand = ''
+ mkdir -p $out/bin
+ ${lib.concatStringsSep "\n" (lib.mapAttrsToList (command: binary: ''
+ cat <<_EOF >$out/bin/${command}
+ #!${pkgs.stdenv.shell} -e
+ /run/wrappers/bin/firejail ${binary} "\$@"
+ _EOF
+ chmod 0755 $out/bin/${command}
+ '') cfg.wrappedBinaries)}
+ '';
+ };
+
+in {
+ options.programs.firejail = {
+ enable = mkEnableOption "firejail";
+
+ wrappedBinaries = mkOption {
+ type = types.attrs;
+ default = {};
+ description = ''
+ Wrap the binaries in firejail and place them in the global path.
+ </para>
+ <para>
+ You will get file collisions if you put the actual application binary in
+ the global environment and applications started via .desktop files are
+ not wrapped if they specify the absolute path to the binary.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ security.wrappers.firejail.source = "${lib.getBin pkgs.firejail}/bin/firejail";
+
+ environment.systemPackages = [ wrappedBins ];
+ };
+
+ meta.maintainers = with maintainers; [ peterhoeg ];
+}
diff --git a/nixpkgs/nixos/modules/programs/fish.nix b/nixpkgs/nixos/modules/programs/fish.nix
new file mode 100644
index 00000000000..87f6816e4ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/fish.nix
@@ -0,0 +1,240 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfge = config.environment;
+
+ cfg = config.programs.fish;
+
+ fishAliases = concatStringsSep "\n" (
+ mapAttrsFlatten (k: v: "alias ${k} ${escapeShellArg v}")
+ (filterAttrs (k: v: v != null) cfg.shellAliases)
+ );
+
+in
+
+{
+
+ options = {
+
+ programs.fish = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to configure fish as an interactive shell.
+ '';
+ type = types.bool;
+ };
+
+ vendor.config.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether fish should source configuration snippets provided by other packages.
+ '';
+ };
+
+ vendor.completions.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether fish should use completion files provided by other packages.
+ '';
+ };
+
+ vendor.functions.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether fish should autoload fish functions provided by other packages.
+ '';
+ };
+
+ shellAliases = mkOption {
+ default = {};
+ description = ''
+ Set of aliases for fish shell, which overrides <option>environment.shellAliases</option>.
+ See <option>environment.shellAliases</option> for an option format description.
+ '';
+ type = with types; attrsOf (nullOr (either str path));
+ };
+
+ shellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during fish shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ loginShellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during fish login shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ interactiveShellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during interactive fish shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ promptInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code used to initialise fish prompt.
+ '';
+ type = types.lines;
+ };
+
+ };
+
+ };
+
+ 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;
+
+ environment.etc."fish/nixos-env-preinit.fish".text = ''
+ # This happens before $__fish_datadir/config.fish sets fish_function_path, so it is currently
+ # unset. We set it and then completely erase it, leaving its configuration to $__fish_datadir/config.fish
+ set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $__fish_datadir/functions
+
+ # source the NixOS environment config
+ if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]
+ fenv source ${config.system.build.setEnvironment}
+ end
+
+ # clear fish_function_path so that it will be correctly set when we return to $__fish_datadir/config.fish
+ set -e fish_function_path
+ '';
+
+ environment.etc."fish/config.fish".text = ''
+ # /etc/fish/config.fish: DO NOT EDIT -- this file has been generated automatically.
+
+ # if we haven't sourced the general config, do it
+ if not set -q __fish_nixos_general_config_sourced
+ set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
+ fenv source /etc/fish/foreign-env/shellInit > /dev/null
+ set -e fish_function_path[1]
+
+ ${cfg.shellInit}
+
+ # and leave a note so we don't source this config section again from
+ # this very shell (children will source the general config anew)
+ set -g __fish_nixos_general_config_sourced 1
+ end
+
+ # if we haven't sourced the login config, do it
+ status --is-login; and not set -q __fish_nixos_login_config_sourced
+ and begin
+ set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
+ fenv source /etc/fish/foreign-env/loginShellInit > /dev/null
+ set -e fish_function_path[1]
+
+ ${cfg.loginShellInit}
+
+ # and leave a note so we don't source this config section again from
+ # this very shell (children will source the general config anew)
+ set -g __fish_nixos_login_config_sourced 1
+ end
+
+ # if we haven't sourced the interactive config, do it
+ status --is-interactive; and not set -q __fish_nixos_interactive_config_sourced
+ and begin
+ ${fishAliases}
+
+ set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
+ fenv source /etc/fish/foreign-env/interactiveShellInit > /dev/null
+ set -e fish_function_path[1]
+
+ ${cfg.promptInit}
+ ${cfg.interactiveShellInit}
+
+ # and leave a note so we don't source this config section again from
+ # this very shell (children will source the general config anew,
+ # allowing configuration changes in, e.g, aliases, to propagate)
+ set -g __fish_nixos_interactive_config_sourced 1
+ end
+ '';
+
+ programs.fish.interactiveShellInit = ''
+ # add completions generated by NixOS to $fish_complete_path
+ begin
+ # joins with null byte to acommodate all characters in paths, then respectively gets all paths before (exclusive) / after (inclusive) the first one including "generated_completions",
+ # splits by null byte, and then removes all empty lines produced by using 'string'
+ set -l prev (string join0 $fish_complete_path | string match --regex "^.*?(?=\x00[^\x00]*generated_completions.*)" | string split0 | string match -er ".")
+ set -l post (string join0 $fish_complete_path | string match --regex "[^\x00]*generated_completions.*" | string split0 | string match -er ".")
+ set fish_complete_path $prev "/etc/fish/generated_completions" $post
+ end
+ '';
+
+ environment.etc."fish/generated_completions".source =
+ let
+ patchedGenerator = pkgs.stdenv.mkDerivation {
+ name = "fish_patched-completion-generator";
+ srcs = [
+ "${pkgs.fish}/share/fish/tools/create_manpage_completions.py"
+ "${pkgs.fish}/share/fish/tools/deroff.py"
+ ];
+ unpackCmd = "cp $curSrc $(basename $curSrc)";
+ sourceRoot = ".";
+ patches = [ ./fish_completion-generator.patch ]; # to prevent collisions of identical completion files
+ dontBuild = true;
+ installPhase = ''
+ mkdir -p $out
+ cp * $out/
+ '';
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ };
+ generateCompletions = package: pkgs.runCommand
+ "${package.name}_fish-completions"
+ (
+ {
+ inherit package;
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ }
+ // optionalAttrs (package ? meta.priority) { meta.priority = package.meta.priority; }
+ )
+ ''
+ mkdir -p $out
+ if [ -d $package/share/man ]; then
+ find $package/share/man -type f | xargs ${pkgs.python3.interpreter} ${patchedGenerator}/create_manpage_completions.py --directory $out >/dev/null
+ fi
+ '';
+ in
+ pkgs.buildEnv {
+ name = "system_fish-completions";
+ ignoreCollisions = true;
+ paths = map generateCompletions config.environment.systemPackages;
+ };
+
+ # include programs that bring their own completions
+ environment.pathsToLink = []
+ ++ optional cfg.vendor.config.enable "/share/fish/vendor_conf.d"
+ ++ optional cfg.vendor.completions.enable "/share/fish/vendor_completions.d"
+ ++ optional cfg.vendor.functions.enable "/share/fish/vendor_functions.d";
+
+ environment.systemPackages = [ pkgs.fish ];
+
+ environment.shells = [
+ "/run/current-system/sw/bin/fish"
+ "${pkgs.fish}/bin/fish"
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/fish_completion-generator.patch b/nixpkgs/nixos/modules/programs/fish_completion-generator.patch
new file mode 100644
index 00000000000..a8c797d185a
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/fish_completion-generator.patch
@@ -0,0 +1,11 @@
+--- a/create_manpage_completions.py
++++ b/create_manpage_completions.py
+@@ -776,8 +776,6 @@ def parse_manpage_at_path(manpage_path, output_directory):
+
+ built_command_output.insert(0, "# " + CMDNAME)
+
+- # Output the magic word Autogenerated so we can tell if we can overwrite this
+- built_command_output.insert(1, "# Autogenerated from man page " + manpage_path)
+ # built_command_output.insert(2, "# using " + parser.__class__.__name__) # XXX MISATTRIBUTES THE CULPABILE PARSER! Was really using Type2 but reporting TypeDeroffManParser
+
+ for line in built_command_output:
diff --git a/nixpkgs/nixos/modules/programs/freetds.nix b/nixpkgs/nixos/modules/programs/freetds.nix
new file mode 100644
index 00000000000..e0860a242b7
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/freetds.nix
@@ -0,0 +1,61 @@
+# Global configuration for freetds environment.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.environment.freetds;
+
+in
+{
+ ###### interface
+
+ options = {
+
+ environment.freetds = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = literalExample ''
+ { MYDATABASE = '''
+ host = 10.0.2.100
+ port = 1433
+ tds version = 7.2
+ ''';
+ }
+ '';
+ description =
+ ''
+ Configure freetds database entries. Each attribute denotes
+ a section within freetds.conf, and the value (a string) is the config
+ content for that section. When at least one entry is configured
+ the global environment variables FREETDSCONF, FREETDS and SYBASE
+ will be configured to allow the programs that use freetds to find the
+ library and config.
+ '';
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf (length (attrNames cfg) > 0) {
+
+ environment.variables.FREETDSCONF = "/etc/freetds.conf";
+ environment.variables.FREETDS = "/etc/freetds.conf";
+ environment.variables.SYBASE = "${pkgs.freetds}";
+
+ environment.etc."freetds.conf" = { text =
+ (concatStrings (mapAttrsToList (name: value:
+ ''
+ [${name}]
+ ${value}
+ ''
+ ) cfg));
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/fuse.nix b/nixpkgs/nixos/modules/programs/fuse.nix
new file mode 100644
index 00000000000..c15896efbb5
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/fuse.nix
@@ -0,0 +1,37 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.fuse;
+in {
+ meta.maintainers = with maintainers; [ primeos ];
+
+ options.programs.fuse = {
+ mountMax = mkOption {
+ # In the C code it's an "int" (i.e. signed and at least 16 bit), but
+ # negative numbers obviously make no sense:
+ type = types.ints.between 0 32767; # 2^15 - 1
+ default = 1000;
+ description = ''
+ Set the maximum number of FUSE mounts allowed to non-root users.
+ '';
+ };
+
+ userAllowOther = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow non-root users to specify the allow_other or allow_root mount
+ options, see mount.fuse3(8).
+ '';
+ };
+ };
+
+ config = {
+ environment.etc."fuse.conf".text = ''
+ ${optionalString (!cfg.userAllowOther) "#"}user_allow_other
+ mount_max = ${toString cfg.mountMax}
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/gnome-disks.nix b/nixpkgs/nixos/modules/programs/gnome-disks.nix
new file mode 100644
index 00000000000..1cf839a6ddb
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/gnome-disks.nix
@@ -0,0 +1,46 @@
+# GNOME Disks.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ # Added 2019-08-09
+ imports = [
+ (mkRenamedOptionModule
+ [ "services" "gnome3" "gnome-disks" "enable" ]
+ [ "programs" "gnome-disks" "enable" ])
+ ];
+
+ ###### interface
+
+ options = {
+
+ programs.gnome-disks = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable GNOME Disks daemon, a program designed to
+ be a UDisks2 graphical front-end.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.programs.gnome-disks.enable {
+
+ environment.systemPackages = [ pkgs.gnome3.gnome-disk-utility ];
+
+ services.dbus.packages = [ pkgs.gnome3.gnome-disk-utility ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/gnome-documents.nix b/nixpkgs/nixos/modules/programs/gnome-documents.nix
new file mode 100644
index 00000000000..bfa3d409ee3
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/gnome-documents.nix
@@ -0,0 +1,50 @@
+# GNOME Documents.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ # Added 2019-08-09
+ imports = [
+ (mkRenamedOptionModule
+ [ "services" "gnome3" "gnome-documents" "enable" ]
+ [ "programs" "gnome-documents" "enable" ])
+ ];
+
+ ###### interface
+
+ options = {
+
+ programs.gnome-documents = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable GNOME Documents, a document
+ manager application for GNOME.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.programs.gnome-documents.enable {
+
+ environment.systemPackages = [ pkgs.gnome3.gnome-documents ];
+
+ services.dbus.packages = [ pkgs.gnome3.gnome-documents ];
+
+ services.gnome3.gnome-online-accounts.enable = true;
+
+ services.gnome3.gnome-online-miners.enable = true;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/gnome-terminal.nix b/nixpkgs/nixos/modules/programs/gnome-terminal.nix
new file mode 100644
index 00000000000..0036677a157
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/gnome-terminal.nix
@@ -0,0 +1,36 @@
+# GNOME Terminal.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.gnome-terminal;
+
+in
+
+{
+
+ # Added 2019-08-19
+ imports = [
+ (mkRenamedOptionModule
+ [ "services" "gnome3" "gnome-terminal-server" "enable" ]
+ [ "programs" "gnome-terminal" "enable" ])
+ ];
+
+ options = {
+
+ programs.gnome-terminal.enable = mkEnableOption "GNOME Terminal";
+
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.gnome3.gnome-terminal ];
+ services.dbus.packages = [ pkgs.gnome3.gnome-terminal ];
+ systemd.packages = [ pkgs.gnome3.gnome-terminal ];
+
+ programs.bash.vteIntegration = true;
+ programs.zsh.vteIntegration = true;
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/gnupg.nix b/nixpkgs/nixos/modules/programs/gnupg.nix
new file mode 100644
index 00000000000..bcbc994efe9
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/gnupg.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.gnupg;
+
+in
+
+{
+
+ options.programs.gnupg = {
+ package = mkOption {
+ type = types.package;
+ default = pkgs.gnupg;
+ defaultText = "pkgs.gnupg";
+ description = ''
+ The gpg package that should be used.
+ '';
+ };
+
+ agent.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables GnuPG agent with socket-activation for every user session.
+ '';
+ };
+
+ agent.enableSSHSupport = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable SSH agent support in GnuPG agent. Also sets SSH_AUTH_SOCK
+ environment variable correctly. This will disable socket-activation
+ and thus always start a GnuPG agent per user session.
+ '';
+ };
+
+ agent.enableExtraSocket = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable extra socket for GnuPG agent.
+ '';
+ };
+
+ agent.enableBrowserSocket = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable browser socket for GnuPG agent.
+ '';
+ };
+
+ dirmngr.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables GnuPG network certificate management daemon with socket-activation for every user session.
+ '';
+ };
+ };
+
+ config = mkIf cfg.agent.enable {
+ systemd.user.sockets.gpg-agent = {
+ wantedBy = [ "sockets.target" ];
+ };
+
+ systemd.user.sockets.gpg-agent-ssh = mkIf cfg.agent.enableSSHSupport {
+ wantedBy = [ "sockets.target" ];
+ };
+
+ systemd.user.sockets.gpg-agent-extra = mkIf cfg.agent.enableExtraSocket {
+ wantedBy = [ "sockets.target" ];
+ };
+
+ systemd.user.sockets.gpg-agent-browser = mkIf cfg.agent.enableBrowserSocket {
+ wantedBy = [ "sockets.target" ];
+ };
+
+ systemd.user.sockets.dirmngr = mkIf cfg.dirmngr.enable {
+ wantedBy = [ "sockets.target" ];
+ };
+
+ environment.systemPackages = with pkgs; [ cfg.package ];
+ systemd.packages = [ cfg.package ];
+
+ environment.interactiveShellInit = ''
+ # Bind gpg-agent to this TTY if gpg commands are used.
+ export GPG_TTY=$(tty)
+
+ '' + (optionalString cfg.agent.enableSSHSupport ''
+ # SSH agent protocol doesn't support changing TTYs, so bind the agent
+ # to every new TTY.
+ ${cfg.package}/bin/gpg-connect-agent --quiet updatestartuptty /bye > /dev/null
+ '');
+
+ environment.extraInit = mkIf cfg.agent.enableSSHSupport ''
+ if [ -z "$SSH_AUTH_SOCK" ]; then
+ export SSH_AUTH_SOCK=$(${cfg.package}/bin/gpgconf --list-dirs agent-ssh-socket)
+ fi
+ '';
+
+ assertions = [
+ { assertion = cfg.agent.enableSSHSupport -> !config.programs.ssh.startAgent;
+ message = "You can't use ssh-agent and GnuPG agent with SSH support enabled at the same time!";
+ }
+ ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/gpaste.nix b/nixpkgs/nixos/modules/programs/gpaste.nix
new file mode 100644
index 00000000000..4f6deb77e5e
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/gpaste.nix
@@ -0,0 +1,34 @@
+# GPaste.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ # Added 2019-08-09
+ imports = [
+ (mkRenamedOptionModule
+ [ "services" "gnome3" "gpaste" "enable" ]
+ [ "programs" "gpaste" "enable" ])
+ ];
+
+ ###### interface
+ options = {
+ programs.gpaste = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable GPaste, a clipboard manager.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkIf config.programs.gpaste.enable {
+ environment.systemPackages = [ pkgs.gnome3.gpaste ];
+ services.dbus.packages = [ pkgs.gnome3.gpaste ];
+ systemd.packages = [ pkgs.gnome3.gpaste ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/gphoto2.nix b/nixpkgs/nixos/modules/programs/gphoto2.nix
new file mode 100644
index 00000000000..93923ff3133
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/gphoto2.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.league ];
+
+ ###### interface
+ options = {
+ programs.gphoto2 = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to configure system to use gphoto2.
+ To grant digital camera access to a user, the user must
+ be part of the camera group:
+ <code>users.users.alice.extraGroups = ["camera"];</code>
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkIf config.programs.gphoto2.enable {
+ services.udev.packages = [ pkgs.libgphoto2 ];
+ environment.systemPackages = [ pkgs.gphoto2 ];
+ users.groups.camera = {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/iftop.nix b/nixpkgs/nixos/modules/programs/iftop.nix
new file mode 100644
index 00000000000..a98a9a8187d
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/iftop.nix
@@ -0,0 +1,18 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.iftop;
+in {
+ options = {
+ programs.iftop.enable = mkEnableOption "iftop + setcap wrapper";
+ };
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.iftop ];
+ security.wrappers.iftop = {
+ source = "${pkgs.iftop}/bin/iftop";
+ capabilities = "cap_net_raw+p";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/iotop.nix b/nixpkgs/nixos/modules/programs/iotop.nix
new file mode 100644
index 00000000000..5512dbc62f7
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/programs/java.nix b/nixpkgs/nixos/modules/programs/java.nix
new file mode 100644
index 00000000000..d31698c3b39
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/java.nix
@@ -0,0 +1,58 @@
+# This module provides JAVA_HOME, with a different way to install java
+# system-wide.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.java;
+in
+
+{
+
+ options = {
+
+ programs.java = {
+
+ enable = mkEnableOption "java" // {
+ description = ''
+ Install and setup the Java development kit.
+ <note>
+ <para>This adds JAVA_HOME to the global environment, by sourcing the
+ jdk's setup-hook on shell init. It is equivalent to starting a shell
+ through 'nix-shell -p jdk', or roughly the following system-wide
+ configuration:
+ </para>
+ <programlisting>
+ environment.variables.JAVA_HOME = ''${pkgs.jdk.home}/lib/openjdk;
+ environment.systemPackages = [ pkgs.jdk ];
+ </programlisting>
+ </note>
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.jdk;
+ defaultText = "pkgs.jdk";
+ description = ''
+ Java package to install. Typical values are pkgs.jdk or pkgs.jre.
+ '';
+ type = types.package;
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ environment.shellInit = ''
+ test -e ${cfg.package}/nix-support/setup-hook && source ${cfg.package}/nix-support/setup-hook
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/kbdlight.nix b/nixpkgs/nixos/modules/programs/kbdlight.nix
new file mode 100644
index 00000000000..58e45872fac
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/kbdlight.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.kbdlight;
+
+in
+{
+ options.programs.kbdlight.enable = mkEnableOption "kbdlight";
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.kbdlight ];
+ security.wrappers.kbdlight.source = "${pkgs.kbdlight.out}/bin/kbdlight";
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/less.nix b/nixpkgs/nixos/modules/programs/less.nix
new file mode 100644
index 00000000000..75b3e707d57
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/less.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.less;
+
+ configText = if (cfg.configFile != null) then (builtins.readFile cfg.configFile) else ''
+ #command
+ ${concatStringsSep "\n"
+ (mapAttrsToList (command: action: "${command} ${action}") cfg.commands)
+ }
+ ${if cfg.clearDefaultCommands then "#stop" else ""}
+
+ #line-edit
+ ${concatStringsSep "\n"
+ (mapAttrsToList (command: action: "${command} ${action}") cfg.lineEditingKeys)
+ }
+
+ #env
+ ${concatStringsSep "\n"
+ (mapAttrsToList (variable: values: "${variable}=${values}") cfg.envVariables)
+ }
+ '';
+
+ lessKey = pkgs.runCommand "lesskey"
+ { src = pkgs.writeText "lessconfig" configText; preferLocalBuild = true; }
+ "${pkgs.less}/bin/lesskey -o $out $src";
+
+in
+
+{
+ options = {
+
+ programs.less = {
+
+ enable = mkEnableOption "less";
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = literalExample "$${pkgs.my-configs}/lesskey";
+ description = ''
+ Path to lesskey configuration file.
+
+ <option>configFile</option> takes precedence over <option>commands</option>,
+ <option>clearDefaultCommands</option>, <option>lineEditingKeys</option>, and
+ <option>envVariables</option>.
+ '';
+ };
+
+ commands = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = {
+ h = "noaction 5\\e(";
+ l = "noaction 5\\e)";
+ };
+ description = "Defines new command keys.";
+ };
+
+ clearDefaultCommands = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Clear all default commands.
+ You should remember to set the quit key.
+ Otherwise you will not be able to leave less without killing it.
+ '';
+ };
+
+ lineEditingKeys = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = {
+ e = "abort";
+ };
+ description = "Defines new line-editing keys.";
+ };
+
+ envVariables = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = {
+ LESS = "--quit-if-one-screen";
+ };
+ description = "Defines environment variables.";
+ };
+
+ lessopen = mkOption {
+ type = types.nullOr types.str;
+ default = "|${pkgs.lesspipe}/bin/lesspipe.sh %s";
+ description = ''
+ Before less opens a file, it first gives your input preprocessor a chance to modify the way the contents of the file are displayed.
+ '';
+ };
+
+ lessclose = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ When less closes a file opened in such a way, it will call another program, called the input postprocessor, which may perform any desired clean-up action (such as deleting the replacement file created by LESSOPEN).
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.less ];
+
+ environment.variables = {
+ LESSKEY_SYSTEM = toString lessKey;
+ } // optionalAttrs (cfg.lessopen != null) {
+ LESSOPEN = cfg.lessopen;
+ } // optionalAttrs (cfg.lessclose != null) {
+ LESSCLOSE = cfg.lessclose;
+ };
+
+ warnings = optional (
+ cfg.clearDefaultCommands && (all (x: x != "quit") (attrValues cfg.commands))
+ ) ''
+ config.programs.less.clearDefaultCommands clears all default commands of less but there is no alternative binding for exiting.
+ Consider adding a binding for 'quit'.
+ '';
+ };
+
+ meta.maintainers = with maintainers; [ johnazoidberg ];
+
+}
diff --git a/nixpkgs/nixos/modules/programs/light.nix b/nixpkgs/nixos/modules/programs/light.nix
new file mode 100644
index 00000000000..9f2a03e7e76
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/light.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.light;
+
+in
+{
+ options = {
+ programs.light = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to install Light backlight control command
+ and udev rules granting access to members of the "video" group.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.light ];
+ services.udev.packages = [ pkgs.light ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/mininet.nix b/nixpkgs/nixos/modules/programs/mininet.nix
new file mode 100644
index 00000000000..ecc924325e6
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/programs/mosh.nix b/nixpkgs/nixos/modules/programs/mosh.nix
new file mode 100644
index 00000000000..359fe23e0ec
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/mosh.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.mosh;
+
+in
+{
+ options.programs.mosh = {
+ enable = mkOption {
+ description = ''
+ Whether to enable mosh. Note, this will open ports in your firewall!
+ '';
+ default = false;
+ type = lib.types.bool;
+ };
+ withUtempter = mkOption {
+ description = ''
+ Whether to enable libutempter for mosh.
+ This is required so that mosh can write to /var/run/utmp (which can be queried with `who` to display currently connected user sessions).
+ Note, this will add a guid wrapper for the group utmp!
+ '';
+ default = true;
+ type = lib.types.bool;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [ mosh ];
+ networking.firewall.allowedUDPPortRanges = [ { from = 60000; to = 61000; } ];
+ security.wrappers = mkIf cfg.withUtempter {
+ utempter = {
+ source = "${pkgs.libutempter}/lib/utempter/utempter";
+ owner = "nobody";
+ group = "utmp";
+ setuid = false;
+ setgid = true;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/mtr.nix b/nixpkgs/nixos/modules/programs/mtr.nix
new file mode 100644
index 00000000000..75b710c1584
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/mtr.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.mtr;
+
+in {
+ options = {
+ programs.mtr = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to add mtr to the global environment and configure a
+ setcap wrapper for it.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.mtr;
+ description = ''
+ The package to use.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [ cfg.package ];
+
+ security.wrappers.mtr-packet = {
+ source = "${cfg.package}/bin/mtr-packet";
+ capabilities = "cap_net_raw+p";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/nano.nix b/nixpkgs/nixos/modules/programs/nano.nix
new file mode 100644
index 00000000000..5837dd46d7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/nano.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.programs.nano;
+ LF = "\n";
+in
+
+{
+ ###### interface
+
+ options = {
+ programs.nano = {
+
+ nanorc = lib.mkOption {
+ type = lib.types.lines;
+ default = "";
+ description = ''
+ The system-wide nano configuration.
+ See <citerefentry><refentrytitle>nanorc</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ '';
+ example = ''
+ set nowrap
+ set tabstospaces
+ set tabsize 2
+ '';
+ };
+ syntaxHighlight = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ description = "Whether to enable syntax highlight for various languages.";
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = lib.mkIf (cfg.nanorc != "" || cfg.syntaxHighlight) {
+ environment.etc.nanorc.text = lib.concatStrings [ cfg.nanorc
+ (lib.optionalString cfg.syntaxHighlight ''${LF}include "${pkgs.nano}/share/nano/*.nanorc"'') ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/nm-applet.nix b/nixpkgs/nixos/modules/programs/nm-applet.nix
new file mode 100644
index 00000000000..e42219e9638
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/programs/npm.nix b/nixpkgs/nixos/modules/programs/npm.nix
new file mode 100644
index 00000000000..b351d80c7ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/npm.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.npm;
+in
+
+{
+ ###### interface
+
+ options = {
+ programs.npm = {
+ enable = mkEnableOption "<command>npm</command> global config";
+
+ npmrc = lib.mkOption {
+ type = lib.types.lines;
+ description = ''
+ The system-wide npm configuration.
+ See <link xlink:href="https://docs.npmjs.com/misc/config"/>.
+ '';
+ default = ''
+ prefix = ''${HOME}/.npm
+ '';
+ example = ''
+ prefix = ''${HOME}/.npm
+ https-proxy=proxy.example.com
+ init-license=MIT
+ init-author-url=http://npmjs.org
+ color=true
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = lib.mkIf cfg.enable {
+ environment.etc.npmrc.text = cfg.npmrc;
+
+ environment.variables.NPM_CONFIG_GLOBALCONFIG = "/etc/npmrc";
+
+ environment.systemPackages = [ pkgs.nodePackages.npm ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/oblogout.nix b/nixpkgs/nixos/modules/programs/oblogout.nix
new file mode 100644
index 00000000000..720c29b1eae
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/oblogout.nix
@@ -0,0 +1,176 @@
+# Global configuration for oblogout.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.programs.oblogout;
+
+in
+{
+ ###### interface
+
+ options = {
+
+ programs.oblogout = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to install OBLogout and create <filename>/etc/oblogout.conf</filename>.
+ See <filename>${pkgs.oblogout}/share/doc/README</filename>.
+ '';
+ };
+
+ opacity = mkOption {
+ type = types.int;
+ default = 70;
+ description = ''
+ Opacity percentage of Cairo rendered backgrounds.
+ '';
+ };
+
+ bgcolor = mkOption {
+ type = types.str;
+ default = "black";
+ description = ''
+ Colour name or hex code (#ffffff) of the background color.
+ '';
+ };
+
+ buttontheme = mkOption {
+ type = types.str;
+ default = "simplistic";
+ description = ''
+ Icon theme for the buttons, must be in the themes folder of
+ the package, or in
+ <filename>~/.themes/&lt;name&gt;/oblogout/</filename>.
+ '';
+ };
+
+ buttons = mkOption {
+ type = types.str;
+ default = "cancel, logout, restart, shutdown, suspend, hibernate";
+ description = ''
+ List and order of buttons to show.
+ '';
+ };
+
+ cancel = mkOption {
+ type = types.str;
+ default = "Escape";
+ description = ''
+ Cancel logout/shutdown shortcut.
+ '';
+ };
+
+ shutdown = mkOption {
+ type = types.str;
+ default = "S";
+ description = ''
+ Shutdown shortcut.
+ '';
+ };
+
+ restart = mkOption {
+ type = types.str;
+ default = "R";
+ description = ''
+ Restart shortcut.
+ '';
+ };
+
+ suspend = mkOption {
+ type = types.str;
+ default = "U";
+ description = ''
+ Suspend shortcut.
+ '';
+ };
+
+ logout = mkOption {
+ type = types.str;
+ default = "L";
+ description = ''
+ Logout shortcut.
+ '';
+ };
+
+ lock = mkOption {
+ type = types.str;
+ default = "K";
+ description = ''
+ Lock session shortcut.
+ '';
+ };
+
+ hibernate = mkOption {
+ type = types.str;
+ default = "H";
+ description = ''
+ Hibernate shortcut.
+ '';
+ };
+
+ clogout = mkOption {
+ type = types.str;
+ default = "openbox --exit";
+ description = ''
+ Command to logout.
+ '';
+ };
+
+ clock = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Command to lock screen.
+ '';
+ };
+
+ cswitchuser = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Command to switch user.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.oblogout ];
+
+ environment.etc."oblogout.conf".text = ''
+ [settings]
+ usehal = false
+
+ [looks]
+ opacity = ${toString cfg.opacity}
+ bgcolor = ${cfg.bgcolor}
+ buttontheme = ${cfg.buttontheme}
+ buttons = ${cfg.buttons}
+
+ [shortcuts]
+ cancel = ${cfg.cancel}
+ shutdown = ${cfg.shutdown}
+ restart = ${cfg.restart}
+ suspend = ${cfg.suspend}
+ logout = ${cfg.logout}
+ lock = ${cfg.lock}
+ hibernate = ${cfg.hibernate}
+
+ [commands]
+ shutdown = systemctl poweroff
+ restart = systemctl reboot
+ suspend = systemctl suspend
+ hibernate = systemctl hibernate
+ logout = ${cfg.clogout}
+ lock = ${cfg.clock}
+ switchuser = ${cfg.cswitchuser}
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/plotinus.nix b/nixpkgs/nixos/modules/programs/plotinus.nix
new file mode 100644
index 00000000000..e3549c79588
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/plotinus.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.plotinus;
+in
+{
+ meta = {
+ maintainers = pkgs.plotinus.meta.maintainers;
+ doc = ./plotinus.xml;
+ };
+
+ ###### interface
+
+ options = {
+ programs.plotinus = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable the Plotinus GTK 3 plugin. Plotinus provides a
+ popup (triggered by Ctrl-Shift-P) to search the menus of a
+ compatible application.
+ '';
+ type = types.bool;
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ environment.variables.XDG_DATA_DIRS = [ "${pkgs.plotinus}/share/gsettings-schemas/${pkgs.plotinus.name}" ];
+ environment.variables.GTK3_MODULES = [ "${pkgs.plotinus}/lib/libplotinus.so" ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/plotinus.xml b/nixpkgs/nixos/modules/programs/plotinus.xml
new file mode 100644
index 00000000000..8fc8c22c6d7
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/plotinus.xml
@@ -0,0 +1,30 @@
+<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-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>:
+<programlisting>
+<xref linkend="opt-programs.plotinus.enable"/> = true;
+</programlisting>
+ </para>
+</chapter>
diff --git a/nixpkgs/nixos/modules/programs/qt5ct.nix b/nixpkgs/nixos/modules/programs/qt5ct.nix
new file mode 100644
index 00000000000..aeb7fc50849
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/qt5ct.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.romildo ];
+
+ ###### interface
+ options = {
+ programs.qt5ct = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable the Qt5 Configuration Tool (qt5ct), a
+ program that allows users to configure Qt5 settings (theme,
+ font, icons, etc.) under desktop environments or window
+ manager without Qt integration.
+
+ Official home page: <link xlink:href="https://sourceforge.net/projects/qt5ct/">https://sourceforge.net/projects/qt5ct/</link>
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkIf config.programs.qt5ct.enable {
+ environment.variables.QT_QPA_PLATFORMTHEME = "qt5ct";
+ environment.systemPackages = with pkgs; [ qt5ct libsForQt5.qtstyleplugins ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/screen.nix b/nixpkgs/nixos/modules/programs/screen.nix
new file mode 100644
index 00000000000..4fd800dbae7
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/screen.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkOption mkIf types;
+ cfg = config.programs.screen;
+in
+
+{
+ ###### interface
+
+ options = {
+ programs.screen = {
+
+ screenrc = mkOption {
+ default = "";
+ description = ''
+ The contents of /etc/screenrc file.
+ '';
+ type = types.lines;
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf (cfg.screenrc != "") {
+ environment.etc.screenrc.text = cfg.screenrc;
+
+ environment.systemPackages = [ pkgs.screen ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/seahorse.nix b/nixpkgs/nixos/modules/programs/seahorse.nix
new file mode 100644
index 00000000000..c08b0a85374
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/seahorse.nix
@@ -0,0 +1,44 @@
+# Seahorse.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ # Added 2019-08-27
+ imports = [
+ (mkRenamedOptionModule
+ [ "services" "gnome3" "seahorse" "enable" ]
+ [ "programs" "seahorse" "enable" ])
+ ];
+
+
+ ###### interface
+
+ options = {
+
+ programs.seahorse = {
+
+ enable = mkEnableOption "Seahorse, a GNOME application for managing encryption keys and passwords in the GNOME Keyring";
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.programs.seahorse.enable {
+
+ environment.systemPackages = [
+ pkgs.gnome3.seahorse
+ ];
+
+ services.dbus.packages = [
+ pkgs.gnome3.seahorse
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/sedutil.nix b/nixpkgs/nixos/modules/programs/sedutil.nix
new file mode 100644
index 00000000000..7efc80f4abb
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/sedutil.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.sedutil;
+
+in {
+ options.programs.sedutil.enable = mkEnableOption "sedutil";
+
+ config = mkIf cfg.enable {
+ boot.kernelParams = [
+ "libata.allow_tpm=1"
+ ];
+
+ environment.systemPackages = with pkgs; [ sedutil ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/shadow.nix b/nixpkgs/nixos/modules/programs/shadow.nix
new file mode 100644
index 00000000000..8ec4169207d
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/shadow.nix
@@ -0,0 +1,115 @@
+# Configuration for the pwdutils suite of tools: passwd, useradd, etc.
+
+{ config, lib, utils, pkgs, ... }:
+
+with lib;
+
+let
+
+ loginDefs =
+ ''
+ DEFAULT_HOME yes
+
+ SYS_UID_MIN 400
+ SYS_UID_MAX 499
+ UID_MIN 1000
+ UID_MAX 29999
+
+ SYS_GID_MIN 400
+ SYS_GID_MAX 499
+ GID_MIN 1000
+ GID_MAX 29999
+
+ TTYGROUP tty
+ TTYPERM 0620
+
+ # Ensure privacy for newly created home directories.
+ UMASK 077
+
+ # Uncomment this and install chfn SUID to allow non-root
+ # users to change their account GECOS information.
+ # This should be made configurable.
+ #CHFN_RESTRICT frwh
+
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ users.defaultUserShell = lib.mkOption {
+ description = ''
+ This option defines the default shell assigned to user
+ accounts. This can be either a full system path or a shell package.
+
+ This must not be a store path, since the path is
+ used outside the store (in particular in /etc/passwd).
+ '';
+ example = literalExample "pkgs.zsh";
+ type = types.either types.path types.shellPackage;
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ environment.systemPackages =
+ lib.optional config.users.mutableUsers pkgs.shadow ++
+ lib.optional (types.shellPackage.check config.users.defaultUserShell)
+ config.users.defaultUserShell;
+
+ environment.etc =
+ [ { # /etc/login.defs: global configuration for pwdutils. You
+ # cannot login without it!
+ source = pkgs.writeText "login.defs" loginDefs;
+ target = "login.defs";
+ }
+
+ { # /etc/default/useradd: configuration for useradd.
+ source = pkgs.writeText "useradd"
+ ''
+ GROUP=100
+ HOME=/home
+ SHELL=${utils.toShellPath config.users.defaultUserShell}
+ '';
+ target = "default/useradd";
+ }
+ ];
+
+ security.pam.services =
+ { chsh = { rootOK = true; };
+ chfn = { rootOK = true; };
+ su = { rootOK = true; forwardXAuth = true; logFailures = true; };
+ passwd = {};
+ # Note: useradd, groupadd etc. aren't setuid root, so it
+ # doesn't really matter what the PAM config says as long as it
+ # lets root in.
+ useradd = { rootOK = true; };
+ usermod = { rootOK = true; };
+ userdel = { rootOK = true; };
+ groupadd = { rootOK = true; };
+ groupmod = { rootOK = true; };
+ groupmems = { rootOK = true; };
+ groupdel = { rootOK = true; };
+ login = { startSession = true; allowNullPassword = true; showMotd = true; updateWtmp = true; };
+ chpasswd = { rootOK = true; };
+ };
+
+ security.wrappers = {
+ su.source = "${pkgs.shadow.su}/bin/su";
+ sg.source = "${pkgs.shadow.out}/bin/sg";
+ newgrp.source = "${pkgs.shadow.out}/bin/newgrp";
+ newuidmap.source = "${pkgs.shadow.out}/bin/newuidmap";
+ newgidmap.source = "${pkgs.shadow.out}/bin/newgidmap";
+ } // (if config.users.mutableUsers then {
+ passwd.source = "${pkgs.shadow.out}/bin/passwd";
+ } else {});
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/shell.nix b/nixpkgs/nixos/modules/programs/shell.nix
new file mode 100644
index 00000000000..b7f7b91b5fb
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/shell.nix
@@ -0,0 +1,54 @@
+# This module defines a standard configuration for NixOS shells.
+
+{ config, lib, ... }:
+
+with lib;
+
+{
+
+ config = {
+
+ environment.shellInit =
+ ''
+ # Set up the per-user profile.
+ mkdir -m 0755 -p "$NIX_USER_PROFILE_DIR"
+ if [ "$(stat -c '%u' "$NIX_USER_PROFILE_DIR")" != "$(id -u)" ]; then
+ echo "WARNING: the per-user profile dir $NIX_USER_PROFILE_DIR should belong to user id $(id -u)" >&2
+ fi
+
+ if [ -w "$HOME" ]; then
+ if ! [ -L "$HOME/.nix-profile" ]; then
+ if [ "$USER" != root ]; then
+ ln -s "$NIX_USER_PROFILE_DIR/profile" "$HOME/.nix-profile"
+ else
+ # Root installs in the system-wide profile by default.
+ ln -s /nix/var/nix/profiles/default "$HOME/.nix-profile"
+ fi
+ fi
+
+ # Subscribe the root user to the NixOS channel by default.
+ if [ "$USER" = root -a ! -e "$HOME/.nix-channels" ]; then
+ echo "${config.system.defaultChannel} nixos" > "$HOME/.nix-channels"
+ fi
+
+ # Create the per-user garbage collector roots directory.
+ NIX_USER_GCROOTS_DIR="/nix/var/nix/gcroots/per-user/$USER"
+ mkdir -m 0755 -p "$NIX_USER_GCROOTS_DIR"
+ if [ "$(stat -c '%u' "$NIX_USER_GCROOTS_DIR")" != "$(id -u)" ]; then
+ echo "WARNING: the per-user gcroots dir $NIX_USER_GCROOTS_DIR should belong to user id $(id -u)" >&2
+ fi
+
+ # Set up a default Nix expression from which to install stuff.
+ if [ ! -e "$HOME/.nix-defexpr" -o -L "$HOME/.nix-defexpr" ]; then
+ rm -f "$HOME/.nix-defexpr"
+ mkdir -p "$HOME/.nix-defexpr"
+ if [ "$USER" != root ]; then
+ ln -s /nix/var/nix/profiles/per-user/root/channels "$HOME/.nix-defexpr/channels_root"
+ fi
+ fi
+ fi
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/singularity.nix b/nixpkgs/nixos/modules/programs/singularity.nix
new file mode 100644
index 00000000000..b27e122bd1d
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/singularity.nix
@@ -0,0 +1,29 @@
+{ config, pkgs, lib, ... }:
+
+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 = [ 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/nixpkgs/nixos/modules/programs/slock.nix b/nixpkgs/nixos/modules/programs/slock.nix
new file mode 100644
index 00000000000..0e1281e62cd
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/slock.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.slock;
+
+in
+{
+ options = {
+ programs.slock = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to install slock screen locker with setuid wrapper.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.slock ];
+ security.wrappers.slock.source = "${pkgs.slock.out}/bin/slock";
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/spacefm.nix b/nixpkgs/nixos/modules/programs/spacefm.nix
new file mode 100644
index 00000000000..6d03608402f
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/spacefm.nix
@@ -0,0 +1,55 @@
+# Global configuration for spacefm.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.programs.spacefm;
+
+in
+{
+ ###### interface
+
+ options = {
+
+ programs.spacefm = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to install SpaceFM and create <filename>/etc/spacefm/spacefm.conf</filename>.
+ '';
+ };
+
+ settings = mkOption {
+ type = types.attrs;
+ default = {
+ tmp_dir = "/tmp";
+ terminal_su = "${pkgs.sudo}/bin/sudo";
+ graphical_su = "${pkgs.gksu}/bin/gksu";
+ };
+ example = literalExample ''{
+ tmp_dir = "/tmp";
+ terminal_su = "''${pkgs.sudo}/bin/sudo";
+ graphical_su = "''${pkgs.gksu}/bin/gksu";
+ }'';
+ description = ''
+ The system-wide spacefm configuration.
+ Parameters to be written to <filename>/etc/spacefm/spacefm.conf</filename>.
+ Refer to the <link xlink:href="https://ignorantguru.github.io/spacefm/spacefm-manual-en.html#programfiles-etc">relevant entry</link> in the SpaceFM manual.
+ '';
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.spaceFM ];
+
+ environment.etc."spacefm/spacefm.conf".text =
+ concatStrings (mapAttrsToList (n: v: "${n}=${toString v}\n") cfg.settings);
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/ssh.nix b/nixpkgs/nixos/modules/programs/ssh.nix
new file mode 100644
index 00000000000..733b8f7636f
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/ssh.nix
@@ -0,0 +1,267 @@
+# Global configuration for the SSH client.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.ssh;
+
+ askPassword = cfg.askPassword;
+
+ askPasswordWrapper = pkgs.writeScript "ssh-askpass-wrapper"
+ ''
+ #! ${pkgs.runtimeShell} -e
+ export DISPLAY="$(systemctl --user show-environment | ${pkgs.gnused}/bin/sed 's/^DISPLAY=\(.*\)/\1/; t; d')"
+ exec ${askPassword}
+ '';
+
+ knownHosts = map (h: getAttr h cfg.knownHosts) (attrNames cfg.knownHosts);
+
+ knownHostsText = (flip (concatMapStringsSep "\n") knownHosts
+ (h: assert h.hostNames != [];
+ optionalString h.certAuthority "@cert-authority " + concatStringsSep "," h.hostNames + " "
+ + (if h.publicKey != null then h.publicKey else readFile h.publicKeyFile)
+ )) + "\n";
+
+in
+{
+ ###### interface
+
+ options = {
+
+ programs.ssh = {
+
+ askPassword = mkOption {
+ type = types.str;
+ default = "${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass";
+ description = ''Program used by SSH to ask for passwords.'';
+ };
+
+ forwardX11 = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to request X11 forwarding on outgoing connections by default.
+ This is useful for running graphical programs on the remote machine and have them display to your local X11 server.
+ Historically, this value has depended on the value used by the local sshd daemon, but there really isn't a relation between the two.
+ Note: there are some security risks to forwarding an X11 connection.
+ NixOS's X server is built with the SECURITY extension, which prevents some obvious attacks.
+ To enable or disable forwarding on a per-connection basis, see the -X and -x options to ssh.
+ The -Y option to ssh enables trusted forwarding, which bypasses the SECURITY extension.
+ '';
+ };
+
+ setXAuthLocation = mkOption {
+ type = types.bool;
+ description = ''
+ Whether to set the path to <command>xauth</command> for X11-forwarded connections.
+ This causes a dependency on X11 packages.
+ '';
+ };
+
+ # Allow DSA keys for now. (These were deprecated in OpenSSH 7.0.)
+ pubkeyAcceptedKeyTypes = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "+ssh-dss"
+ ];
+ example = [ "ssh-ed25519" "ssh-rsa" ];
+ description = ''
+ Specifies the key types that will be used for public key authentication.
+ '';
+ };
+
+ hostKeyAlgorithms = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "+ssh-dss"
+ ];
+ example = [ "ssh-ed25519" "ssh-rsa" ];
+ description = ''
+ Specifies the host key algorithms that the client wants to use in order of preference.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration text prepended to <filename>ssh_config</filename>. Other generated
+ options will be added after a <code>Host *</code> pattern.
+ See <citerefentry><refentrytitle>ssh_config</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for help.
+ '';
+ };
+
+ startAgent = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to start the OpenSSH agent when you log in. The OpenSSH agent
+ remembers private keys for you so that you don't have to type in
+ passphrases every time you make an SSH connection. Use
+ <command>ssh-add</command> to add a key to the agent.
+ '';
+ };
+
+ agentTimeout = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "1h";
+ description = ''
+ How long to keep the private keys in memory. Use null to keep them forever.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.openssh;
+ defaultText = "pkgs.openssh";
+ description = ''
+ The package used for the openssh client and daemon.
+ '';
+ };
+
+ knownHosts = mkOption {
+ default = {};
+ type = types.loaOf (types.submodule ({ name, ... }: {
+ options = {
+ certAuthority = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This public key is an SSH certificate authority, rather than an
+ individual host's key.
+ '';
+ };
+ hostNames = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ A list of host names and/or IP numbers used for accessing
+ the host's ssh service.
+ '';
+ };
+ publicKey = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ example = "ecdsa-sha2-nistp521 AAAAE2VjZHN...UEPg==";
+ description = ''
+ The public key data for the host. You can fetch a public key
+ from a running SSH server with the <command>ssh-keyscan</command>
+ command. The public key should not include any host names, only
+ the key type and the key itself.
+ '';
+ };
+ publicKeyFile = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = ''
+ The path to the public key file for the host. The public
+ key file is read at build time and saved in the Nix store.
+ You can fetch a public key file from a running SSH server
+ with the <command>ssh-keyscan</command> command. The content
+ of the file should follow the same format as described for
+ the <literal>publicKey</literal> option.
+ '';
+ };
+ };
+ config = {
+ hostNames = mkDefault [ name ];
+ };
+ }));
+ description = ''
+ 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;
+ };
+ }
+ '';
+ };
+
+ };
+
+ };
+
+ config = {
+
+ programs.ssh.setXAuthLocation =
+ mkDefault (config.services.xserver.enable || config.programs.ssh.forwardX11 || config.services.openssh.forwardX11);
+
+ assertions =
+ [ { assertion = cfg.forwardX11 -> cfg.setXAuthLocation;
+ message = "cannot enable X11 forwarding without setting XAuth location";
+ }
+ ] ++ flip mapAttrsToList cfg.knownHosts (name: data: {
+ assertion = (data.publicKey == null && data.publicKeyFile != null) ||
+ (data.publicKey != null && data.publicKeyFile == null);
+ message = "knownHost ${name} must contain either a publicKey or publicKeyFile";
+ });
+
+ # SSH configuration. Slight duplication of the sshd_config
+ # generation in the sshd service.
+ environment.etc."ssh/ssh_config".text =
+ ''
+ # Custom options from `extraConfig`, to override generated options
+ ${cfg.extraConfig}
+
+ # Generated options from other settings
+ Host *
+ AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
+
+ ${optionalString cfg.setXAuthLocation ''
+ XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
+ ''}
+
+ ForwardX11 ${if cfg.forwardX11 then "yes" else "no"}
+
+ ${optionalString (cfg.pubkeyAcceptedKeyTypes != []) "PubkeyAcceptedKeyTypes ${concatStringsSep "," cfg.pubkeyAcceptedKeyTypes}"}
+ ${optionalString (cfg.hostKeyAlgorithms != []) "HostKeyAlgorithms ${concatStringsSep "," cfg.hostKeyAlgorithms}"}
+ '';
+
+ environment.etc."ssh/ssh_known_hosts".text = knownHostsText;
+
+ # FIXME: this should really be socket-activated for über-awesomeness.
+ systemd.user.services.ssh-agent = mkIf cfg.startAgent
+ { description = "SSH Agent";
+ wantedBy = [ "default.target" ];
+ unitConfig.ConditionUser = "!@system";
+ serviceConfig =
+ { ExecStartPre = "${pkgs.coreutils}/bin/rm -f %t/ssh-agent";
+ ExecStart =
+ "${cfg.package}/bin/ssh-agent " +
+ optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") +
+ "-a %t/ssh-agent";
+ StandardOutput = "null";
+ Type = "forking";
+ Restart = "on-failure";
+ SuccessExitStatus = "0 2";
+ };
+ # Allow ssh-agent to ask for confirmation. This requires the
+ # unit to know about the user's $DISPLAY (via ‘systemctl
+ # import-environment’).
+ environment.SSH_ASKPASS = optionalString config.services.xserver.enable askPasswordWrapper;
+ environment.DISPLAY = "fake"; # required to make ssh-agent start $SSH_ASKPASS
+ };
+
+ environment.extraInit = optionalString cfg.startAgent
+ ''
+ if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then
+ export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent"
+ fi
+ '';
+
+ environment.variables.SSH_ASKPASS = optionalString config.services.xserver.enable askPassword;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/ssmtp.nix b/nixpkgs/nixos/modules/programs/ssmtp.nix
new file mode 100644
index 00000000000..0e060e3f522
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/ssmtp.nix
@@ -0,0 +1,165 @@
+# Configuration for `ssmtp', a trivial mail transfer agent that can
+# replace sendmail/postfix on simple systems. It delivers email
+# directly to an SMTP server defined in its configuration file, wihout
+# queueing mail locally.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.networking.defaultMailServer;
+
+in
+
+{
+
+ options = {
+
+ networking.defaultMailServer = {
+
+ directDelivery = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Use the trivial Mail Transfer Agent (MTA)
+ <command>ssmtp</command> package to allow programs to send
+ e-mail. If you don't want to run a “real” MTA like
+ <command>sendmail</command> or <command>postfix</command> on
+ your machine, set this option to <literal>true</literal>, and
+ set the option
+ <option>networking.defaultMailServer.hostName</option> to the
+ host name of your preferred mail server.
+ '';
+ };
+
+ hostName = mkOption {
+ type = types.str;
+ example = "mail.example.org";
+ description = ''
+ The host name of the default mail server to use to deliver
+ e-mail. Can also contain a port number (ex: mail.example.org:587),
+ defaults to port 25 if no port is given.
+ '';
+ };
+
+ root = mkOption {
+ type = types.str;
+ default = "";
+ example = "root@example.org";
+ description = ''
+ The e-mail to which mail for users with UID &lt; 1000 is forwarded.
+ '';
+ };
+
+ domain = mkOption {
+ type = types.str;
+ default = "";
+ example = "example.org";
+ description = ''
+ The domain from which mail will appear to be sent.
+ '';
+ };
+
+ useTLS = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether TLS should be used to connect to the default mail
+ server.
+ '';
+ };
+
+ useSTARTTLS = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether the STARTTLS should be used to connect to the default
+ mail server. (This is needed for TLS-capable mail servers
+ running on the default SMTP port 25.)
+ '';
+ };
+
+ authUser = mkOption {
+ type = types.str;
+ default = "";
+ example = "foo@example.org";
+ description = ''
+ Username used for SMTP auth. Leave blank to disable.
+ '';
+ };
+
+ authPass = mkOption {
+ type = types.str;
+ default = "";
+ example = "correctHorseBatteryStaple";
+ description = ''
+ Password used for SMTP auth. (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)
+
+ It's recommended to use <option>authPassFile</option>
+ which takes precedence over <option>authPass</option>.
+ '';
+ };
+
+ authPassFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/run/keys/ssmtp-authpass";
+ description = ''
+ Path to a file that contains the password used for SMTP auth. The file
+ should not contain a trailing newline, if the password does not contain one.
+ This file should be readable by the users that need to execute ssmtp.
+
+ <option>authPassFile</option> takes precedence over <option>authPass</option>.
+
+ Warning: when <option>authPass</option> is non-empty <option>authPassFile</option>
+ defaults to a file in the WORLD-READABLE Nix store containing that password.
+ '';
+ };
+
+ setSendmail = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to set the system sendmail to ssmtp's.";
+ };
+
+ };
+
+ };
+
+
+ config = mkIf cfg.directDelivery {
+
+ networking.defaultMailServer.authPassFile = mkIf (cfg.authPass != "")
+ (mkDefault (toString (pkgs.writeTextFile {
+ name = "ssmtp-authpass";
+ text = cfg.authPass;
+ })));
+
+ environment.etc."ssmtp/ssmtp.conf".text =
+ let yesNo = yes : if yes then "YES" else "NO"; in
+ ''
+ MailHub=${cfg.hostName}
+ FromLineOverride=YES
+ ${optionalString (cfg.root != "") "root=${cfg.root}"}
+ ${optionalString (cfg.domain != "") "rewriteDomain=${cfg.domain}"}
+ UseTLS=${yesNo cfg.useTLS}
+ UseSTARTTLS=${yesNo cfg.useSTARTTLS}
+ #Debug=YES
+ ${optionalString (cfg.authUser != "") "AuthUser=${cfg.authUser}"}
+ ${optionalString (cfg.authPassFile != null) "AuthPassFile=${cfg.authPassFile}"}
+ '';
+
+ environment.systemPackages = [pkgs.ssmtp];
+
+ services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail {
+ program = "sendmail";
+ source = "${pkgs.ssmtp}/bin/sendmail";
+ setuid = false;
+ setgid = false;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/sway.nix b/nixpkgs/nixos/modules/programs/sway.nix
new file mode 100644
index 00000000000..f92d09a7ef4
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/sway.nix
@@ -0,0 +1,93 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.sway;
+ swayPackage = pkgs.sway;
+
+ swayWrapped = pkgs.writeShellScriptBin "sway" ''
+ set -o errexit
+
+ if [ ! "$_SWAY_WRAPPER_ALREADY_EXECUTED" ]; then
+ export _SWAY_WRAPPER_ALREADY_EXECUTED=1
+ ${cfg.extraSessionCommands}
+ fi
+
+ 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 = {
+ enable = mkEnableOption ''
+ Sway, the i3-compatible tiling Wayland compositor. You can manually launch
+ Sway by executing "exec sway" on a TTY. Copy /etc/sway/config to
+ ~/.config/sway/config to modify the default configuration. See
+ https://github.com/swaywm/sway/wiki and "man 5 sway" for more information.
+ Please have a look at the "extraSessionCommands" example for running
+ programs natively under Wayland'';
+
+ 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 swaybg
+ 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;
+ etc = {
+ "sway/config".source = mkOptionDefault "${swayPackage}/etc/sway/config";
+ #"sway/security.d".source = mkOptionDefault "${swayPackage}/etc/sway/security.d/";
+ #"sway/config.d".source = mkOptionDefault "${swayPackage}/etc/sway/config.d/";
+ };
+ };
+ security.pam.services.swaylock = {};
+ hardware.opengl.enable = mkDefault true;
+ fonts.enableDefaultFonts = mkDefault true;
+ programs.dconf.enable = mkDefault true;
+ };
+
+ meta.maintainers = with lib.maintainers; [ gnidorah primeos colemickens ];
+}
diff --git a/nixpkgs/nixos/modules/programs/sysdig.nix b/nixpkgs/nixos/modules/programs/sysdig.nix
new file mode 100644
index 00000000000..fbbf2906556
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/sysdig.nix
@@ -0,0 +1,14 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.sysdig;
+in {
+ options.programs.sysdig.enable = mkEnableOption "sysdig";
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.sysdig ];
+ boot.extraModulePackages = [ config.boot.kernelPackages.sysdig ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/system-config-printer.nix b/nixpkgs/nixos/modules/programs/system-config-printer.nix
new file mode 100644
index 00000000000..34592dd7064
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/system-config-printer.nix
@@ -0,0 +1,32 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ programs.system-config-printer = {
+
+ enable = mkEnableOption "system-config-printer, a Graphical user interface for CUPS administration";
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.programs.system-config-printer.enable {
+
+ environment.systemPackages = [
+ pkgs.system-config-printer
+ ];
+
+ services.system-config-printer.enable = true;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/systemtap.nix b/nixpkgs/nixos/modules/programs/systemtap.nix
new file mode 100644
index 00000000000..ca81e018c9d
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/systemtap.nix
@@ -0,0 +1,28 @@
+{ config, lib, ... }:
+
+with lib;
+
+let cfg = config.programs.systemtap;
+in {
+
+ options = {
+ programs.systemtap = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Install <command>systemtap</command> along with necessary kernel options.
+ '';
+ };
+ };
+ };
+ config = mkIf cfg.enable {
+ system.requiredKernelConfig = with config.lib.kernelConfig; [
+ (isYes "DEBUG")
+ ];
+ boot.kernel.features.debug = true;
+ environment.systemPackages = [
+ config.boot.kernelPackages.systemtap
+ ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/thefuck.nix b/nixpkgs/nixos/modules/programs/thefuck.nix
new file mode 100644
index 00000000000..b909916158d
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/thefuck.nix
@@ -0,0 +1,39 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ prg = config.programs;
+ cfg = prg.thefuck;
+
+ initScript = ''
+ eval $(${pkgs.thefuck}/bin/thefuck --alias ${cfg.alias})
+ '';
+in
+ {
+ options = {
+ programs.thefuck = {
+ enable = mkEnableOption "thefuck";
+
+ alias = mkOption {
+ default = "fuck";
+ type = types.str;
+
+ description = ''
+ `thefuck` needs an alias to be configured.
+ The default value is `fuck`, but you can use anything else as well.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [ thefuck ];
+
+ 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/nixpkgs/nixos/modules/programs/tmux.nix b/nixpkgs/nixos/modules/programs/tmux.nix
new file mode 100644
index 00000000000..ed077e3daa7
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/tmux.nix
@@ -0,0 +1,184 @@
+{ config, pkgs, lib, ... }:
+
+let
+ inherit (lib) mkOption mkIf types;
+
+ cfg = config.programs.tmux;
+
+ defaultKeyMode = "emacs";
+ defaultResize = 5;
+ defaultShortcut = "b";
+ defaultTerminal = "screen";
+
+ boolToStr = value: if value then "on" else "off";
+
+ tmuxConf = ''
+ set -g default-terminal "${cfg.terminal}"
+ set -g base-index ${toString cfg.baseIndex}
+ setw -g pane-base-index ${toString cfg.baseIndex}
+
+ ${if cfg.newSession then "new-session" else ""}
+
+ ${if cfg.reverseSplit then ''
+ bind v split-window -h
+ bind s split-window -v
+ '' else ""}
+
+ set -g status-keys ${cfg.keyMode}
+ set -g mode-keys ${cfg.keyMode}
+
+ ${if cfg.keyMode == "vi" && cfg.customPaneNavigationAndResize then ''
+ bind h select-pane -L
+ bind j select-pane -D
+ bind k select-pane -U
+ bind l select-pane -R
+
+ bind -r H resize-pane -L ${toString cfg.resizeAmount}
+ bind -r J resize-pane -D ${toString cfg.resizeAmount}
+ bind -r K resize-pane -U ${toString cfg.resizeAmount}
+ bind -r L resize-pane -R ${toString cfg.resizeAmount}
+ '' else ""}
+
+ ${if (cfg.shortcut != defaultShortcut) then ''
+ # rebind main key: C-${cfg.shortcut}
+ unbind C-${defaultShortcut}
+ set -g prefix C-${cfg.shortcut}
+ bind ${cfg.shortcut} send-prefix
+ bind C-${cfg.shortcut} last-window
+ '' else ""}
+
+ setw -g aggressive-resize ${boolToStr cfg.aggressiveResize}
+ setw -g clock-mode-style ${if cfg.clock24 then "24" else "12"}
+ set -s escape-time ${toString cfg.escapeTime}
+ set -g history-limit ${toString cfg.historyLimit}
+
+ ${cfg.extraTmuxConf}
+ '';
+
+in {
+ ###### interface
+
+ options = {
+ programs.tmux = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whenever to configure <command>tmux</command> system-wide.";
+ relatedPackages = [ "tmux" ];
+ };
+
+ aggressiveResize = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Resize the window to the size of the smallest session for which it is the current window.
+ '';
+ };
+
+ baseIndex = mkOption {
+ default = 0;
+ example = 1;
+ type = types.int;
+ description = "Base index for windows and panes.";
+ };
+
+ clock24 = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Use 24 hour clock.";
+ };
+
+ customPaneNavigationAndResize = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Override the hjkl and HJKL bindings for pane navigation and resizing in VI mode.";
+ };
+
+ escapeTime = mkOption {
+ default = 500;
+ example = 0;
+ type = types.int;
+ description = "Time in milliseconds for which tmux waits after an escape is input.";
+ };
+
+ extraTmuxConf = mkOption {
+ default = "";
+ description = ''
+ Additional contents of /etc/tmux.conf
+ '';
+ type = types.lines;
+ };
+
+ historyLimit = mkOption {
+ default = 2000;
+ example = 5000;
+ type = types.int;
+ description = "Maximum number of lines held in window history.";
+ };
+
+ keyMode = mkOption {
+ default = defaultKeyMode;
+ example = "vi";
+ type = types.enum [ "emacs" "vi" ];
+ description = "VI or Emacs style shortcuts.";
+ };
+
+ newSession = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Automatically spawn a session if trying to attach and none are running.";
+ };
+
+ reverseSplit = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Reverse the window split shortcuts.";
+ };
+
+ resizeAmount = mkOption {
+ default = defaultResize;
+ example = 10;
+ type = types.int;
+ description = "Number of lines/columns when resizing.";
+ };
+
+ shortcut = mkOption {
+ default = defaultShortcut;
+ example = "a";
+ type = types.str;
+ description = "Ctrl following by this key is used as the main shortcut.";
+ };
+
+ terminal = mkOption {
+ default = defaultTerminal;
+ example = "screen-256color";
+ type = types.str;
+ description = "Set the $TERM variable.";
+ };
+
+ secureSocket = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Store tmux socket under /run, which is more secure than /tmp, but as a
+ downside it doesn't survive user logout.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ environment = {
+ etc."tmux.conf".text = tmuxConf;
+
+ systemPackages = [ pkgs.tmux ];
+
+ variables = {
+ TMUX_TMPDIR = lib.optional cfg.secureSocket ''''${XDG_RUNTIME_DIR:-"/run/user/$(id -u)"}'';
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/tsm-client.nix b/nixpkgs/nixos/modules/programs/tsm-client.nix
new file mode 100644
index 00000000000..eb6f1247528
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/tsm-client.nix
@@ -0,0 +1,287 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+ inherit (builtins) length map;
+ inherit (lib.attrsets) attrNames filterAttrs hasAttr mapAttrs mapAttrsToList optionalAttrs;
+ inherit (lib.modules) mkDefault mkIf;
+ inherit (lib.options) literalExample mkEnableOption mkOption;
+ inherit (lib.strings) concatStringsSep optionalString toLower;
+ inherit (lib.types) addCheck attrsOf lines loaOf nullOr package path port str strMatching submodule;
+
+ # Checks if given list of strings contains unique
+ # elements when compared without considering case.
+ # Type: checkIUnique :: [string] -> bool
+ # Example: checkIUnique ["foo" "Foo"] => false
+ checkIUnique = lst:
+ let
+ lenUniq = l: length (lib.lists.unique l);
+ in
+ lenUniq lst == lenUniq (map toLower lst);
+
+ # TSM rejects servername strings longer than 64 chars.
+ servernameType = strMatching ".{1,64}";
+
+ serverOptions = { name, config, ... }: {
+ options.name = mkOption {
+ type = servernameType;
+ example = "mainTsmServer";
+ description = ''
+ Local name of the IBM TSM server,
+ must be uncapitalized and no longer than 64 chars.
+ The value will be used for the
+ <literal>server</literal>
+ directive in <filename>dsm.sys</filename>.
+ '';
+ };
+ options.server = mkOption {
+ type = strMatching ".+";
+ example = "tsmserver.company.com";
+ description = ''
+ Host/domain name or IP address of the IBM TSM server.
+ The value will be used for the
+ <literal>tcpserveraddress</literal>
+ directive in <filename>dsm.sys</filename>.
+ '';
+ };
+ options.port = mkOption {
+ type = addCheck port (p: p<=32767);
+ default = 1500; # official default
+ description = ''
+ TCP port of the IBM TSM server.
+ The value will be used for the
+ <literal>tcpport</literal>
+ directive in <filename>dsm.sys</filename>.
+ TSM does not support ports above 32767.
+ '';
+ };
+ options.node = mkOption {
+ type = strMatching ".+";
+ example = "MY-TSM-NODE";
+ description = ''
+ Target node name on the IBM TSM server.
+ The value will be used for the
+ <literal>nodename</literal>
+ directive in <filename>dsm.sys</filename>.
+ '';
+ };
+ options.genPasswd = mkEnableOption ''
+ automatic client password generation.
+ This option influences the
+ <literal>passwordaccess</literal>
+ directive in <filename>dsm.sys</filename>.
+ The password will be stored in the directory
+ given by the option <option>passwdDir</option>.
+ <emphasis>Caution</emphasis>:
+ If this option is enabled and the server forces
+ to renew the password (e.g. on first connection),
+ a random password will be generated and stored
+ '';
+ options.passwdDir = mkOption {
+ type = path;
+ example = "/home/alice/tsm-password";
+ description = ''
+ Directory that holds the TSM
+ node's password information.
+ The value will be used for the
+ <literal>passworddir</literal>
+ directive in <filename>dsm.sys</filename>.
+ '';
+ };
+ options.includeExclude = mkOption {
+ type = lines;
+ default = "";
+ example = ''
+ exclude.dir /nix/store
+ include.encrypt /home/.../*
+ '';
+ description = ''
+ <literal>include.*</literal> and
+ <literal>exclude.*</literal> directives to be
+ used when sending files to the IBM TSM server.
+ The lines will be written into a file that the
+ <literal>inclexcl</literal>
+ directive in <filename>dsm.sys</filename> points to.
+ '';
+ };
+ options.extraConfig = mkOption {
+ # TSM option keys are case insensitive;
+ # we have to ensure there are no keys that
+ # differ only by upper and lower case.
+ type = addCheck
+ (attrsOf (nullOr str))
+ (attrs: checkIUnique (attrNames attrs));
+ default = {};
+ example.compression = "yes";
+ example.passwordaccess = null;
+ description = ''
+ Additional key-value pairs for the server stanza.
+ Values must be strings, or <literal>null</literal>
+ for the key not to be used in the stanza
+ (e.g. to overrule values generated by other options).
+ '';
+ };
+ options.text = mkOption {
+ type = lines;
+ example = literalExample
+ ''lib.modules.mkAfter "compression no"'';
+ description = ''
+ Additional text lines for the server stanza.
+ This option can be used if certion configuration keys
+ must be used multiple times or ordered in a certain way
+ as the <option>extraConfig</option> option can't
+ control the order of lines in the resulting stanza.
+ Note that the <literal>server</literal>
+ line at the beginning of the stanza is
+ not part of this option's value.
+ '';
+ };
+ options.stanza = mkOption {
+ type = str;
+ internal = true;
+ visible = false;
+ description = "Server stanza text generated from the options.";
+ };
+ config.name = mkDefault name;
+ # Client system-options file directives are explained here:
+ # https://www.ibm.com/support/knowledgecenter/SSEQVQ_8.1.8/client/c_opt_usingopts.html
+ config.extraConfig =
+ mapAttrs (lib.trivial.const mkDefault) (
+ {
+ commmethod = "v6tcpip"; # uses v4 or v6, based on dns lookup result
+ tcpserveraddress = config.server;
+ tcpport = builtins.toString config.port;
+ nodename = config.node;
+ passwordaccess = if config.genPasswd then "generate" else "prompt";
+ passworddir = ''"${config.passwdDir}"'';
+ } // optionalAttrs (config.includeExclude!="") {
+ inclexcl = ''"${pkgs.writeText "inclexcl.dsm.sys" config.includeExclude}"'';
+ }
+ );
+ config.text =
+ let
+ attrset = filterAttrs (k: v: v!=null) config.extraConfig;
+ mkLine = k: v: k + optionalString (v!="") " ${v}";
+ lines = mapAttrsToList mkLine attrset;
+ in
+ concatStringsSep "\n" lines;
+ config.stanza = ''
+ server ${config.name}
+ ${config.text}
+ '';
+ };
+
+ options.programs.tsmClient = {
+ enable = mkEnableOption ''
+ IBM Spectrum Protect (Tivoli Storage Manager, TSM)
+ client command line applications with a
+ client system-options file "dsm.sys"
+ '';
+ servers = mkOption {
+ type = loaOf (submodule [ serverOptions ]);
+ default = {};
+ example.mainTsmServer = {
+ server = "tsmserver.company.com";
+ node = "MY-TSM-NODE";
+ extraConfig.compression = "yes";
+ };
+ description = ''
+ Server definitions ("stanzas")
+ for the client system-options file.
+ '';
+ };
+ defaultServername = mkOption {
+ type = nullOr servernameType;
+ default = null;
+ example = "mainTsmServer";
+ description = ''
+ If multiple server stanzas are declared with
+ <option>programs.tsmClient.servers</option>,
+ this option may be used to name a default
+ server stanza that IBM TSM uses in the absence of
+ a user-defined <filename>dsm.opt</filename> file.
+ This option translates to a
+ <literal>defaultserver</literal> configuration line.
+ '';
+ };
+ dsmSysText = mkOption {
+ type = lines;
+ readOnly = true;
+ description = ''
+ This configuration key contains the effective text
+ of the client system-options file "dsm.sys".
+ It should not be changed, but may be
+ used to feed the configuration into other
+ TSM-depending packages used on the system.
+ '';
+ };
+ package = mkOption {
+ type = package;
+ default = pkgs.tsm-client;
+ defaultText = "pkgs.tsm-client";
+ example = literalExample "pkgs.tsm-client-withGui";
+ description = ''
+ The TSM client derivation to be
+ added to the system environment.
+ It will called with <literal>.override</literal>
+ to add paths to the client system-options file.
+ '';
+ };
+ wrappedPackage = mkOption {
+ type = package;
+ readOnly = true;
+ description = ''
+ The TSM client derivation, wrapped with the path
+ to the client system-options file "dsm.sys".
+ This option is to provide the effective derivation
+ for other modules that want to call TSM executables.
+ '';
+ };
+ };
+
+ cfg = config.programs.tsmClient;
+
+ assertions = [
+ {
+ assertion = checkIUnique (mapAttrsToList (k: v: v.name) cfg.servers);
+ message = ''
+ TSM servernames contain duplicate name
+ (note that case doesn't matter!)
+ '';
+ }
+ {
+ assertion = (cfg.defaultServername!=null)->(hasAttr cfg.defaultServername cfg.servers);
+ message = "TSM defaultServername not found in list of servers";
+ }
+ ];
+
+ dsmSysText = ''
+ **** IBM Spectrum Protect (Tivoli Storage Manager)
+ **** client system-options file "dsm.sys".
+ **** Do not edit!
+ **** This file is generated by NixOS configuration.
+
+ ${optionalString (cfg.defaultServername!=null) "defaultserver ${cfg.defaultServername}"}
+
+ ${concatStringsSep "\n" (mapAttrsToList (k: v: v.stanza) cfg.servers)}
+ '';
+
+in
+
+{
+
+ inherit options;
+
+ config = mkIf cfg.enable {
+ inherit assertions;
+ programs.tsmClient.dsmSysText = dsmSysText;
+ programs.tsmClient.wrappedPackage = cfg.package.override rec {
+ dsmSysCli = pkgs.writeText "dsm.sys" cfg.dsmSysText;
+ dsmSysApi = dsmSysCli;
+ };
+ environment.systemPackages = [ cfg.wrappedPackage ];
+ };
+
+ meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixpkgs/nixos/modules/programs/udevil.nix b/nixpkgs/nixos/modules/programs/udevil.nix
new file mode 100644
index 00000000000..ba5670f9dfe
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/udevil.nix
@@ -0,0 +1,14 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.udevil;
+
+in {
+ options.programs.udevil.enable = mkEnableOption "udevil";
+
+ config = mkIf cfg.enable {
+ security.wrappers.udevil.source = "${lib.getBin pkgs.udevil}/bin/udevil";
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/usbtop.nix b/nixpkgs/nixos/modules/programs/usbtop.nix
new file mode 100644
index 00000000000..c1b6ee38caa
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/usbtop.nix
@@ -0,0 +1,21 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.usbtop;
+in {
+ options = {
+ programs.usbtop.enable = mkEnableOption "usbtop and required kernel module";
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ usbtop
+ ];
+
+ boot.kernelModules = [
+ "usbmon"
+ ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/venus.nix b/nixpkgs/nixos/modules/programs/venus.nix
new file mode 100644
index 00000000000..110570ac3f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/venus.nix
@@ -0,0 +1,173 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.venus;
+
+ configFile = pkgs.writeText "venus.ini"
+ ''
+ [Planet]
+ name = ${cfg.name}
+ link = ${cfg.link}
+ owner_name = ${cfg.ownerName}
+ owner_email = ${cfg.ownerEmail}
+ output_theme = ${cfg.cacheDirectory}/theme
+ output_dir = ${cfg.outputDirectory}
+ cache_directory = ${cfg.cacheDirectory}
+ items_per_page = ${toString cfg.itemsPerPage}
+ ${(concatStringsSep "\n\n"
+ (map ({ name, feedUrl, homepageUrl }:
+ ''
+ [${feedUrl}]
+ name = ${name}
+ link = ${homepageUrl}
+ '') cfg.feeds))}
+ '';
+
+in
+{
+
+ options = {
+ services.venus = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Planet Venus is an awesome ‘river of news’ feed reader. It downloads
+ news feeds published by web sites and aggregates their content
+ together into a single combined feed, latest news first.
+ '';
+ };
+
+ dates = mkOption {
+ default = "*:0/15";
+ type = types.str;
+ description = ''
+ Specification (in the format described by
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>) of the time at
+ which the Venus will collect feeds.
+ '';
+ };
+
+ user = mkOption {
+ default = "root";
+ type = types.str;
+ description = ''
+ User for running venus script.
+ '';
+ };
+
+ group = mkOption {
+ default = "root";
+ type = types.str;
+ description = ''
+ Group for running venus script.
+ '';
+ };
+
+ name = mkOption {
+ default = "NixOS Planet";
+ type = types.str;
+ description = ''
+ Your planet's name.
+ '';
+ };
+
+ link = mkOption {
+ default = "http://planet.nixos.org";
+ type = types.str;
+ description = ''
+ Link to the main page.
+ '';
+ };
+
+ ownerName = mkOption {
+ default = "Rok Garbas";
+ type = types.str;
+ description = ''
+ Your name.
+ '';
+ };
+
+ ownerEmail = mkOption {
+ default = "some@example.com";
+ type = types.str;
+ description = ''
+ Your e-mail address.
+ '';
+ };
+
+ outputTheme = mkOption {
+ default = "${pkgs.venus}/themes/classic_fancy";
+ type = types.path;
+ description = ''
+ Directory containing a config.ini file which is merged with this one.
+ This is typically used to specify templating and bill of material
+ information.
+ '';
+ };
+
+ outputDirectory = mkOption {
+ type = types.path;
+ description = ''
+ Directory to place output files.
+ '';
+ };
+
+ cacheDirectory = mkOption {
+ default = "/var/cache/venus";
+ type = types.path;
+ description = ''
+ Where cached feeds are stored.
+ '';
+ };
+
+ itemsPerPage = mkOption {
+ default = 15;
+ type = types.int;
+ description = ''
+ How many items to put on each page.
+ '';
+ };
+
+ feeds = mkOption {
+ default = [];
+ example = [
+ {
+ name = "Rok Garbas";
+ feedUrl= "http://url/to/rss/feed.xml";
+ homepageUrl = "http://garbas.si";
+ }
+ ];
+ description = ''
+ List of feeds.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ system.activationScripts.venus =
+ ''
+ mkdir -p ${cfg.outputDirectory}
+ chown ${cfg.user}:${cfg.group} ${cfg.outputDirectory} -R
+ rm -rf ${cfg.cacheDirectory}/theme
+ mkdir -p ${cfg.cacheDirectory}/theme
+ cp -R ${cfg.outputTheme}/* ${cfg.cacheDirectory}/theme
+ chown ${cfg.user}:${cfg.group} ${cfg.cacheDirectory} -R
+ '';
+
+ systemd.services.venus =
+ { description = "Planet Venus Feed Reader";
+ path = [ pkgs.venus ];
+ script = "exec venus-planet ${configFile}";
+ serviceConfig.User = "${cfg.user}";
+ serviceConfig.Group = "${cfg.group}";
+ startAt = cfg.dates;
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/vim.nix b/nixpkgs/nixos/modules/programs/vim.nix
new file mode 100644
index 00000000000..fe0e7f2c6d6
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/vim.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.vim;
+in {
+ options.programs.vim = {
+ defaultEditor = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ When enabled, installs vim and configures vim to be the default editor
+ using the EDITOR environment variable.
+ '';
+ };
+ };
+
+ config = mkIf cfg.defaultEditor {
+ environment.systemPackages = [ pkgs.vim ];
+ environment.variables = { EDITOR = mkOverride 900 "vim"; };
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/virtualbox.nix b/nixpkgs/nixos/modules/programs/virtualbox.nix
new file mode 100644
index 00000000000..be96cf23b39
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/virtualbox.nix
@@ -0,0 +1,8 @@
+let
+ msg = "Importing <nixpkgs/nixos/modules/programs/virtualbox.nix> is "
+ + "deprecated, please use `virtualisation.virtualbox.host.enable = true' "
+ + "instead.";
+in {
+ config.warnings = [ msg ];
+ config.virtualisation.virtualbox.host.enable = true;
+}
diff --git a/nixpkgs/nixos/modules/programs/wavemon.nix b/nixpkgs/nixos/modules/programs/wavemon.nix
new file mode 100644
index 00000000000..ac665fe4a02
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/programs/way-cooler.nix b/nixpkgs/nixos/modules/programs/way-cooler.nix
new file mode 100644
index 00000000000..f27bd42bd76
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/way-cooler.nix
@@ -0,0 +1,78 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.way-cooler;
+ way-cooler = pkgs.way-cooler;
+
+ wcWrapped = pkgs.writeShellScriptBin "way-cooler" ''
+ ${cfg.extraSessionCommands}
+ exec ${pkgs.dbus}/bin/dbus-run-session ${way-cooler}/bin/way-cooler
+ '';
+ wcJoined = pkgs.symlinkJoin {
+ name = "way-cooler-wrapped";
+ paths = [ wcWrapped way-cooler ];
+ };
+ configFile = readFile "${way-cooler}/etc/way-cooler/init.lua";
+ spawnBar = ''
+ util.program.spawn_at_startup("lemonbar");
+ '';
+in
+{
+ options.programs.way-cooler = {
+ enable = mkEnableOption "way-cooler";
+
+ extraSessionCommands = mkOption {
+ default = "";
+ type = types.lines;
+ example = ''
+ export XKB_DEFAULT_LAYOUT=us,de
+ export XKB_DEFAULT_VARIANT=,nodeadkeys
+ export XKB_DEFAULT_OPTIONS=grp:caps_toggle,
+ '';
+ description = ''
+ Shell commands executed just before way-cooler is started.
+ '';
+ };
+
+ extraPackages = mkOption {
+ type = with types; listOf package;
+ default = with pkgs; [
+ westonLite xwayland dmenu
+ ];
+ example = literalExample ''
+ with pkgs; [
+ westonLite xwayland dmenu
+ ]
+ '';
+ description = ''
+ Extra packages to be installed system wide.
+ '';
+ };
+
+ enableBar = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable an unofficial bar.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ wcJoined ] ++ cfg.extraPackages;
+
+ security.pam.services.wc-lock = {};
+ environment.etc."way-cooler/init.lua".text = ''
+ ${configFile}
+ ${optionalString cfg.enableBar spawnBar}
+ '';
+
+ hardware.opengl.enable = mkDefault true;
+ fonts.enableDefaultFonts = mkDefault true;
+ programs.dconf.enable = mkDefault true;
+ };
+
+ meta.maintainers = with maintainers; [ gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/programs/waybar.nix b/nixpkgs/nixos/modules/programs/waybar.nix
new file mode 100644
index 00000000000..22530e6c7d4
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/waybar.nix
@@ -0,0 +1,20 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+{
+ options.programs.waybar = {
+ enable = mkEnableOption "waybar";
+ };
+
+ config = mkIf config.programs.waybar.enable {
+ systemd.user.services.waybar = {
+ description = "Waybar as systemd service";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+ script = "${pkgs.waybar}/bin/waybar";
+ };
+ };
+
+ meta.maintainers = [ maintainers.FlorianFranzen ];
+}
diff --git a/nixpkgs/nixos/modules/programs/wireshark.nix b/nixpkgs/nixos/modules/programs/wireshark.nix
new file mode 100644
index 00000000000..819f15b98a0
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/wireshark.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.wireshark;
+ wireshark = cfg.package;
+in {
+ options = {
+ programs.wireshark = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to add Wireshark to the global environment and configure a
+ setcap wrapper for 'dumpcap' for users in the 'wireshark' group.
+ '';
+ };
+ package = mkOption {
+ type = types.package;
+ default = pkgs.wireshark-cli;
+ defaultText = "pkgs.wireshark-cli";
+ description = ''
+ Which Wireshark package to install in the global environment.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ wireshark ];
+ users.groups.wireshark = {};
+
+ security.wrappers.dumpcap = {
+ source = "${wireshark}/bin/dumpcap";
+ capabilities = "cap_net_raw+p";
+ owner = "root";
+ group = "wireshark";
+ permissions = "u+rx,g+x";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/x2goserver.nix b/nixpkgs/nixos/modules/programs/x2goserver.nix
new file mode 100644
index 00000000000..77a1a0da799
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/x2goserver.nix
@@ -0,0 +1,148 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.x2goserver;
+
+ defaults = {
+ superenicer = { enable = cfg.superenicer.enable; };
+ };
+ confText = generators.toINI {} (recursiveUpdate defaults cfg.settings);
+ x2goServerConf = pkgs.writeText "x2goserver.conf" confText;
+
+ x2goAgentOptions = pkgs.writeText "x2goagent.options" ''
+ X2GO_NXOPTIONS=""
+ X2GO_NXAGENT_DEFAULT_OPTIONS="${concatStringsSep " " cfg.nxagentDefaultOptions}"
+ '';
+
+in {
+ options.programs.x2goserver = {
+ enable = mkEnableOption "x2goserver" // {
+ description = ''
+ Enables the x2goserver module.
+ NOTE: This will create a good amount of symlinks in `/usr/local/bin`
+ '';
+ };
+
+ superenicer = {
+ enable = mkEnableOption "superenicer" // {
+ description = ''
+ Enables the SupeReNicer code in x2gocleansessions, this will renice
+ suspended sessions to nice level 19 and renice them to level 0 if the
+ session becomes marked as running again
+ '';
+ };
+ };
+
+ nxagentDefaultOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ "-extension GLX" "-nolisten tcp" ];
+ example = [ "-extension GLX" "-nolisten tcp" ];
+ description = ''
+ List of default nx agent options.
+ '';
+ };
+
+ settings = mkOption {
+ type = types.attrsOf types.attrs;
+ default = {};
+ description = ''
+ x2goserver.conf ini configuration as nix attributes. See
+ `x2goserver.conf(5)` for details
+ '';
+ example = literalExample ''
+ superenicer = {
+ "enable" = "yes";
+ "idle-nice-level" = 19;
+ };
+ telekinesis = { "enable" = "no"; };
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.x2goserver ];
+
+ users.groups.x2go = {};
+ users.users.x2go = {
+ home = "/var/lib/x2go/db";
+ group = "x2go";
+ };
+
+ security.wrappers.x2gosqliteWrapper = {
+ source = "${pkgs.x2goserver}/lib/x2go/libx2go-server-db-sqlite3-wrapper.pl";
+ owner = "x2go";
+ group = "x2go";
+ setgid = true;
+ };
+ security.wrappers.x2goprintWrapper = {
+ source = "${pkgs.x2goserver}/bin/x2goprint";
+ owner = "x2go";
+ group = "x2go";
+ setgid = true;
+ };
+
+ systemd.tmpfiles.rules = with pkgs; [
+ "d /var/lib/x2go/ - x2go x2go - -"
+ "d /var/lib/x2go/db - x2go x2go - -"
+ "d /var/lib/x2go/conf - x2go x2go - -"
+ "d /run/x2go 0755 x2go x2go - -"
+ ] ++
+ # x2goclient sends SSH commands with preset PATH set to
+ # "/usr/local/bin;/usr/bin;/bin". Since we cannot filter arbitrary ssh
+ # commands, we have to make the following executables available.
+ map (f: "L+ /usr/local/bin/${f} - - - - ${x2goserver}/bin/${f}") [
+ "x2goagent" "x2gobasepath" "x2gocleansessions" "x2gocmdexitmessage"
+ "x2godbadmin" "x2gofeature" "x2gofeaturelist" "x2gofm" "x2gogetapps"
+ "x2gogetservers" "x2golistdesktops" "x2golistmounts" "x2golistsessions"
+ "x2golistsessions_root" "x2golistshadowsessions" "x2gomountdirs"
+ "x2gopath" "x2goprint" "x2goresume-desktopsharing" "x2goresume-session"
+ "x2goruncommand" "x2goserver-run-extensions" "x2gosessionlimit"
+ "x2gosetkeyboard" "x2goshowblocks" "x2gostartagent"
+ "x2gosuspend-desktopsharing" "x2gosuspend-session"
+ "x2goterminate-desktopsharing" "x2goterminate-session"
+ "x2goumount-session" "x2goversion"
+ ] ++ [
+ "L+ /usr/local/bin/awk - - - - ${gawk}/bin/awk"
+ "L+ /usr/local/bin/chmod - - - - ${coreutils}/bin/chmod"
+ "L+ /usr/local/bin/cp - - - - ${coreutils}/bin/cp"
+ "L+ /usr/local/bin/sed - - - - ${gnused}/bin/sed"
+ "L+ /usr/local/bin/setsid - - - - ${utillinux}/bin/setsid"
+ "L+ /usr/local/bin/xrandr - - - - ${xorg.xrandr}/bin/xrandr"
+ "L+ /usr/local/bin/xmodmap - - - - ${xorg.xmodmap}/bin/xmodmap"
+ ];
+
+ systemd.services.x2goserver = {
+ description = "X2Go Server Daemon";
+ wantedBy = [ "multi-user.target" ];
+ unitConfig.Documentation = "man:x2goserver.conf(5)";
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.x2goserver}/bin/x2gocleansessions";
+ PIDFile = "/run/x2go/x2goserver.pid";
+ User = "x2go";
+ Group = "x2go";
+ RuntimeDirectory = "x2go";
+ StateDirectory = "x2go";
+ };
+ preStart = ''
+ if [ ! -e /var/lib/x2go/setup_ran ]
+ then
+ mkdir -p /var/lib/x2go/conf
+ cp -r ${pkgs.x2goserver}/etc/x2go/* /var/lib/x2go/conf/
+ ln -sf ${x2goServerConf} /var/lib/x2go/conf/x2goserver.conf
+ ln -sf ${x2goAgentOptions} /var/lib/x2go/conf/x2goagent.options
+ ${pkgs.x2goserver}/bin/x2godbadmin --createdb
+ touch /var/lib/x2go/setup_ran
+ fi
+ '';
+ };
+
+ # https://bugs.x2go.org/cgi-bin/bugreport.cgi?bug=276
+ security.sudo.extraConfig = ''
+ Defaults env_keep+=QT_GRAPHICSSYSTEM
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/xfs_quota.nix b/nixpkgs/nixos/modules/programs/xfs_quota.nix
new file mode 100644
index 00000000000..c03e59a5b4a
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/xfs_quota.nix
@@ -0,0 +1,110 @@
+# Configuration for the xfs_quota command
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.xfs_quota;
+
+ limitOptions = opts: concatStringsSep " " [
+ (optionalString (opts.sizeSoftLimit != null) "bsoft=${opts.sizeSoftLimit}")
+ (optionalString (opts.sizeHardLimit != null) "bhard=${opts.sizeHardLimit}")
+ ];
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ programs.xfs_quota = {
+ projects = mkOption {
+ default = {};
+ type = types.attrsOf (types.submodule {
+ options = {
+ id = mkOption {
+ type = types.int;
+ description = "Project ID.";
+ };
+
+ fileSystem = mkOption {
+ type = types.str;
+ description = "XFS filesystem hosting the xfs_quota project.";
+ default = "/";
+ };
+
+ path = mkOption {
+ type = types.str;
+ description = "Project directory.";
+ };
+
+ sizeSoftLimit = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "30g";
+ description = "Soft limit of the project size";
+ };
+
+ sizeHardLimit = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "50g";
+ description = "Hard limit of the project size.";
+ };
+ };
+ });
+
+ description = "Setup of xfs_quota projects. Make sure the filesystem is mounted with the pquota option.";
+
+ example = {
+ projname = {
+ id = 50;
+ path = "/xfsprojects/projname";
+ sizeHardLimit = "50g";
+ };
+ };
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf (cfg.projects != {}) {
+
+ environment.etc.projects.source = pkgs.writeText "etc-project"
+ (concatStringsSep "\n" (mapAttrsToList
+ (name: opts: "${toString opts.id}:${opts.path}") cfg.projects));
+
+ environment.etc.projid.source = pkgs.writeText "etc-projid"
+ (concatStringsSep "\n" (mapAttrsToList
+ (name: opts: "${name}:${toString opts.id}") cfg.projects));
+
+ systemd.services = mapAttrs' (name: opts:
+ nameValuePair "xfs_quota-${name}" {
+ description = "Setup xfs_quota for project ${name}";
+ script = ''
+ ${pkgs.xfsprogs.bin}/bin/xfs_quota -x -c 'project -s ${name}' ${opts.fileSystem}
+ ${pkgs.xfsprogs.bin}/bin/xfs_quota -x -c 'limit -p ${limitOptions opts} ${name}' ${opts.fileSystem}
+ '';
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ ((replaceChars [ "/" ] [ "-" ] opts.fileSystem) + ".mount") ];
+
+ restartTriggers = [ config.environment.etc.projects.source ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ }
+ ) cfg.projects;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/xonsh.nix b/nixpkgs/nixos/modules/programs/xonsh.nix
new file mode 100644
index 00000000000..1590020f7b6
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/xonsh.nix
@@ -0,0 +1,60 @@
+# This module defines global configuration for the xonsh.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.xonsh;
+
+in
+
+{
+
+ options = {
+
+ programs.xonsh = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to configure xonsh as an interactive shell.
+ '';
+ type = types.bool;
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.xonsh;
+ example = literalExample "pkgs.xonsh.override { configFile = \"/path/to/xonshrc\"; }";
+ description = ''
+ xonsh package to use.
+ '';
+ };
+
+ config = mkOption {
+ default = "";
+ description = "Control file to customize your shell behavior.";
+ type = types.lines;
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.etc.xonshrc.text = cfg.config;
+
+ environment.systemPackages = [ cfg.package ];
+
+ environment.shells =
+ [ "/run/current-system/sw/bin/xonsh"
+ "${cfg.package}/bin/xonsh"
+ ];
+
+ };
+
+}
+
diff --git a/nixpkgs/nixos/modules/programs/xss-lock.nix b/nixpkgs/nixos/modules/programs/xss-lock.nix
new file mode 100644
index 00000000000..a7ad9b89db4
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/xss-lock.nix
@@ -0,0 +1,44 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.xss-lock;
+in
+{
+ options.programs.xss-lock = {
+ enable = mkEnableOption "xss-lock";
+
+ lockerCommand = mkOption {
+ default = "${pkgs.i3lock}/bin/i3lock";
+ example = literalExample ''''${pkgs.i3lock-fancy}/bin/i3lock-fancy'';
+ type = types.separatedString " ";
+ description = "Locker to be used with xsslock";
+ };
+
+ extraOptions = mkOption {
+ default = [ ];
+ example = [ "--ignore-sleep" ];
+ type = types.listOf types.str;
+ description = ''
+ Additional command-line arguments to pass to
+ <command>xss-lock</command>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.xss-lock = {
+ description = "XSS Lock Daemon";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+ serviceConfig.ExecStart = with lib;
+ strings.concatStringsSep " " ([
+ "${pkgs.xss-lock}/bin/xss-lock"
+ ] ++ (map escapeShellArg cfg.extraOptions) ++ [
+ "--"
+ cfg.lockerCommand
+ ]);
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/yabar.nix b/nixpkgs/nixos/modules/programs/yabar.nix
new file mode 100644
index 00000000000..5de9331ac52
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/yabar.nix
@@ -0,0 +1,162 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.yabar;
+
+ mapExtra = v: lib.concatStringsSep "\n" (mapAttrsToList (
+ key: val: "${key} = ${if (isString val) then "\"${val}\"" else "${builtins.toString val}"};"
+ ) v);
+
+ listKeys = r: concatStringsSep "," (map (n: "\"${n}\"") (attrNames r));
+
+ configFile = let
+ bars = mapAttrsToList (
+ name: cfg: ''
+ ${name}: {
+ font: "${cfg.font}";
+ position: "${cfg.position}";
+
+ ${mapExtra cfg.extra}
+
+ block-list: [${listKeys cfg.indicators}]
+
+ ${concatStringsSep "\n" (mapAttrsToList (
+ name: cfg: ''
+ ${name}: {
+ exec: "${cfg.exec}";
+ align: "${cfg.align}";
+ ${mapExtra cfg.extra}
+ };
+ ''
+ ) cfg.indicators)}
+ };
+ ''
+ ) cfg.bars;
+ in pkgs.writeText "yabar.conf" ''
+ bar-list = [${listKeys cfg.bars}];
+ ${concatStringsSep "\n" bars}
+ '';
+in
+ {
+ options.programs.yabar = {
+ enable = mkEnableOption "yabar";
+
+ package = mkOption {
+ 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.
+
+ Nixpkgs provides the `yabar` and `yabar-unstable`
+ derivations since 18.03, so it's possible to choose.
+ '';
+ };
+
+ bars = mkOption {
+ default = {};
+ type = types.attrsOf(types.submodule {
+ options = {
+ font = mkOption {
+ default = "sans bold 9";
+ example = "Droid Sans, FontAwesome Bold 9";
+ type = types.str;
+
+ description = ''
+ The font that will be used to draw the status bar.
+ '';
+ };
+
+ position = mkOption {
+ default = "top";
+ example = "bottom";
+ type = types.enum [ "top" "bottom" ];
+
+ description = ''
+ The position where the bar will be rendered.
+ '';
+ };
+
+ extra = mkOption {
+ default = {};
+ type = types.attrsOf types.str;
+
+ description = ''
+ An attribute set which contains further attributes of a bar.
+ '';
+ };
+
+ indicators = mkOption {
+ default = {};
+ type = types.attrsOf(types.submodule {
+ options.exec = mkOption {
+ example = "YABAR_DATE";
+ type = types.str;
+ description = ''
+ The type of the indicator to be executed.
+ '';
+ };
+
+ options.align = mkOption {
+ default = "left";
+ example = "right";
+ type = types.enum [ "left" "center" "right" ];
+
+ description = ''
+ Whether to align the indicator at the left or right of the bar.
+ '';
+ };
+
+ options.extra = mkOption {
+ default = {};
+ type = types.attrsOf (types.either types.str types.int);
+
+ description = ''
+ An attribute set which contains further attributes of a indicator.
+ '';
+ };
+ });
+
+ description = ''
+ Indicators that should be rendered by yabar.
+ '';
+ };
+ };
+ });
+
+ description = ''
+ List of bars that should be rendered by yabar.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.yabar = {
+ description = "yabar service";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+
+ script = ''
+ ${cfg.package}/bin/yabar -c ${configFile}
+ '';
+
+ serviceConfig.Restart = "always";
+ };
+ };
+ }
diff --git a/nixpkgs/nixos/modules/programs/zmap.nix b/nixpkgs/nixos/modules/programs/zmap.nix
new file mode 100644
index 00000000000..2e27fce4d7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/zmap.nix
@@ -0,0 +1,18 @@
+{ pkgs, config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.zmap;
+in {
+ options.programs.zmap = {
+ enable = mkEnableOption "ZMap";
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.zmap ];
+
+ environment.etc."zmap/blacklist.conf".source = "${pkgs.zmap}/etc/zmap/blacklist.conf";
+ environment.etc."zmap/zmap.conf".source = "${pkgs.zmap}/etc/zmap.conf";
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.nix
new file mode 100644
index 00000000000..f4df4e983e4
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.nix
@@ -0,0 +1,138 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.zsh.ohMyZsh;
+
+ mkLinkFarmEntry = name: dir:
+ let
+ env = pkgs.buildEnv {
+ name = "zsh-${name}-env";
+ paths = cfg.customPkgs;
+ pathsToLink = "/share/zsh/${dir}";
+ };
+ in
+ { inherit name; path = "${env}/share/zsh/${dir}"; };
+
+ mkLinkFarmEntry' = name: mkLinkFarmEntry name name;
+
+ custom =
+ if cfg.custom != null then cfg.custom
+ else if length cfg.customPkgs == 0 then null
+ else pkgs.linkFarm "oh-my-zsh-custom" [
+ (mkLinkFarmEntry' "themes")
+ (mkLinkFarmEntry "completions" "site-functions")
+ (mkLinkFarmEntry' "plugins")
+ ];
+
+in
+ {
+ options = {
+ programs.zsh.ohMyZsh = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Enable oh-my-zsh.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.oh-my-zsh;
+ defaultText = "pkgs.oh-my-zsh";
+ description = ''
+ Package to install for `oh-my-zsh` usage.
+ '';
+
+ type = types.package;
+ };
+
+ plugins = mkOption {
+ default = [];
+ type = types.listOf(types.str);
+ description = ''
+ List of oh-my-zsh plugins
+ '';
+ };
+
+ custom = mkOption {
+ default = null;
+ type = with types; nullOr str;
+ description = ''
+ Path to a custom oh-my-zsh package to override config of oh-my-zsh.
+ (Can't be used along with `customPkgs`).
+ '';
+ };
+
+ customPkgs = mkOption {
+ default = [];
+ type = types.listOf types.package;
+ description = ''
+ List of custom packages that should be loaded into `oh-my-zsh`.
+ '';
+ };
+
+ theme = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Name of the theme to be used by oh-my-zsh.
+ '';
+ };
+
+ cacheDir = mkOption {
+ default = "$HOME/.cache/oh-my-zsh";
+ type = types.str;
+ description = ''
+ Cache directory to be used by `oh-my-zsh`.
+ Without this option it would default to the read-only nix store.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ # Prevent zsh from overwriting oh-my-zsh's prompt
+ programs.zsh.promptInit = mkDefault "";
+
+ environment.systemPackages = [ cfg.package ];
+
+ programs.zsh.interactiveShellInit = ''
+ # oh-my-zsh configuration generated by NixOS
+ export ZSH=${cfg.package}/share/oh-my-zsh
+
+ ${optionalString (length(cfg.plugins) > 0)
+ "plugins=(${concatStringsSep " " cfg.plugins})"
+ }
+
+ ${optionalString (custom != null)
+ "ZSH_CUSTOM=\"${custom}\""
+ }
+
+ ${optionalString (stringLength(cfg.theme) > 0)
+ "ZSH_THEME=\"${cfg.theme}\""
+ }
+
+ ${optionalString (cfg.cacheDir != null) ''
+ if [[ ! -d "${cfg.cacheDir}" ]]; then
+ mkdir -p "${cfg.cacheDir}"
+ fi
+ ZSH_CACHE_DIR=${cfg.cacheDir}
+ ''}
+
+ source $ZSH/oh-my-zsh.sh
+ '';
+
+ assertions = [
+ {
+ assertion = cfg.custom != null -> cfg.customPkgs == [];
+ message = "If `cfg.custom` is set for `ZSH_CUSTOM`, `customPkgs` can't be used!";
+ }
+ ];
+
+ };
+
+ meta.doc = ./oh-my-zsh.xml;
+ }
diff --git a/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.xml b/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.xml
new file mode 100644
index 00000000000..568c2de6557
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.xml
@@ -0,0 +1,155 @@
+<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-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>.
+<programlisting>
+{
+ 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:
+<programlisting>
+{
+ 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:
+<programlisting>
+{ 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.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>
+
+ <para>
+ A derivation for <literal>oh-my-zsh</literal> may look like this:
+<programlisting>
+{ stdenv, fetchFromGitHub }:
+
+stdenv.mkDerivation rec {
+ name = "exemplary-zsh-customization-${version}";
+ version = "1.0.0";
+ src = fetchFromGitHub {
+ # path to the upstream repository
+ };
+
+ dontBuild = true;
+ installPhase = ''
+ mkdir -p $out/share/zsh/site-functions
+ cp {themes,plugins} $out/share/zsh
+ cp completions $out/share/zsh/site-functions
+ '';
+}
+</programlisting>
+ </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/programs/zsh/zinputrc b/nixpkgs/nixos/modules/programs/zsh/zinputrc
new file mode 100644
index 00000000000..6121f3e21f1
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/zsh/zinputrc
@@ -0,0 +1,42 @@
+# Stolen from ArchWiki
+
+# create a zkbd compatible hash;
+# to add other keys to this hash, see: man 5 terminfo
+typeset -A key
+
+key[Home]=${terminfo[khome]}
+
+key[End]=${terminfo[kend]}
+key[Insert]=${terminfo[kich1]}
+key[Delete]=${terminfo[kdch1]}
+key[Up]=${terminfo[kcuu1]}
+key[Down]=${terminfo[kcud1]}
+key[Left]=${terminfo[kcub1]}
+key[Right]=${terminfo[kcuf1]}
+key[PageUp]=${terminfo[kpp]}
+key[PageDown]=${terminfo[knp]}
+
+# setup key accordingly
+[[ -n "${key[Home]}" ]] && bindkey "${key[Home]}" beginning-of-line
+[[ -n "${key[End]}" ]] && bindkey "${key[End]}" end-of-line
+[[ -n "${key[Insert]}" ]] && bindkey "${key[Insert]}" overwrite-mode
+[[ -n "${key[Delete]}" ]] && bindkey "${key[Delete]}" delete-char
+[[ -n "${key[Up]}" ]] && bindkey "${key[Up]}" up-line-or-history
+[[ -n "${key[Down]}" ]] && bindkey "${key[Down]}" down-line-or-history
+[[ -n "${key[Left]}" ]] && bindkey "${key[Left]}" backward-char
+[[ -n "${key[Right]}" ]] && bindkey "${key[Right]}" forward-char
+[[ -n "${key[PageUp]}" ]] && bindkey "${key[PageUp]}" beginning-of-buffer-or-history
+[[ -n "${key[PageDown]}" ]] && bindkey "${key[PageDown]}" end-of-buffer-or-history
+
+# Finally, make sure the terminal is in application mode, when zle is
+# active. Only then are the values from $terminfo valid.
+if (( ${+terminfo[smkx]} )) && (( ${+terminfo[rmkx]} )); then
+ function zle-line-init () {
+ printf '%s' "${terminfo[smkx]}"
+ }
+ function zle-line-finish () {
+ printf '%s' "${terminfo[rmkx]}"
+ }
+ zle -N zle-line-init
+ zle -N zle-line-finish
+fi
diff --git a/nixpkgs/nixos/modules/programs/zsh/zsh-autoenv.nix b/nixpkgs/nixos/modules/programs/zsh/zsh-autoenv.nix
new file mode 100644
index 00000000000..630114bcda9
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/zsh/zsh-autoenv.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.zsh.zsh-autoenv;
+in {
+ options = {
+ programs.zsh.zsh-autoenv = {
+ enable = mkEnableOption "zsh-autoenv";
+ package = mkOption {
+ default = pkgs.zsh-autoenv;
+ defaultText = "pkgs.zsh-autoenv";
+ description = ''
+ Package to install for `zsh-autoenv` usage.
+ '';
+
+ type = types.package;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.zsh.interactiveShellInit = ''
+ source ${cfg.package}/share/zsh-autoenv/autoenv.zsh
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix b/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix
new file mode 100644
index 00000000000..ded17f38a61
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix
@@ -0,0 +1,60 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.zsh.autosuggestions;
+in
+{
+ options.programs.zsh.autosuggestions = {
+
+ enable = mkEnableOption "zsh-autosuggestions";
+
+ highlightStyle = mkOption {
+ type = types.str;
+ default = "fg=8"; # https://github.com/zsh-users/zsh-autosuggestions/tree/v0.4.3#suggestion-highlight-style
+ description = "Highlight style for suggestions ({fore,back}ground color)";
+ example = "fg=cyan";
+ };
+
+ strategy = mkOption {
+ 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:
+
+ * 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
+ HIST_IGNORE_ALL_DUPS or HIST_EXPIRE_DUPS_FIRST.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = with types; attrsOf str;
+ default = {};
+ description = "Attribute set with additional configuration values";
+ example = literalExample ''
+ {
+ "ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" = "20";
+ }
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ programs.zsh.interactiveShellInit = ''
+ source ${pkgs.zsh-autosuggestions}/share/zsh-autosuggestions/zsh-autosuggestions.zsh
+
+ export ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="${cfg.highlightStyle}"
+ export ZSH_AUTOSUGGEST_STRATEGY=("${cfg.strategy}")
+
+ ${concatStringsSep "\n" (mapAttrsToList (key: value: ''export ${key}="${value}"'') cfg.extraConfig)}
+ '';
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix b/nixpkgs/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
new file mode 100644
index 00000000000..7184e5d9b9a
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.zsh.syntaxHighlighting;
+in
+{
+ options = {
+ programs.zsh.syntaxHighlighting = {
+ enable = mkEnableOption "zsh-syntax-highlighting";
+
+ highlighters = mkOption {
+ default = [ "main" ];
+
+ # https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/docs/highlighters.md
+ type = types.listOf(types.enum([
+ "main"
+ "brackets"
+ "pattern"
+ "cursor"
+ "root"
+ "line"
+ ]));
+
+ description = ''
+ Specifies the highlighters to be used by zsh-syntax-highlighting.
+
+ The following defined options can be found here:
+ https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/docs/highlighters.md
+ '';
+ };
+
+ patterns = mkOption {
+ default = {};
+ type = types.attrsOf types.str;
+
+ example = literalExample ''
+ {
+ "rm -rf *" = "fg=white,bold,bg=red";
+ }
+ '';
+
+ description = ''
+ Specifies custom patterns 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/pattern.md
+ '';
+ };
+ styles = mkOption {
+ default = {};
+ type = types.attrsOf types.str;
+
+ 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
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [ zsh-syntax-highlighting ];
+
+ assertions = [
+ {
+ assertion = length(attrNames cfg.patterns) > 0 -> elem "pattern" cfg.highlighters;
+ message = ''
+ When highlighting patterns, "pattern" needs to be included in the list of highlighters.
+ '';
+ }
+ ];
+
+ programs.zsh.interactiveShellInit = with pkgs;
+ lib.concatStringsSep "\n" ([
+ "source ${zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
+ ] ++ optional (length(cfg.highlighters) > 0)
+ "ZSH_HIGHLIGHT_HIGHLIGHTERS=(${concatStringsSep " " cfg.highlighters})"
+ ++ optionals (length(attrNames cfg.patterns) > 0)
+ (mapAttrsToList (
+ 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/nixpkgs/nixos/modules/programs/zsh/zsh.nix b/nixpkgs/nixos/modules/programs/zsh/zsh.nix
new file mode 100644
index 00000000000..c66c29ed45f
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/zsh/zsh.nix
@@ -0,0 +1,244 @@
+# This module defines global configuration for the zshell.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfge = config.environment;
+
+ cfg = config.programs.zsh;
+
+ zshAliases = concatStringsSep "\n" (
+ mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
+ (filterAttrs (k: v: v != null) cfg.shellAliases)
+ );
+
+in
+
+{
+
+ options = {
+
+ programs.zsh = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to configure zsh as an interactive shell. To enable zsh for
+ a particular user, use the <option>users.users.&lt;name?&gt;.shell</option>
+ option for that user. To enable zsh system-wide use the
+ <option>users.defaultUserShell</option> option.
+ '';
+ type = types.bool;
+ };
+
+ shellAliases = mkOption {
+ default = {};
+ description = ''
+ Set of aliases for zsh shell, which overrides <option>environment.shellAliases</option>.
+ See <option>environment.shellAliases</option> for an option format description.
+ '';
+ type = with types; attrsOf (nullOr (either str path));
+ };
+
+ shellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during zsh shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ loginShellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during zsh login shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ interactiveShellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during interactive zsh shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ promptInit = mkOption {
+ default = ''
+ autoload -U promptinit && promptinit && prompt walters && setopt prompt_sp
+ '';
+ description = ''
+ Shell script code used to initialise the zsh prompt.
+ '';
+ type = types.lines;
+ };
+
+ histSize = mkOption {
+ default = 2000;
+ description = ''
+ Change history size.
+ '';
+ type = types.int;
+ };
+
+ histFile = mkOption {
+ default = "$HOME/.zsh_history";
+ description = ''
+ Change history file.
+ '';
+ type = types.str;
+ };
+
+ setOptions = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "HIST_IGNORE_DUPS" "SHARE_HISTORY" "HIST_FCNTL_LOCK"
+ ];
+ example = [ "EXTENDED_HISTORY" "RM_STAR_WAIT" ];
+ description = ''
+ Configure zsh options.
+ '';
+ };
+
+ enableCompletion = mkOption {
+ default = true;
+ description = ''
+ Enable zsh completion for all interactive zsh shells.
+ '';
+ type = types.bool;
+ };
+
+
+ enableGlobalCompInit = mkOption {
+ default = cfg.enableCompletion;
+ description = ''
+ Enable execution of compinit call for all interactive zsh shells.
+
+ This option can be disabled if the user wants to extend its
+ <literal>fpath</literal> and a custom <literal>compinit</literal>
+ call in the local config is required.
+ '';
+ type = types.bool;
+ };
+
+ };
+
+ };
+
+ 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.
+ # This file is read for all shells.
+
+ # Only execute this file once per shell.
+ # But don't clobber the environment of interactive non-login children!
+ if [ -n "$__ETC_ZSHENV_SOURCED" ]; then return; fi
+ export __ETC_ZSHENV_SOURCED=1
+
+ if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then
+ . ${config.system.build.setEnvironment}
+ fi
+
+ ${cfge.shellInit}
+
+ ${cfg.shellInit}
+
+ # Read system-wide modifications.
+ if test -f /etc/zshenv.local; then
+ . /etc/zshenv.local
+ fi
+ '';
+
+ environment.etc.zprofile.text =
+ ''
+ # /etc/zprofile: DO NOT EDIT -- this file has been generated automatically.
+ # This file is read for login shells.
+
+ # Only execute this file once per shell.
+ if [ -n "$__ETC_ZPROFILE_SOURCED" ]; then return; fi
+ __ETC_ZPROFILE_SOURCED=1
+
+ ${cfge.loginShellInit}
+
+ ${cfg.loginShellInit}
+
+ # Read system-wide modifications.
+ if test -f /etc/zprofile.local; then
+ . /etc/zprofile.local
+ fi
+ '';
+
+ environment.etc.zshrc.text =
+ ''
+ # /etc/zshrc: DO NOT EDIT -- this file has been generated automatically.
+ # This file is read for interactive shells.
+
+ # Only execute this file once per shell.
+ if [ -n "$__ETC_ZSHRC_SOURCED" -o -n "$NOSYSZSHRC" ]; then return; fi
+ __ETC_ZSHRC_SOURCED=1
+
+ . /etc/zinputrc
+
+ # Don't export these, otherwise other shells (bash) will try to use same histfile
+ SAVEHIST=${toString cfg.histSize}
+ HISTSIZE=${toString cfg.histSize}
+ HISTFILE=${cfg.histFile}
+
+ HELPDIR="${pkgs.zsh}/share/zsh/$ZSH_VERSION/help"
+
+ # Tell zsh how to find installed completions
+ for p in ''${(z)NIX_PROFILES}; do
+ fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions)
+ done
+
+ ${optionalString cfg.enableGlobalCompInit "autoload -U compinit && compinit"}
+
+ ${cfge.interactiveShellInit}
+
+ ${cfg.interactiveShellInit}
+
+ ${optionalString (cfg.setOptions != []) "setopt ${concatStringsSep " " cfg.setOptions}"}
+
+ ${zshAliases}
+
+ ${cfg.promptInit}
+
+ # Need to disable features to support TRAMP
+ if [ "$TERM" = dumb ]; then
+ unsetopt zle prompt_cr prompt_subst
+ unset RPS1 RPROMPT
+ PS1='$ '
+ PROMPT='$ '
+ fi
+
+ # Read system-wide modifications.
+ if test -f /etc/zshrc.local; then
+ . /etc/zshrc.local
+ fi
+ '';
+
+ environment.etc.zinputrc.source = ./zinputrc;
+
+ environment.systemPackages = [ pkgs.zsh ]
+ ++ optional cfg.enableCompletion pkgs.nix-zsh-completions;
+
+ environment.pathsToLink = optional cfg.enableCompletion "/share/zsh";
+
+ #users.defaultUserShell = mkDefault "/run/current-system/sw/bin/zsh";
+
+ environment.shells =
+ [ "/run/current-system/sw/bin/zsh"
+ "${pkgs.zsh}/bin/zsh"
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/rename.nix b/nixpkgs/nixos/modules/rename.nix
new file mode 100644
index 00000000000..802ffcdc94e
--- /dev/null
+++ b/nixpkgs/nixos/modules/rename.nix
@@ -0,0 +1,292 @@
+{ lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [
+ (mkRenamedOptionModule [ "networking" "enableRT73Firmware" ] [ "hardware" "enableRedistributableFirmware" ])
+ (mkRenamedOptionModule [ "networking" "enableIntel3945ABGFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
+ (mkRenamedOptionModule [ "networking" "enableIntel2100BGFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
+ (mkRenamedOptionModule [ "networking" "enableRalinkFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
+ (mkRenamedOptionModule [ "networking" "enableRTL8192cFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
+ (mkRenamedOptionModule [ "networking" "networkmanager" "useDnsmasq" ] [ "networking" "networkmanager" "dns" ])
+ (mkChangedOptionModule [ "services" "printing" "gutenprint" ] [ "services" "printing" "drivers" ]
+ (config:
+ let enabled = getAttrFromPath [ "services" "printing" "gutenprint" ] config;
+ in if enabled then [ pkgs.gutenprint ] else [ ]))
+ (mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ]
+ (config:
+ let value = getAttrFromPath [ "services" "ddclient" "domain" ] config;
+ in if value != "" then [ value ] else []))
+ (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
+ (mkRenamedOptionModule [ "services" "flatpak" "extraPortals" ] [ "xdg" "portal" "extraPortals" ])
+ (mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ])
+ (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "admissionControl" ] [ "services" "kubernetes" "apiserver" "enableAdmissionPlugins" ])
+ (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "address" ] ["services" "kubernetes" "apiserver" "bindAddress"])
+ (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "port" ] ["services" "kubernetes" "apiserver" "insecurePort"])
+ (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "publicAddress" ] "")
+ (mkRenamedOptionModule [ "services" "kubernetes" "addons" "dashboard" "enableRBAC" ] [ "services" "kubernetes" "addons" "dashboard" "rbac" "enable" ])
+ (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "address" ] ["services" "kubernetes" "controllerManager" "bindAddress"])
+ (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "port" ] ["services" "kubernetes" "controllerManager" "insecurePort"])
+ (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "servers" ] [ "services" "kubernetes" "apiserver" "etcd" "servers" ])
+ (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "keyFile" ] [ "services" "kubernetes" "apiserver" "etcd" "keyFile" ])
+ (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "certFile" ] [ "services" "kubernetes" "apiserver" "etcd" "certFile" ])
+ (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "caFile" ] [ "services" "kubernetes" "apiserver" "etcd" "caFile" ])
+ (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "applyManifests" ] "")
+ (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "")
+ (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "allowPrivileged" ] "")
+ (mkRenamedOptionModule [ "services" "kubernetes" "proxy" "address" ] ["services" "kubernetes" "proxy" "bindAddress"])
+ (mkRemovedOptionModule [ "services" "kubernetes" "verbose" ] "")
+ (mkRenamedOptionModule [ "services" "logstash" "address" ] [ "services" "logstash" "listenAddress" ])
+ (mkRenamedOptionModule [ "services" "neo4j" "host" ] [ "services" "neo4j" "defaultListenAddress" ])
+ (mkRenamedOptionModule [ "services" "neo4j" "listenAddress" ] [ "services" "neo4j" "defaultListenAddress" ])
+ (mkRenamedOptionModule [ "services" "neo4j" "enableBolt" ] [ "services" "neo4j" "bolt" "enable" ])
+ (mkRenamedOptionModule [ "services" "neo4j" "enableHttps" ] [ "services" "neo4j" "https" "enable" ])
+ (mkRenamedOptionModule [ "services" "neo4j" "certDir" ] [ "services" "neo4j" "directories" "certificates" ])
+ (mkRenamedOptionModule [ "services" "neo4j" "dataDir" ] [ "services" "neo4j" "directories" "home" ])
+ (mkRemovedOptionModule [ "services" "neo4j" "port" ] "Use services.neo4j.http.listenAddress instead.")
+ (mkRemovedOptionModule [ "services" "neo4j" "boltPort" ] "Use services.neo4j.bolt.listenAddress instead.")
+ (mkRemovedOptionModule [ "services" "neo4j" "httpsPort" ] "Use services.neo4j.https.listenAddress instead.")
+ (mkRemovedOptionModule [ "services" "misc" "nzbget" "configFile" ] "The configuration of nzbget is now managed by users through the web interface.")
+ (mkRemovedOptionModule [ "services" "misc" "nzbget" "dataDir" ] "The data directory for nzbget is now /var/lib/nzbget.")
+ (mkRemovedOptionModule [ "services" "misc" "nzbget" "openFirewall" ] "The port used by nzbget is managed through the web interface so you should adjust your firewall rules accordingly.")
+ (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "user" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a user setting.")
+ (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "group" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a group setting.")
+ (mkRemovedOptionModule [ "services" "prometheus" "alertmanagerURL" ] ''
+ Due to incompatibility, the alertmanagerURL option has been removed,
+ please use 'services.prometheus2.alertmanagers' instead.
+ '')
+ (mkRenamedOptionModule [ "services" "prometheus2" ] [ "services" "prometheus" ])
+ (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "relay" "port" ])
+ (mkRenamedOptionModule [ "services" "vmwareGuest" ] [ "virtualisation" "vmware" "guest" ])
+ (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ])
+
+ (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ])
+ (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "")
+
+ (mkRenamedOptionModule [ "services" "clamav" "updater" "config" ] [ "services" "clamav" "updater" "extraConfig" ])
+
+ (mkRemovedOptionModule [ "services" "pykms" "verbose" ] "Use services.pykms.logLevel instead")
+
+ (mkRemovedOptionModule [ "security" "setuidOwners" ] "Use security.wrappers instead")
+ (mkRemovedOptionModule [ "security" "setuidPrograms" ] "Use security.wrappers instead")
+
+ (mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ])
+
+ # PAM
+ (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
+
+ # rmilter/rspamd
+ (mkRemovedOptionModule [ "services" "rmilter" ] "Use services.rspamd.* instead to set up milter service")
+
+ # Xsession script
+ (mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logsXsession" ] [ "services" "xserver" "displayManager" "job" "logToFile" ])
+ (mkRenamedOptionModule [ "services" "xserver" "displayManager" "logToJournal" ] [ "services" "xserver" "displayManager" "job" "logToJournal" ])
+
+ # Old Grub-related options.
+ (mkRenamedOptionModule [ "boot" "loader" "grub" "timeout" ] [ "boot" "loader" "timeout" ])
+ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "timeout" ] [ "boot" "loader" "timeout" ])
+
+ # OpenSSH
+ (mkAliasOptionModule [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ])
+ (mkAliasOptionModule [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ])
+
+ # libvirtd
+ (mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ]
+ "Set the option `virtualisation.libvirtd.qemuPackage' instead.")
+
+ # ibus
+ (mkRenamedOptionModule [ "programs" "ibus" "plugins" ] [ "i18n" "inputMethod" "ibus" "engines" ])
+
+ # sandboxing
+ (mkRenamedOptionModule [ "nix" "useChroot" ] [ "nix" "useSandbox" ])
+ (mkRenamedOptionModule [ "nix" "chrootDirs" ] [ "nix" "sandboxPaths" ])
+
+ (mkRenamedOptionModule [ "services" "xserver" "vaapiDrivers" ] [ "hardware" "opengl" "extraPackages" ])
+
+ (mkAliasOptionModule [ "environment" "checkConfigurationOptions" ] [ "_module" "check" ])
+
+ # opendkim
+ (mkRenamedOptionModule [ "services" "opendkim" "keyFile" ] [ "services" "opendkim" "keyPath" ])
+
+ # Enlightenment
+ (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "e19" "enable" ] [ "services" "xserver" "desktopManager" "enlightenment" "enable" ])
+
+ # Iodine
+ (mkRenamedOptionModule [ "services" "iodined" "enable" ] [ "services" "iodine" "server" "enable" ])
+ (mkRenamedOptionModule [ "services" "iodined" "domain" ] [ "services" "iodine" "server" "domain" ])
+ (mkRenamedOptionModule [ "services" "iodined" "ip" ] [ "services" "iodine" "server" "ip" ])
+ (mkRenamedOptionModule [ "services" "iodined" "extraConfig" ] [ "services" "iodine" "server" "extraConfig" ])
+ (mkRemovedOptionModule [ "services" "iodined" "client" ] "")
+
+ # Unity3D
+ (mkRenamedOptionModule [ "programs" "unity3d" "enable" ] [ "security" "chromiumSuidSandbox" "enable" ])
+
+ # murmur
+ (mkRenamedOptionModule [ "services" "murmur" "welcome" ] [ "services" "murmur" "welcometext" ])
+ (mkRemovedOptionModule [ "services" "murmur" "pidfile" ] "Hardcoded to /run/murmur/murmurd.pid now")
+
+ # parsoid
+ (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] "Use services.parsoid.wikis instead")
+
+ # plexpy / tautulli
+ (mkRenamedOptionModule [ "services" "plexpy" ] [ "services" "tautulli" ])
+
+ # piwik was renamed to matomo
+ (mkRenamedOptionModule [ "services" "piwik" "enable" ] [ "services" "matomo" "enable" ])
+ (mkRenamedOptionModule [ "services" "piwik" "webServerUser" ] [ "services" "matomo" "webServerUser" ])
+ (mkRenamedOptionModule [ "services" "piwik" "phpfpmProcessManagerConfig" ] [ "services" "matomo" "phpfpmProcessManagerConfig" ])
+ (mkRenamedOptionModule [ "services" "piwik" "nginx" ] [ "services" "matomo" "nginx" ])
+
+ # tarsnap
+ (mkRemovedOptionModule [ "services" "tarsnap" "cachedir" ] "Use services.tarsnap.archives.<name>.cachedir")
+
+ # alsa
+ (mkRenamedOptionModule [ "sound" "enableMediaKeys" ] [ "sound" "mediaKeys" "enable" ])
+
+ # postgrey
+ (mkMergedOptionModule [ [ "services" "postgrey" "inetAddr" ] [ "services" "postgrey" "inetPort" ] ] [ "services" "postgrey" "socket" ] (config: let
+ value = p: getAttrFromPath p config;
+ inetAddr = [ "services" "postgrey" "inetAddr" ];
+ inetPort = [ "services" "postgrey" "inetPort" ];
+ in
+ if value inetAddr == null
+ then { path = "/run/postgrey.sock"; }
+ else { addr = value inetAddr; port = value inetPort; }
+ ))
+
+ # dhcpd
+ (mkRenamedOptionModule [ "services" "dhcpd" ] [ "services" "dhcpd4" ])
+
+ # locate
+ (mkRenamedOptionModule [ "services" "locate" "period" ] [ "services" "locate" "interval" ])
+ (mkRemovedOptionModule [ "services" "locate" "includeStore" ] "Use services.locate.prunePaths" )
+
+ # nfs
+ (mkRenamedOptionModule [ "services" "nfs" "lockdPort" ] [ "services" "nfs" "server" "lockdPort" ])
+ (mkRenamedOptionModule [ "services" "nfs" "statdPort" ] [ "services" "nfs" "server" "statdPort" ])
+
+ # KDE Plasma 5
+ (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "kde5" ] [ "services" "xserver" "desktopManager" "plasma5" ])
+
+ # Fontconfig
+ (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowBitmaps" ] [ "fonts" "fontconfig" "allowBitmaps" ])
+ (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowType1" ] [ "fonts" "fontconfig" "allowType1" ])
+ (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "useEmbeddedBitmaps" ] [ "fonts" "fontconfig" "useEmbeddedBitmaps" ])
+ (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "forceAutohint" ] [ "fonts" "fontconfig" "forceAutohint" ])
+ (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "renderMonoTTFAsBitmap" ] [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ])
+
+ # postgresqlBackup
+ (mkRemovedOptionModule [ "services" "postgresqlBackup" "period" ] ''
+ A systemd timer is now used instead of cron.
+ The starting time can be configured via <literal>services.postgresqlBackup.startAt</literal>.
+ '')
+
+ # phpfpm
+ (mkRemovedOptionModule [ "services" "phpfpm" "poolConfigs" ] "Use services.phpfpm.pools instead.")
+
+ # zabbixServer
+ (mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ])
+
+ # Profile splitting
+ (mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ])
+
+ # misc/version.nix
+ (mkRenamedOptionModule [ "system" "nixosVersion" ] [ "system" "nixos" "version" ])
+ (mkRenamedOptionModule [ "system" "nixosVersionSuffix" ] [ "system" "nixos" "versionSuffix" ])
+ (mkRenamedOptionModule [ "system" "nixosRevision" ] [ "system" "nixos" "revision" ])
+ (mkRenamedOptionModule [ "system" "nixosLabel" ] [ "system" "nixos" "label" ])
+
+ # Users
+ (mkAliasOptionModule [ "users" "extraUsers" ] [ "users" "users" ])
+ (mkAliasOptionModule [ "users" "extraGroups" ] [ "users" "groups" ])
+
+ # Options that are obsolete and have no replacement.
+ (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
+ (mkRemovedOptionModule [ "programs" "bash" "enable" ] "")
+ (mkRemovedOptionModule [ "services" "samba" "defaultShare" ] "")
+ (mkRemovedOptionModule [ "services" "syslog-ng" "serviceName" ] "")
+ (mkRemovedOptionModule [ "services" "syslog-ng" "listenToJournal" ] "")
+ (mkRemovedOptionModule [ "ec2" "metadata" ] "")
+ (mkRemovedOptionModule [ "services" "openvpn" "enable" ] "")
+ (mkRemovedOptionModule [ "services" "printing" "cupsFilesConf" ] "")
+ (mkRemovedOptionModule [ "services" "printing" "cupsdConf" ] "")
+ (mkRemovedOptionModule [ "services" "tor" "relay" "isBridge" ] "Use services.tor.relay.role instead.")
+ (mkRemovedOptionModule [ "services" "tor" "relay" "isExit" ] "Use services.tor.relay.role instead.")
+ (mkRemovedOptionModule [ "services" "xserver" "startGnuPGAgent" ]
+ "See the 16.09 release notes for more information.")
+ (mkRemovedOptionModule [ "services" "phpfpm" "phpIni" ] "")
+ (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "")
+ (mkRemovedOptionModule [ "services" "firefox" "syncserver" "user" ] "")
+ (mkRemovedOptionModule [ "services" "firefox" "syncserver" "group" ] "")
+ (mkRemovedOptionModule [ "fonts" "fontconfig" "hinting" "style" ] "")
+ (mkRemovedOptionModule [ "services" "xserver" "displayManager" "sddm" "themes" ]
+ "Set the option `services.xserver.displayManager.sddm.package' instead.")
+ (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "xfce" "screenLock" ] "")
+ (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "")
+ (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "")
+ (mkRemovedOptionModule [ "virtualisation" "xen" "qemu" ] "You don't need this option anymore, it will work without it.")
+ (mkRemovedOptionModule [ "services" "logstash" "enableWeb" ] "The web interface was removed from logstash")
+ (mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.")
+ (mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.")
+ (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd")
+ (mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.")
+ (mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.")
+ (mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.")
+ (mkRemovedOptionModule [ "systemd" "coredump" "enable" ] "Enabled by default. Set boot.kernel.sysctl.\"kernel.core_pattern\" = \"core\"; to disable.")
+
+ # ZSH
+ (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
+ (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "enable" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
+ (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "highlighters" ] [ "programs" "zsh" "syntaxHighlighting" "highlighters" ])
+ (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "patterns" ] [ "programs" "zsh" "syntaxHighlighting" "patterns" ])
+ (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "enable" ] [ "programs" "zsh" "ohMyZsh" "enable" ])
+ (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "theme" ] [ "programs" "zsh" "ohMyZsh" "theme" ])
+ (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "custom" ] [ "programs" "zsh" "ohMyZsh" "custom" ])
+ (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "plugins" ] [ "programs" "zsh" "ohMyZsh" "plugins" ])
+
+ (mkRenamedOptionModule [ "programs" "zsh" "enableAutosuggestions" ] [ "programs" "zsh" "autosuggestions" "enable" ])
+
+ # Xen
+ (mkRenamedOptionModule [ "virtualisation" "xen" "qemu-package" ] [ "virtualisation" "xen" "package-qemu" ])
+
+ (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ])
+ (mkRenamedOptionModule [ "programs" "man" "enable" ] [ "documentation" "man" "enable" ])
+ (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ])
+
+ # ckb
+ (mkRenamedOptionModule [ "hardware" "ckb" "enable" ] [ "hardware" "ckb-next" "enable" ])
+ (mkRenamedOptionModule [ "hardware" "ckb" "package" ] [ "hardware" "ckb-next" "package" ])
+
+ # binfmt
+ (mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ])
+
+ # ACME
+ (mkRemovedOptionModule [ "security" "acme" "directory"] "ACME Directory is now hardcoded to /var/lib/acme and its permisisons are managed by systemd. See https://github.com/NixOS/nixpkgs/issues/53852 for more info.")
+ (mkRemovedOptionModule [ "security" "acme" "preDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal")
+ (mkRemovedOptionModule [ "security" "acme" "activationDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal")
+
+ # KSM
+ (mkRenamedOptionModule [ "hardware" "enableKSM" ] [ "hardware" "ksm" "enable" ])
+
+ # resolvconf
+ (mkRenamedOptionModule [ "networking" "dnsSingleRequest" ] [ "networking" "resolvconf" "dnsSingleRequest" ])
+ (mkRenamedOptionModule [ "networking" "dnsExtensionMechanism" ] [ "networking" "resolvconf" "dnsExtensionMechanism" ])
+ (mkRenamedOptionModule [ "networking" "extraResolvconfConf" ] [ "networking" "resolvconf" "extraConfig" ])
+ (mkRenamedOptionModule [ "networking" "resolvconfOptions" ] [ "networking" "resolvconf" "extraOptions" ])
+
+ # Redis
+ (mkRemovedOptionModule [ "services" "redis" "user" ] "The redis module now is hardcoded to the redis user.")
+ (mkRemovedOptionModule [ "services" "redis" "dbpath" ] "The redis module now uses /var/lib/redis as data directory.")
+ (mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.")
+ (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.")
+ (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.")
+
+ ] ++ (forEach [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
+ "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter"
+ "snmpExporter" "unifiExporter" "varnishExporter" ]
+ (opt: mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] ''
+ The prometheus exporters are now configured using `services.prometheus.exporters'.
+ See the 18.03 release notes for more information.
+ '' ));
+}
diff --git a/nixpkgs/nixos/modules/security/acme.nix b/nixpkgs/nixos/modules/security/acme.nix
new file mode 100644
index 00000000000..b321c04e574
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/acme.nix
@@ -0,0 +1,312 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.security.acme;
+
+ certOpts = { name, ... }: {
+ options = {
+ webroot = mkOption {
+ type = types.str;
+ example = "/var/lib/acme/acme-challenges";
+ description = ''
+ Where the webroot of the HTTP vhost is located.
+ <filename>.well-known/acme-challenge/</filename> directory
+ will be created below the webroot if it doesn't exist.
+ <literal>http://example.org/.well-known/acme-challenge/</literal> must also
+ be available (notice unencrypted HTTP).
+ '';
+ };
+
+ domain = mkOption {
+ type = types.str;
+ default = name;
+ description = "Domain to fetch certificate for (defaults to the entry name)";
+ };
+
+ email = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Contact email address for the CA to be able to reach you.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "root";
+ description = "User running the ACME client.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "root";
+ description = "Group running the ACME client.";
+ };
+
+ allowKeysForGroup = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Give read permissions to the specified group
+ (<option>security.acme.cert.&lt;name&gt;.group</option>) to read SSL private certificates.
+ '';
+ };
+
+ postRun = mkOption {
+ type = types.lines;
+ default = "";
+ example = "systemctl reload nginx.service";
+ description = ''
+ Commands to run after new certificates go live. Typically
+ the web server and other servers using certificates need to
+ be reloaded.
+
+ Executed in the same directory with the new certificate.
+ '';
+ };
+
+ plugins = mkOption {
+ type = types.listOf (types.enum [
+ "cert.der" "cert.pem" "chain.pem" "external.sh"
+ "fullchain.pem" "full.pem" "key.der" "key.pem" "account_key.json"
+ ]);
+ default = [ "fullchain.pem" "full.pem" "key.pem" "account_key.json" ];
+ description = ''
+ Plugins to enable. With default settings simp_le will
+ store public certificate bundle in <filename>fullchain.pem</filename>,
+ private key in <filename>key.pem</filename> and those two previous
+ files combined in <filename>full.pem</filename> in its state directory.
+ '';
+ };
+
+ directory = mkOption {
+ type = types.str;
+ readOnly = true;
+ default = "/var/lib/acme/${name}";
+ description = "Directory where certificate and other state is stored.";
+ };
+
+ extraDomains = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = {};
+ example = literalExample ''
+ {
+ "example.org" = "/srv/http/nginx";
+ "mydomain.org" = null;
+ }
+ '';
+ description = ''
+ A list of extra domain names, which are included in the one certificate to be issued, with their
+ own server roots if needed.
+ '';
+ };
+ };
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ security.acme = {
+
+ validMin = mkOption {
+ type = types.int;
+ default = 30 * 24 * 3600;
+ description = "Minimum remaining validity before renewal in seconds.";
+ };
+
+ renewInterval = mkOption {
+ type = types.str;
+ default = "weekly";
+ description = ''
+ Systemd calendar expression when to check for renewal. See
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ '';
+ };
+
+ preliminarySelfsigned = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether a preliminary self-signed certificate should be generated before
+ doing ACME requests. This can be useful when certificates are required in
+ a webserver, but ACME needs the webserver to make its requests.
+
+ With preliminary self-signed certificate the webserver can be started and
+ can later reload the correct ACME certificates.
+ '';
+ };
+
+ production = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If set to true, use Let's Encrypt's production environment
+ instead of the staging environment. The main benefit of the
+ staging environment is to get much higher rate limits.
+
+ See
+ <literal>https://letsencrypt.org/docs/staging-environment</literal>
+ for more detail.
+ '';
+ };
+
+ certs = mkOption {
+ default = { };
+ type = with types; attrsOf (submodule certOpts);
+ description = ''
+ Attribute set of certificates to get signed and renewed. Creates
+ <literal>acme-''${cert}.{service,timer}</literal> systemd units for
+ each certificate defined here. Other services can add dependencies
+ to those units if they rely on the certificates being present,
+ or trigger restarts of the service if certificates get renewed.
+ '';
+ example = literalExample ''
+ {
+ "example.com" = {
+ webroot = "/var/www/challenges/";
+ email = "foo@example.com";
+ extraDomains = { "www.example.com" = null; "foo.example.com" = "/var/www/foo/"; };
+ };
+ "bar.example.com" = {
+ webroot = "/var/www/challenges/";
+ email = "bar@example.com";
+ };
+ }
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkMerge [
+ (mkIf (cfg.certs != { }) {
+
+ systemd.services = let
+ services = concatLists servicesLists;
+ servicesLists = mapAttrsToList certToServices cfg.certs;
+ certToServices = cert: data:
+ let
+ lpath = "acme/${cert}";
+ rights = if data.allowKeysForGroup then "750" else "700";
+ cmdline = [ "-v" "-d" data.domain "--default_root" data.webroot "--valid_min" cfg.validMin ]
+ ++ optionals (data.email != null) [ "--email" data.email ]
+ ++ concatMap (p: [ "-f" p ]) data.plugins
+ ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains)
+ ++ optionals (!cfg.production) ["--server" "https://acme-staging.api.letsencrypt.org/directory"];
+ acmeService = {
+ description = "Renew ACME Certificate for ${cert}";
+ after = [ "network.target" "network-online.target" ];
+ wants = [ "network-online.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ SuccessExitStatus = [ "0" "1" ];
+ User = data.user;
+ Group = data.group;
+ PrivateTmp = true;
+ StateDirectory = lpath;
+ StateDirectoryMode = rights;
+ WorkingDirectory = "/var/lib/${lpath}";
+ ExecStart = "${pkgs.simp_le}/bin/simp_le ${escapeShellArgs cmdline}";
+ ExecStopPost =
+ let
+ script = pkgs.writeScript "acme-post-stop" ''
+ #!${pkgs.runtimeShell} -e
+ ${data.postRun}
+ '';
+ in
+ "+${script}";
+ };
+
+ };
+ selfsignedService = {
+ description = "Create preliminary self-signed certificate for ${cert}";
+ path = [ pkgs.openssl ];
+ script =
+ ''
+ workdir="$(mktemp -d)"
+
+ # Create CA
+ openssl genrsa -des3 -passout pass:xxxx -out $workdir/ca.pass.key 2048
+ openssl rsa -passin pass:xxxx -in $workdir/ca.pass.key -out $workdir/ca.key
+ openssl req -new -key $workdir/ca.key -out $workdir/ca.csr \
+ -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=Security Department/CN=example.com"
+ openssl x509 -req -days 1 -in $workdir/ca.csr -signkey $workdir/ca.key -out $workdir/ca.crt
+
+ # Create key
+ openssl genrsa -des3 -passout pass:xxxx -out $workdir/server.pass.key 2048
+ openssl rsa -passin pass:xxxx -in $workdir/server.pass.key -out $workdir/server.key
+ openssl req -new -key $workdir/server.key -out $workdir/server.csr \
+ -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
+ openssl x509 -req -days 1 -in $workdir/server.csr -CA $workdir/ca.crt \
+ -CAkey $workdir/ca.key -CAserial $workdir/ca.srl -CAcreateserial \
+ -out $workdir/server.crt
+
+ # Copy key to destination
+ cp $workdir/server.key /var/lib/${lpath}/key.pem
+
+ # Create fullchain.pem (same format as "simp_le ... -f fullchain.pem" creates)
+ cat $workdir/{server.crt,ca.crt} > "/var/lib/${lpath}/fullchain.pem"
+
+ # Create full.pem for e.g. lighttpd
+ cat $workdir/{server.key,server.crt,ca.crt} > "/var/lib/${lpath}/full.pem"
+
+ # Give key acme permissions
+ chown '${data.user}:${data.group}' "/var/lib/${lpath}/"{key,fullchain,full}.pem
+ chmod ${rights} "/var/lib/${lpath}/"{key,fullchain,full}.pem
+ '';
+ serviceConfig = {
+ Type = "oneshot";
+ PrivateTmp = true;
+ StateDirectory = lpath;
+ User = data.user;
+ Group = data.group;
+ };
+ unitConfig = {
+ # Do not create self-signed key when key already exists
+ ConditionPathExists = "!/var/lib/${lpath}/key.pem";
+ };
+ };
+ in (
+ [ { name = "acme-${cert}"; value = acmeService; } ]
+ ++ optional cfg.preliminarySelfsigned { name = "acme-selfsigned-${cert}"; value = selfsignedService; }
+ );
+ servicesAttr = listToAttrs services;
+ in
+ servicesAttr;
+
+ systemd.tmpfiles.rules =
+ flip mapAttrsToList cfg.certs
+ (cert: data: "d ${data.webroot}/.well-known/acme-challenge - ${data.user} ${data.group}");
+
+ systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair
+ ("acme-${cert}")
+ ({
+ description = "Renew ACME Certificate for ${cert}";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = cfg.renewInterval;
+ Unit = "acme-${cert}.service";
+ Persistent = "yes";
+ AccuracySec = "5m";
+ RandomizedDelaySec = "1h";
+ };
+ })
+ );
+
+ systemd.targets.acme-selfsigned-certificates = mkIf cfg.preliminarySelfsigned {};
+ systemd.targets.acme-certificates = {};
+ })
+
+ ];
+
+ meta = {
+ maintainers = with lib.maintainers; [ abbradar fpletz globin ];
+ doc = ./acme.xml;
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/acme.xml b/nixpkgs/nixos/modules/security/acme.xml
new file mode 100644
index 00000000000..9d0a1995e0f
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/acme.xml
@@ -0,0 +1,97 @@
+<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-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:
+<programlisting>
+http {
+ server {
+ server_name _;
+ listen 80;
+ listen [::]:80;
+
+ location /.well-known/acme-challenge {
+ root /var/www/challenges;
+ }
+
+ location / {
+ return 301 https://$host$request_uri;
+ }
+ }
+}
+</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>:
+<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>.
+ </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 = {
+ <link linkend="opt-services.nginx.enable">enable = true;</link>
+ <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
+ "foo.example.com" = {
+ <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
+ <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
+ locations."/" = {
+ <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www";
+ };
+ };
+ };
+}
+</programlisting>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/security/apparmor-suid.nix b/nixpkgs/nixos/modules/security/apparmor-suid.nix
new file mode 100644
index 00000000000..498c2f25d1c
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/apparmor-suid.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.security.apparmor;
+in
+with lib;
+{
+
+ options.security.apparmor.confineSUIDApplications = mkOption {
+ default = true;
+ description = ''
+ Install AppArmor profiles for commonly-used SUID application
+ to mitigate potential privilege escalation attacks due to bugs
+ in such applications.
+
+ Currently available profiles: ping
+ '';
+ };
+
+ config = mkIf (cfg.confineSUIDApplications) {
+ security.apparmor.profiles = [ (pkgs.writeText "ping" ''
+ #include <tunables/global>
+ /run/wrappers/bin/ping {
+ #include <abstractions/base>
+ #include <abstractions/consoles>
+ #include <abstractions/nameservice>
+
+ capability net_raw,
+ capability setuid,
+ network inet raw,
+
+ ${pkgs.stdenv.cc.libc.out}/lib/*.so mr,
+ ${pkgs.libcap.lib}/lib/libcap.so* mr,
+ ${pkgs.attr.out}/lib/libattr.so* mr,
+
+ ${pkgs.iputils}/bin/ping mixr,
+
+ #/etc/modules.conf r,
+
+ ## Site-specific additions and overrides. See local/README for details.
+ ##include <local/bin.ping>
+ }
+ '') ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/security/apparmor.nix b/nixpkgs/nixos/modules/security/apparmor.nix
new file mode 100644
index 00000000000..cfc65b347bc
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/apparmor.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkIf mkOption types concatMapStrings;
+ cfg = config.security.apparmor;
+in
+
+{
+ options = {
+ security.apparmor = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the AppArmor Mandatory Access Control system.";
+ };
+ profiles = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = "List of files containing AppArmor profiles.";
+ };
+ packages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = "List of packages to be added to apparmor's include path";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.apparmor-utils ];
+
+ boot.kernelParams = [ "apparmor=1" "security=apparmor" ];
+
+ systemd.services.apparmor = let
+ paths = concatMapStrings (s: " -I ${s}/etc/apparmor.d")
+ ([ pkgs.apparmor-profiles ] ++ cfg.packages);
+ in {
+ after = [ "local-fs.target" ];
+ before = [ "sysinit.target" ];
+ wantedBy = [ "multi-user.target" ];
+ unitConfig = {
+ DefaultDependencies = "no";
+ };
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = "yes";
+ ExecStart = map (p:
+ ''${pkgs.apparmor-parser}/bin/apparmor_parser -rKv ${paths} "${p}"''
+ ) cfg.profiles;
+ ExecStop = map (p:
+ ''${pkgs.apparmor-parser}/bin/apparmor_parser -Rv "${p}"''
+ ) cfg.profiles;
+ ExecReload = map (p:
+ ''${pkgs.apparmor-parser}/bin/apparmor_parser --reload ${paths} "${p}"''
+ ) cfg.profiles;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/audit.nix b/nixpkgs/nixos/modules/security/audit.nix
new file mode 100644
index 00000000000..2b22bdd9f0a
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/audit.nix
@@ -0,0 +1,123 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.security.audit;
+ enabled = cfg.enable == "lock" || cfg.enable;
+
+ failureModes = {
+ silent = 0;
+ printk = 1;
+ panic = 2;
+ };
+
+ disableScript = pkgs.writeScript "audit-disable" ''
+ #!${pkgs.runtimeShell} -eu
+ # Explicitly disable everything, as otherwise journald might start it.
+ auditctl -D
+ auditctl -e 0 -a task,never
+ '';
+
+ # TODO: it seems like people like their rules to be somewhat secret, yet they will not be if
+ # put in the store like this. At the same time, it doesn't feel like a huge deal and working
+ # around that is a pain so I'm leaving it like this for now.
+ startScript = pkgs.writeScript "audit-start" ''
+ #!${pkgs.runtimeShell} -eu
+ # Clear out any rules we may start with
+ auditctl -D
+
+ # Put the rules in a temporary file owned and only readable by root
+ rulesfile="$(mktemp)"
+ ${concatMapStrings (x: "echo '${x}' >> $rulesfile\n") cfg.rules}
+
+ # Apply the requested rules
+ auditctl -R "$rulesfile"
+
+ # Enable and configure auditing
+ auditctl \
+ -e ${if cfg.enable == "lock" then "2" else "1"} \
+ -b ${toString cfg.backlogLimit} \
+ -f ${toString failureModes.${cfg.failureMode}} \
+ -r ${toString cfg.rateLimit}
+ '';
+
+ stopScript = pkgs.writeScript "audit-stop" ''
+ #!${pkgs.runtimeShell} -eu
+ # Clear the rules
+ auditctl -D
+
+ # Disable auditing
+ auditctl -e 0
+ '';
+in {
+ options = {
+ security.audit = {
+ enable = mkOption {
+ type = types.enum [ false true "lock" ];
+ default = false;
+ description = ''
+ Whether to enable the Linux audit system. The special `lock' value can be used to
+ enable auditing and prevent disabling it until a restart. Be careful about locking
+ this, as it will prevent you from changing your audit configuration until you
+ restart. If possible, test your configuration using build-vm beforehand.
+ '';
+ };
+
+ failureMode = mkOption {
+ type = types.enum [ "silent" "printk" "panic" ];
+ default = "printk";
+ description = "How to handle critical errors in the auditing system";
+ };
+
+ backlogLimit = mkOption {
+ type = types.int;
+ default = 64; # Apparently the kernel default
+ description = ''
+ The maximum number of outstanding audit buffers allowed; exceeding this is
+ considered a failure and handled in a manner specified by failureMode.
+ '';
+ };
+
+ rateLimit = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ The maximum messages per second permitted before triggering a failure as
+ specified by failureMode. Setting it to zero disables the limit.
+ '';
+ };
+
+ rules = mkOption {
+ type = types.listOf types.str; # (types.either types.str (types.submodule rule));
+ default = [];
+ example = [ "-a exit,always -F arch=b64 -S execve" ];
+ description = ''
+ The ordered audit rules, with each string appearing as one line of the audit.rules file.
+ '';
+ };
+ };
+ };
+
+ config = {
+ systemd.services.audit = {
+ description = "Kernel Auditing";
+ wantedBy = [ "basic.target" ];
+
+ unitConfig = {
+ ConditionVirtualization = "!container";
+ ConditionSecurity = [ "audit" ];
+ };
+
+
+ path = [ pkgs.audit ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "@${if enabled then startScript else disableScript} audit-start";
+ ExecStop = "@${stopScript} audit-stop";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/auditd.nix b/nixpkgs/nixos/modules/security/auditd.nix
new file mode 100644
index 00000000000..9d26cfbcfb1
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/auditd.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options.security.auditd.enable = mkEnableOption "the Linux Audit daemon";
+
+ config = mkIf config.security.auditd.enable {
+ boot.kernelParams = [ "audit=1" ];
+
+ environment.systemPackages = [ pkgs.audit ];
+
+ systemd.services.auditd = {
+ description = "Linux Audit daemon";
+ wantedBy = [ "basic.target" ];
+
+ unitConfig = {
+ ConditionVirtualization = "!container";
+ ConditionSecurity = [ "audit" ];
+ DefaultDependencies = false;
+ };
+
+ path = [ pkgs.audit ];
+
+ serviceConfig = {
+ ExecStartPre="${pkgs.coreutils}/bin/mkdir -p /var/log/audit";
+ ExecStart = "${pkgs.audit}/bin/auditd -l -n -s nochange";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/ca.nix b/nixpkgs/nixos/modules/security/ca.nix
new file mode 100644
index 00000000000..1c4ee421fc5
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/ca.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.security.pki;
+
+ cacertPackage = pkgs.cacert.override {
+ blacklist = cfg.caCertificateBlacklist;
+ };
+
+ caCertificates = pkgs.runCommand "ca-certificates.crt"
+ { files =
+ cfg.certificateFiles ++
+ [ (builtins.toFile "extra.crt" (concatStringsSep "\n" cfg.certificates)) ];
+ preferLocalBuild = true;
+ }
+ ''
+ cat $files > $out
+ '';
+
+in
+
+{
+
+ options = {
+
+ security.pki.certificateFiles = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ example = literalExample "[ \"\${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt\" ]";
+ description = ''
+ A list of files containing trusted root certificates in PEM
+ format. These are concatenated to form
+ <filename>/etc/ssl/certs/ca-certificates.crt</filename>, which is
+ used by many programs that use OpenSSL, such as
+ <command>curl</command> and <command>git</command>.
+ '';
+ };
+
+ security.pki.certificates = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = literalExample ''
+ [ '''
+ NixOS.org
+ =========
+ -----BEGIN CERTIFICATE-----
+ MIIGUDCCBTigAwIBAgIDD8KWMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ
+ TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0
+ ...
+ -----END CERTIFICATE-----
+ '''
+ ]
+ '';
+ description = ''
+ A list of trusted root certificates in PEM format.
+ '';
+ };
+
+ security.pki.caCertificateBlacklist = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [
+ "WoSign" "WoSign China"
+ "CA WoSign ECC Root"
+ "Certification Authority of WoSign G2"
+ ];
+ description = ''
+ A list of blacklisted CA certificate names that won't be imported from
+ the Mozilla Trust Store into
+ <filename>/etc/ssl/certs/ca-certificates.crt</filename>. Use the
+ names from that file.
+ '';
+ };
+
+ };
+
+ config = {
+
+ security.pki.certificateFiles = [ "${cacertPackage}/etc/ssl/certs/ca-bundle.crt" ];
+
+ # NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility.
+ environment.etc."ssl/certs/ca-certificates.crt".source = caCertificates;
+
+ # Old NixOS compatibility.
+ environment.etc."ssl/certs/ca-bundle.crt".source = caCertificates;
+
+ # CentOS/Fedora compatibility.
+ environment.etc."pki/tls/certs/ca-bundle.crt".source = caCertificates;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/security/chromium-suid-sandbox.nix b/nixpkgs/nixos/modules/security/chromium-suid-sandbox.nix
new file mode 100644
index 00000000000..2255477f26e
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/chromium-suid-sandbox.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.security.chromiumSuidSandbox;
+ sandbox = pkgs.chromium.sandbox;
+in
+{
+ options.security.chromiumSuidSandbox.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to install the Chromium SUID sandbox which is an executable that
+ Chromium may use in order to achieve sandboxing.
+
+ If you get the error "The SUID sandbox helper binary was found, but is not
+ configured correctly.", turning this on might help.
+
+ Also, if the URL chrome://sandbox tells you that "You are not adequately
+ sandboxed!", turning this on might resolve the issue.
+ '';
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ sandbox ];
+ security.wrappers.${sandbox.passthru.sandboxExecutableName}.source = "${sandbox}/bin/${sandbox.passthru.sandboxExecutableName}";
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/dhparams.nix b/nixpkgs/nixos/modules/security/dhparams.nix
new file mode 100644
index 00000000000..62a499ea624
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/dhparams.nix
@@ -0,0 +1,175 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkOption types;
+ cfg = config.security.dhparams;
+
+ bitType = types.addCheck types.int (b: b >= 16) // {
+ name = "bits";
+ description = "integer of at least 16 bits";
+ };
+
+ paramsSubmodule = { name, config, ... }: {
+ options.bits = mkOption {
+ type = bitType;
+ default = cfg.defaultBitSize;
+ description = ''
+ The bit size for the prime that is used during a Diffie-Hellman
+ key exchange.
+ '';
+ };
+
+ options.path = mkOption {
+ type = types.path;
+ readOnly = true;
+ description = ''
+ The resulting path of the generated Diffie-Hellman parameters
+ file for other services to reference. This could be either a
+ store path or a file inside the directory specified by
+ <option>security.dhparams.path</option>.
+ '';
+ };
+
+ config.path = let
+ generated = pkgs.runCommand "dhparams-${name}.pem" {
+ nativeBuildInputs = [ pkgs.openssl ];
+ } "openssl dhparam -out \"$out\" ${toString config.bits}";
+ in if cfg.stateful then "${cfg.path}/${name}.pem" else generated;
+ };
+
+in {
+ options = {
+ security.dhparams = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to generate new DH params and clean up old DH params.
+ '';
+ };
+
+ params = mkOption {
+ type = with types; let
+ coerce = bits: { inherit bits; };
+ in attrsOf (coercedTo int coerce (submodule paramsSubmodule));
+ default = {};
+ example = lib.literalExample "{ nginx.bits = 3072; }";
+ description = ''
+ Diffie-Hellman parameters to generate.
+
+ The value is the size (in bits) of the DH params to generate. The
+ generated DH params path can be found in
+ <literal>config.security.dhparams.params.<replaceable>name</replaceable>.path</literal>.
+
+ <note><para>The name of the DH params is taken as being the name of
+ the service it serves and the params will be generated before the
+ said service is started.</para></note>
+
+ <warning><para>If you are removing all dhparams from this list, you
+ have to leave <option>security.dhparams.enable</option> for at
+ least one activation in order to have them be cleaned up. This also
+ means if you rollback to a version without any dhparams the
+ existing ones won't be cleaned up. Of course this only applies if
+ <option>security.dhparams.stateful</option> is
+ <literal>true</literal>.</para></warning>
+
+ <note><title>For module implementers:</title><para>It's recommended
+ to not set a specific bit size here, so that users can easily
+ override this by setting
+ <option>security.dhparams.defaultBitSize</option>.</para></note>
+ '';
+ };
+
+ stateful = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether generation of Diffie-Hellman parameters should be stateful or
+ not. If this is enabled, PEM-encoded files for Diffie-Hellman
+ parameters are placed in the directory specified by
+ <option>security.dhparams.path</option>. Otherwise the files are
+ created within the Nix store.
+
+ <note><para>If this is <literal>false</literal> the resulting store
+ path will be non-deterministic and will be rebuilt every time the
+ <package>openssl</package> package changes.</para></note>
+ '';
+ };
+
+ defaultBitSize = mkOption {
+ type = bitType;
+ default = 2048;
+ description = ''
+ This allows to override the default bit size for all of the
+ Diffie-Hellman parameters set in
+ <option>security.dhparams.params</option>.
+ '';
+ };
+
+ path = mkOption {
+ type = types.str;
+ default = "/var/lib/dhparams";
+ description = ''
+ Path to the directory in which Diffie-Hellman parameters will be
+ stored. This only is relevant if
+ <option>security.dhparams.stateful</option> is
+ <literal>true</literal>.
+ '';
+ };
+ };
+ };
+
+ config = lib.mkIf (cfg.enable && cfg.stateful) {
+ systemd.services = {
+ dhparams-init = {
+ description = "Clean Up Old Diffie-Hellman Parameters";
+
+ # Clean up even when no DH params is set
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig.RemainAfterExit = true;
+ serviceConfig.Type = "oneshot";
+
+ script = ''
+ if [ ! -d ${cfg.path} ]; then
+ mkdir -p ${cfg.path}
+ fi
+
+ # Remove old dhparams
+ for file in ${cfg.path}/*; do
+ if [ ! -f "$file" ]; then
+ continue
+ fi
+ ${lib.concatStrings (lib.mapAttrsToList (name: { bits, path, ... }: ''
+ if [ "$file" = ${lib.escapeShellArg path} ] && \
+ ${pkgs.openssl}/bin/openssl dhparam -in "$file" -text \
+ | head -n 1 | grep "(${toString bits} bit)" > /dev/null; then
+ continue
+ fi
+ '') cfg.params)}
+ rm $file
+ done
+
+ # TODO: Ideally this would be removing the *former* cfg.path, though
+ # this does not seem really important as changes to it are quite
+ # unlikely
+ rmdir --ignore-fail-on-non-empty ${cfg.path}
+ '';
+ };
+ } // lib.mapAttrs' (name: { bits, path, ... }: lib.nameValuePair "dhparams-gen-${name}" {
+ description = "Generate Diffie-Hellman Parameters for ${name}";
+ after = [ "dhparams-init.service" ];
+ before = [ "${name}.service" ];
+ wantedBy = [ "multi-user.target" ];
+ unitConfig.ConditionPathExists = "!${path}";
+ serviceConfig.Type = "oneshot";
+ script = ''
+ mkdir -p ${lib.escapeShellArg cfg.path}
+ ${pkgs.openssl}/bin/openssl dhparam -out ${lib.escapeShellArg path} \
+ ${toString bits}
+ '';
+ }) cfg.params;
+ };
+
+ meta.maintainers = with lib.maintainers; [ ekleog ];
+}
diff --git a/nixpkgs/nixos/modules/security/duosec.nix b/nixpkgs/nixos/modules/security/duosec.nix
new file mode 100644
index 00000000000..997328ad9e6
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/duosec.nix
@@ -0,0 +1,203 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.security.duosec;
+
+ boolToStr = b: if b then "yes" else "no";
+
+ configFilePam = ''
+ [duo]
+ ikey=${cfg.ikey}
+ skey=${cfg.skey}
+ host=${cfg.host}
+ ${optionalString (cfg.group != "") ("group="+cfg.group)}
+ failmode=${cfg.failmode}
+ pushinfo=${boolToStr cfg.pushinfo}
+ autopush=${boolToStr cfg.autopush}
+ prompts=${toString cfg.prompts}
+ fallback_local_ip=${boolToStr cfg.fallbackLocalIP}
+ '';
+
+ configFileLogin = configFilePam + ''
+ motd=${boolToStr cfg.motd}
+ accept_env_factor=${boolToStr cfg.acceptEnvFactor}
+ '';
+
+ loginCfgFile = optional cfg.ssh.enable
+ { source = pkgs.writeText "login_duo.conf" configFileLogin;
+ mode = "0600";
+ user = "sshd";
+ target = "duo/login_duo.conf";
+ };
+
+ pamCfgFile = optional cfg.pam.enable
+ { source = pkgs.writeText "pam_duo.conf" configFilePam;
+ mode = "0600";
+ user = "sshd";
+ target = "duo/pam_duo.conf";
+ };
+in
+{
+ options = {
+ security.duosec = {
+ ssh.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "If enabled, protect SSH logins with Duo Security.";
+ };
+
+ pam.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "If enabled, protect logins with Duo Security using PAM support.";
+ };
+
+ ikey = mkOption {
+ type = types.str;
+ description = "Integration key.";
+ };
+
+ skey = mkOption {
+ type = types.str;
+ description = "Secret key.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ description = "Duo API hostname.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "";
+ description = "Use Duo authentication for users only in this group.";
+ };
+
+ failmode = mkOption {
+ type = types.enum [ "safe" "secure" ];
+ default = "safe";
+ description = ''
+ On service or configuration errors that prevent Duo
+ authentication, fail "safe" (allow access) or "secure" (deny
+ access). The default is "safe".
+ '';
+ };
+
+ pushinfo = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Include information such as the command to be executed in
+ the Duo Push message.
+ '';
+ };
+
+ autopush = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If <literal>true</literal>, Duo Unix will automatically send
+ a push login request to the user’s phone, falling back on a
+ phone call if push is unavailable. If
+ <literal>false</literal>, the user will be prompted to
+ choose an authentication method. When configured with
+ <literal>autopush = yes</literal>, we recommend setting
+ <literal>prompts = 1</literal>.
+ '';
+ };
+
+ motd = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Print the contents of <literal>/etc/motd</literal> to screen
+ after a successful login.
+ '';
+ };
+
+ prompts = mkOption {
+ type = types.enum [ 1 2 3 ];
+ default = 3;
+ description = ''
+ If a user fails to authenticate with a second factor, Duo
+ Unix will prompt the user to authenticate again. This option
+ sets the maximum number of prompts that Duo Unix will
+ display before denying access. Must be 1, 2, or 3. Default
+ is 3.
+
+ For example, when <literal>prompts = 1</literal>, the user
+ will have to successfully authenticate on the first prompt,
+ whereas if <literal>prompts = 2</literal>, if the user
+ enters incorrect information at the initial prompt, he/she
+ will be prompted to authenticate again.
+
+ When configured with <literal>autopush = true</literal>, we
+ recommend setting <literal>prompts = 1</literal>.
+ '';
+ };
+
+ acceptEnvFactor = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Look for factor selection or passcode in the
+ <literal>$DUO_PASSCODE</literal> environment variable before
+ prompting the user for input.
+
+ When $DUO_PASSCODE is non-empty, it will override
+ autopush. The SSH client will need SendEnv DUO_PASSCODE in
+ its configuration, and the SSH server will similarly need
+ AcceptEnv DUO_PASSCODE.
+ '';
+ };
+
+ fallbackLocalIP = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Duo Unix reports the IP address of the authorizing user, for
+ the purposes of authorization and whitelisting. If Duo Unix
+ cannot detect the IP address of the client, setting
+ <literal>fallbackLocalIP = yes</literal> will cause Duo Unix
+ to send the IP address of the server it is running on.
+
+ If you are using IP whitelisting, enabling this option could
+ cause unauthorized logins if the local IP is listed in the
+ whitelist.
+ '';
+ };
+
+ allowTcpForwarding = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ By default, when SSH forwarding, enabling Duo Security will
+ disable TCP forwarding. By enabling this, you potentially
+ undermine some of the SSH based login security. Note this is
+ not needed if you use PAM.
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.ssh.enable || cfg.pam.enable) {
+ environment.systemPackages = [ pkgs.duo-unix ];
+
+ security.wrappers.login_duo.source = "${pkgs.duo-unix.out}/bin/login_duo";
+ environment.etc = loginCfgFile ++ pamCfgFile;
+
+ /* If PAM *and* SSH are enabled, then don't do anything special.
+ If PAM isn't used, set the default SSH-only options. */
+ services.openssh.extraConfig = mkIf (cfg.ssh.enable || cfg.pam.enable) (
+ if cfg.pam.enable then "UseDNS no" else ''
+ # Duo Security configuration
+ ForceCommand ${config.security.wrapperDir}/login_duo
+ PermitTunnel no
+ ${optionalString (!cfg.allowTcpForwarding) ''
+ AllowTcpForwarding no
+ ''}
+ '');
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/google_oslogin.nix b/nixpkgs/nixos/modules/security/google_oslogin.nix
new file mode 100644
index 00000000000..246419b681a
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/security/hidepid.nix b/nixpkgs/nixos/modules/security/hidepid.nix
new file mode 100644
index 00000000000..55a48ea3c9c
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/hidepid.nix
@@ -0,0 +1,27 @@
+{ config, lib, ... }:
+with lib;
+
+{
+ meta = {
+ maintainers = [ maintainers.joachifm ];
+ doc = ./hidepid.xml;
+ };
+
+ options = {
+ security.hideProcessInformation = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Restrict process information to the owning user.
+ '';
+ };
+ };
+
+ config = mkIf config.security.hideProcessInformation {
+ users.groups.proc.gid = config.ids.gids.proc;
+ users.groups.proc.members = [ "polkituser" ];
+
+ boot.specialFileSystems."/proc".options = [ "hidepid=2" "gid=${toString config.ids.gids.proc}" ];
+ systemd.services.systemd-logind.serviceConfig.SupplementaryGroups = [ "proc" ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/hidepid.xml b/nixpkgs/nixos/modules/security/hidepid.xml
new file mode 100644
index 00000000000..5a17cb1da41
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/hidepid.xml
@@ -0,0 +1,28 @@
+<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="sec-hidepid">
+ <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
+<programlisting>
+<link linkend="opt-systemd.services._name_.serviceConfig">systemd.services.<replaceable>foo</replaceable>.serviceConfig</link>.SupplementaryGroups = [ "proc" ];
+</programlisting>
+ </para>
+</chapter>
diff --git a/nixpkgs/nixos/modules/security/lock-kernel-modules.nix b/nixpkgs/nixos/modules/security/lock-kernel-modules.nix
new file mode 100644
index 00000000000..fc9e7939d81
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/lock-kernel-modules.nix
@@ -0,0 +1,48 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ meta = {
+ maintainers = [ maintainers.joachifm ];
+ };
+
+ options = {
+ security.lockKernelModules = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Disable kernel module loading once the system is fully initialised.
+ Module loading is disabled until the next reboot. Problems caused
+ by delayed module loading can be fixed by adding the module(s) in
+ question to <option>boot.kernelModules</option>.
+ '';
+ };
+ };
+
+ config = mkIf config.security.lockKernelModules {
+ boot.kernelModules = concatMap (x:
+ if x.device != null
+ then
+ if x.fsType == "vfat"
+ then [ "vfat" "nls-cp437" "nls-iso8859-1" ]
+ else [ x.fsType ]
+ else []) config.system.build.fileSystems;
+
+ systemd.services.disable-kernel-module-loading = rec {
+ description = "Disable kernel module loading";
+
+ wantedBy = [ config.systemd.defaultUnit ];
+
+ after = [ "systemd-udev-settle.service" "firewall.service" "systemd-modules-load.service" ] ++ wantedBy;
+
+ unitConfig.ConditionPathIsReadWrite = "/proc/sys/kernel";
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "/bin/sh -c 'echo -n 1 >/proc/sys/kernel/modules_disabled'";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/misc.nix b/nixpkgs/nixos/modules/security/misc.nix
new file mode 100644
index 00000000000..16e3bfb1419
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/misc.nix
@@ -0,0 +1,137 @@
+{ 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.
+
+ The motivation for disabling user namespaces is the potential
+ presence of code paths where the kernel's permission checking
+ logic fails to account for namespacing, instead permitting a
+ namespaced process to act outside the namespace with the same
+ privileges as it would have inside it. This is particularly
+ damaging in the common case of running as root within the namespace.
+
+ When user namespace creation is disallowed, attempting to create a
+ user namespace fails with "no space left on device" (ENOSPC).
+ root may re-enable user namespace creation at runtime.
+ '';
+ };
+
+ 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.
+
+ 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.
+
+ Disabling SMT is a supplement to the L1 data cache flushing mitigation
+ (see <xref linkend="opt-security.virtualisation.flushL1DataCache"/>)
+ versus malicious VM guests (SMT could "bring back" previously flushed
+ data).
+ '';
+ };
+
+ security.forcePageTableIsolation = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to force-enable the Page Table Isolation (PTI) Linux kernel
+ feature even on CPU models that claim to be safe from Meltdown.
+
+ This hardening feature is most beneficial to systems that run untrusted
+ workloads that rely on address space isolation for security.
+ '';
+ };
+
+ security.virtualisation.flushL1DataCache = mkOption {
+ type = types.nullOr (types.enum [ "never" "cond" "always" ]);
+ default = null;
+ description = ''
+ Whether the hypervisor should flush the L1 data cache before
+ entering guests.
+ See also <xref linkend="opt-security.allowSimultaneousMultithreading"/>.
+
+ <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.forcePageTableIsolation {
+ boot.kernelParams = [ "pti=on" ];
+ })
+
+ (mkIf (config.security.virtualisation.flushL1DataCache != null) {
+ boot.kernelParams = [ "kvm-intel.vmentry_l1d_flush=${config.security.virtualisation.flushL1DataCache}" ];
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/security/oath.nix b/nixpkgs/nixos/modules/security/oath.nix
new file mode 100644
index 00000000000..93bdc851117
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/oath.nix
@@ -0,0 +1,50 @@
+# This module provides configuration for the OATH PAM modules.
+
+{ lib, ... }:
+
+with lib;
+
+{
+ options = {
+
+ security.pam.oath = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the OATH (one-time password) PAM module.
+ '';
+ };
+
+ digits = mkOption {
+ type = types.enum [ 6 7 8 ];
+ default = 6;
+ description = ''
+ Specify the length of the one-time password in number of
+ digits.
+ '';
+ };
+
+ window = mkOption {
+ type = types.int;
+ default = 5;
+ description = ''
+ Specify the number of one-time passwords to check in order
+ to accommodate for situations where the system and the
+ client are slightly out of sync (iteration for HOTP or time
+ steps for TOTP).
+ '';
+ };
+
+ usersFile = mkOption {
+ type = types.path;
+ default = "/etc/users.oath";
+ description = ''
+ Set the path to file where the user's credentials are
+ stored. This file must not be world readable!
+ '';
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/pam.nix b/nixpkgs/nixos/modules/security/pam.nix
new file mode 100644
index 00000000000..11227354ad3
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/pam.nix
@@ -0,0 +1,795 @@
+# This module provides configuration for the PAM (Pluggable
+# Authentication Modules) system.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ parentConfig = config;
+
+ pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in {
+
+ options = {
+
+ name = mkOption {
+ example = "sshd";
+ type = types.str;
+ description = "Name of the PAM service.";
+ };
+
+ unixAuth = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether users can log in with passwords defined in
+ <filename>/etc/shadow</filename>.
+ '';
+ };
+
+ rootOK = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If set, root doesn't need to authenticate (e.g. for the
+ <command>useradd</command> service).
+ '';
+ };
+
+ u2fAuth = mkOption {
+ default = config.security.pam.u2f.enable;
+ type = types.bool;
+ description = ''
+ 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. Path can be
+ changed using <option>security.pam.u2f.authFile</option> option.
+ '';
+ };
+
+ yubicoAuth = mkOption {
+ default = config.security.pam.yubico.enable;
+ type = types.bool;
+ description = ''
+ If set, users listed in
+ <filename>~/.yubico/authorized_yubikeys</filename>
+ are able to log in with the asociated Yubikey tokens.
+ '';
+ };
+
+ googleAuthenticator = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If set, users with enabled Google Authenticator (created
+ <filename>~/.google_authenticator</filename>) will be required
+ to provide Google Authenticator token to log in.
+ '';
+ };
+ };
+
+ usbAuth = mkOption {
+ default = config.security.pam.usb.enable;
+ type = types.bool;
+ description = ''
+ If set, users listed in
+ <filename>/etc/pamusb.conf</filename> are able to log in
+ with the associated USB key.
+ '';
+ };
+
+ otpwAuth = mkOption {
+ default = config.security.pam.enableOTPW;
+ type = types.bool;
+ description = ''
+ If set, the OTPW system will be used (if
+ <filename>~/.otpw</filename> exists).
+ '';
+ };
+
+ 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;
+ description = ''
+ If set, fingerprint reader will be used (if exists and
+ your fingerprints are enrolled).
+ '';
+ };
+
+ oathAuth = mkOption {
+ default = config.security.pam.oath.enable;
+ type = types.bool;
+ description = ''
+ If set, the OATH Toolkit will be used.
+ '';
+ };
+
+ sshAgentAuth = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If set, the calling user's SSH agent is used to authenticate
+ against the keys in the calling user's
+ <filename>~/.ssh/authorized_keys</filename>. This is useful
+ for <command>sudo</command> on password-less remote systems.
+ '';
+ };
+
+ duoSecurity = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If set, use the Duo Security pam module
+ <literal>pam_duo</literal> for authentication. Requires
+ configuration of <option>security.duosec</option> options.
+ '';
+ };
+ };
+
+ startSession = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If set, the service will register a new session with
+ systemd's login manager. For local sessions, this will give
+ the user access to audio devices, CD-ROM drives. In the
+ default PolicyKit configuration, it also allows the user to
+ reboot the system.
+ '';
+ };
+
+ setEnvironment = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether the service should set the environment variables
+ listed in <option>environment.sessionVariables</option>
+ using <literal>pam_env.so</literal>.
+ '';
+ };
+
+ setLoginUid = mkOption {
+ type = types.bool;
+ description = ''
+ Set the login uid of the process
+ (<filename>/proc/self/loginuid</filename>) for auditing
+ purposes. The login uid is only set by ‘entry points’ like
+ <command>login</command> and <command>sshd</command>, not by
+ commands like <command>sudo</command>.
+ '';
+ };
+
+ forwardXAuth = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether X authentication keys should be passed from the
+ calling user to the target user (e.g. for
+ <command>su</command>)
+ '';
+ };
+
+ pamMount = mkOption {
+ default = config.security.pam.mount.enable;
+ type = types.bool;
+ description = ''
+ Enable PAM mount (pam_mount) system to mount fileystems on user login.
+ '';
+ };
+
+ allowNullPassword = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to allow logging into accounts that have no password
+ set (i.e., have an empty password field in
+ <filename>/etc/passwd</filename> or
+ <filename>/etc/group</filename>). This does not enable
+ logging into disabled accounts (i.e., that have the password
+ field set to <literal>!</literal>). Note that regardless of
+ what the pam_unix documentation says, accounts with hashed
+ empty passwords are always allowed to log in.
+ '';
+ };
+
+ requireWheel = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to permit root access only to members of group wheel.
+ '';
+ };
+
+ limits = mkOption {
+ description = ''
+ Attribute set describing resource limits. Defaults to the
+ value of <option>security.pam.loginLimits</option>.
+ '';
+ };
+
+ showMotd = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to show the message of the day.";
+ };
+
+ makeHomeDir = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to try to create home directories for users
+ with <literal>$HOME</literal>s pointing to nonexistent
+ locations on session login.
+ '';
+ };
+
+ updateWtmp = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to update <filename>/var/log/wtmp</filename>.";
+ };
+
+ logFailures = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to log authentication failures in <filename>/var/log/faillog</filename>.";
+ };
+
+ enableAppArmor = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable support for attaching AppArmor profiles at the
+ user/group level, e.g., as part of a role based access
+ control scheme.
+ '';
+ };
+
+ enableKwallet = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If enabled, pam_wallet will attempt to automatically unlock the
+ user's default KDE wallet upon login. If the user has no wallet named
+ "kdewallet", or the login password does not match their wallet
+ password, KDE will prompt separately after login.
+ '';
+ };
+ sssdStrictAccess = mkOption {
+ default = false;
+ type = types.bool;
+ description = "enforce sssd access control";
+ };
+
+ enableGnomeKeyring = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If enabled, pam_gnome_keyring will attempt to automatically unlock the
+ user's default Gnome keyring upon login. If the user login password does
+ not match their keyring password, Gnome Keyring will prompt separately
+ after login.
+ '';
+ };
+
+ text = mkOption {
+ type = types.nullOr types.lines;
+ description = "Contents of the PAM service file.";
+ };
+
+ };
+
+ config = {
+ name = mkDefault name;
+ setLoginUid = mkDefault cfg.startSession;
+ limits = mkDefault config.security.pam.loginLimits;
+
+ # !!! TODO: move the LDAP stuff to the LDAP module, and the
+ # Samba stuff to the Samba module. This requires that the PAM
+ # module provides the right hooks.
+ text = mkDefault
+ (''
+ # Account management.
+ 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)
+ "account sufficient ${pkgs.sssd}/lib/security/pam_sss.so"}
+ ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess)
+ "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
+ "auth required pam_wheel.so use_uid"}
+ ${optionalString cfg.logFailures
+ "auth required pam_tally.so"}
+ ${optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth)
+ "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"}
+ ${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
+ "auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}"}
+ ${let yubi = config.security.pam.yubico; in optionalString cfg.yubicoAuth
+ "auth ${yubi.control} ${pkgs.yubico-pam}/lib/security/pam_yubico.so mode=${toString yubi.mode} ${optionalString (yubi.mode == "client") "id=${toString yubi.id}"} ${optionalString yubi.debug "debug"}"}
+ '' +
+ # Modules in this block require having the password set in PAM_AUTHTOK.
+ # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
+ # after it succeeds. Certain modules need to run after pam_unix
+ # prompts the user for password so we run it once with 'required' at an
+ # earlier point and it will run again with 'sufficient' further down.
+ # We use try_first_pass the second time to avoid prompting password twice
+ (optionalString (cfg.unixAuth &&
+ (config.security.pam.enableEcryptfs
+ || cfg.pamMount
+ || cfg.enableKwallet
+ || cfg.enableGnomeKeyring
+ || cfg.googleAuthenticator.enable
+ || cfg.duoSecurity.enable)) ''
+ auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth
+ ${optionalString config.security.pam.enableEcryptfs
+ "auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"}
+ ${optionalString cfg.pamMount
+ "auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
+ ${optionalString cfg.enableKwallet
+ ("auth optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" +
+ " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")}
+ ${optionalString cfg.enableGnomeKeyring
+ "auth optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so"}
+ ${optionalString cfg.googleAuthenticator.enable
+ "auth required ${pkgs.googleAuthenticator}/lib/security/pam_google_authenticator.so no_increment_hotp"}
+ ${optionalString cfg.duoSecurity.enable
+ "auth required ${pkgs.duo-unix}/lib/security/pam_duo.so"}
+ '') + ''
+ ${optionalString cfg.unixAuth
+ "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth try_first_pass"}
+ ${optionalString cfg.otpwAuth
+ "auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"}
+ ${optionalString use_ldap
+ "auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass"}
+ ${optionalString config.services.sssd.enable
+ "auth sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_first_pass"}
+ ${optionalString config.krb5.enable ''
+ auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
+ auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass
+ auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass
+ ''}
+ auth required pam_deny.so
+
+ # Password management.
+ password sufficient pam_unix.so nullok sha512
+ ${optionalString config.security.pam.enableEcryptfs
+ "password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"}
+ ${optionalString cfg.pamMount
+ "password optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
+ ${optionalString use_ldap
+ "password sufficient ${pam_ldap}/lib/security/pam_ldap.so"}
+ ${optionalString config.services.sssd.enable
+ "password sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_authtok"}
+ ${optionalString config.krb5.enable
+ "password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass"}
+ ${optionalString config.services.samba.syncPasswordsByPam
+ "password optional ${pkgs.samba}/lib/security/pam_smbpass.so nullok use_authtok try_first_pass"}
+ ${optionalString cfg.enableGnomeKeyring
+ "password optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so use_authtok"}
+
+ # Session management.
+ ${optionalString cfg.setEnvironment ''
+ session required pam_env.so conffile=${config.system.build.pamEnvironment} readenv=0
+ ''}
+ session required pam_unix.so
+ ${optionalString cfg.setLoginUid
+ "session ${
+ if config.boot.isContainer then "optional" else "required"
+ } pam_loginuid.so"}
+ ${optionalString cfg.makeHomeDir
+ "session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0022"}
+ ${optionalString cfg.updateWtmp
+ "session required ${pkgs.pam}/lib/security/pam_lastlog.so silent"}
+ ${optionalString config.security.pam.enableEcryptfs
+ "session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"}
+ ${optionalString use_ldap
+ "session optional ${pam_ldap}/lib/security/pam_ldap.so"}
+ ${optionalString config.services.sssd.enable
+ "session optional ${pkgs.sssd}/lib/security/pam_sss.so"}
+ ${optionalString config.krb5.enable
+ "session optional ${pam_krb5}/lib/security/pam_krb5.so"}
+ ${optionalString cfg.otpwAuth
+ "session optional ${pkgs.otpw}/lib/security/pam_otpw.so"}
+ ${optionalString cfg.startSession
+ "session optional ${pkgs.systemd}/lib/security/pam_systemd.so"}
+ ${optionalString cfg.forwardXAuth
+ "session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99"}
+ ${optionalString (cfg.limits != [])
+ "session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}"}
+ ${optionalString (cfg.showMotd && config.users.motd != null)
+ "session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}"}
+ ${optionalString cfg.pamMount
+ "session optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
+ ${optionalString (cfg.enableAppArmor && config.security.apparmor.enable)
+ "session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug"}
+ ${optionalString (cfg.enableKwallet)
+ ("session optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" +
+ " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")}
+ ${optionalString (cfg.enableGnomeKeyring)
+ "session optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start"}
+ ${optionalString (config.virtualisation.lxc.lxcfs.enable)
+ "session optional ${pkgs.lxc}/lib/security/pam_cgfs.so -c all"}
+ '');
+ };
+
+ };
+
+
+ inherit (pkgs) pam_krb5 pam_ccreds;
+
+ use_ldap = (config.users.ldap.enable && config.users.ldap.loginPam);
+ pam_ldap = if config.users.ldap.daemon.enable then pkgs.nss_pam_ldapd else pkgs.pam_ldap;
+
+ # Create a limits.conf(5) file.
+ makeLimitsConf = limits:
+ pkgs.writeText "limits.conf"
+ (concatMapStrings ({ domain, type, item, value }:
+ "${domain} ${type} ${item} ${toString value}\n")
+ limits);
+
+ motd = pkgs.writeText "motd" config.users.motd;
+
+ makePAMService = pamService:
+ { source = pkgs.writeText "${pamService.name}.pam" pamService.text;
+ target = "pam.d/${pamService.name}";
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ security.pam.loginLimits = mkOption {
+ default = [];
+ example =
+ [ { domain = "ftp";
+ type = "hard";
+ item = "nproc";
+ value = "0";
+ }
+ { domain = "@student";
+ type = "-";
+ item = "maxlogins";
+ value = "4";
+ }
+ ];
+
+ description =
+ '' Define resource limits that should apply to users or groups.
+ Each item in the list should be an attribute set with a
+ <varname>domain</varname>, <varname>type</varname>,
+ <varname>item</varname>, and <varname>value</varname>
+ attribute. The syntax and semantics of these attributes
+ must be that described in the limits.conf(5) man page.
+
+ Note that these limits do not apply to systemd services,
+ whose limits can be changed via <option>systemd.extraConfig</option>
+ instead.
+ '';
+ };
+
+ security.pam.services = mkOption {
+ default = [];
+ type = with types; loaOf (submodule pamOpts);
+ description =
+ ''
+ This option defines the PAM services. A service typically
+ corresponds to a program that uses PAM,
+ e.g. <command>login</command> or <command>passwd</command>.
+ Each attribute of this set defines a PAM service, with the attribute name
+ defining the name of the service.
+ '';
+ };
+
+ security.pam.makeHomeDir.skelDirectory = mkOption {
+ type = types.str;
+ default = "/var/empty";
+ example = "/etc/skel";
+ description = ''
+ Path to skeleton directory whose contents are copied to home
+ directories newly created by <literal>pam_mkhomedir</literal>.
+ '';
+ };
+
+ security.pam.enableSSHAgentAuth = mkOption {
+ default = false;
+ description =
+ ''
+ Enable sudo logins if the user's SSH agent provides a key
+ present in <filename>~/.ssh/authorized_keys</filename>.
+ This allows machines to exclusively use SSH keys instead of
+ passwords.
+ '';
+ };
+
+ security.pam.enableOTPW = mkOption {
+ default = false;
+ description = ''
+ Enable the OTPW (one-time password) 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.yubico = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enables Yubico PAM (<literal>yubico-pam</literal>) module.
+
+ If set, users listed in
+ <filename>~/.yubico/authorized_yubikeys</filename>
+ are able to log in with the associated Yubikey tokens.
+
+ The file must have only one line:
+ <literal>username:yubikey_token_id1:yubikey_token_id2</literal>
+ More information can be found <link
+ xlink:href="https://developers.yubico.com/yubico-pam/">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 Yubikey instead of regular password, use "sufficient".
+
+ Read
+ <citerefentry>
+ <refentrytitle>pam.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ for better understanding of this option.
+ '';
+ };
+ id = mkOption {
+ example = "42";
+ type = types.str;
+ description = "client id";
+ };
+
+ debug = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Debug output to stderr.
+ '';
+ };
+ mode = mkOption {
+ default = "client";
+ type = types.enum [ "client" "challenge-response" ];
+ description = ''
+ Mode of operation.
+
+ Use "client" for online validation with a YubiKey validation service such as
+ the YubiCloud.
+
+ Use "challenge-response" for offline validation using YubiKeys with HMAC-SHA-1
+ Challenge-Response configurations. See the man-page ykpamcfg(1) for further
+ details on how to configure offline Challenge-Response validation.
+
+ More information can be found <link
+ xlink:href="https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html">here</link>.
+ '';
+ };
+ };
+
+ security.pam.enableEcryptfs = mkOption {
+ default = false;
+ description = ''
+ Enable eCryptfs PAM module (mounting ecryptfs home directory on login).
+ '';
+ };
+
+ users.motd = mkOption {
+ default = null;
+ example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178.";
+ type = types.nullOr types.lines;
+ description = "Message of the day shown to users when they log in.";
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ environment.systemPackages =
+ # Include the PAM modules in the system path mostly for the manpages.
+ [ pkgs.pam ]
+ ++ optional config.users.ldap.enable pam_ldap
+ ++ optional config.services.sssd.enable pkgs.sssd
+ ++ 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.u2f.enable [ pkgs.pam_u2f ];
+
+ boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
+
+ security.wrappers = {
+ unix_chkpwd = {
+ source = "${pkgs.pam}/sbin/unix_chkpwd.orig";
+ owner = "root";
+ setuid = true;
+ };
+ };
+
+ environment.etc =
+ mapAttrsToList (n: v: makePAMService v) config.security.pam.services;
+
+ security.pam.services =
+ { other.text =
+ ''
+ auth required pam_warn.so
+ auth required pam_deny.so
+ account required pam_warn.so
+ account required pam_deny.so
+ password required pam_warn.so
+ password required pam_deny.so
+ session required pam_warn.so
+ session required pam_deny.so
+ '';
+
+ # Most of these should be moved to specific modules.
+ cups = {};
+ ftp = {};
+ i3lock = {};
+ i3lock-color = {};
+ screen = {};
+ vlock = {};
+ xlock = {};
+ xscreensaver = {};
+
+ runuser = { rootOK = true; unixAuth = false; setEnvironment = false; };
+
+ /* FIXME: should runuser -l start a systemd session? Currently
+ it complains "Cannot create session: Already running in a
+ session". */
+ runuser-l = { rootOK = true; unixAuth = false; };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/security/pam_mount.nix b/nixpkgs/nixos/modules/security/pam_mount.nix
new file mode 100644
index 00000000000..8b131c54a2a
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/pam_mount.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.security.pam.mount;
+
+ anyPamMount = any (attrByPath ["pamMount"] false) (attrValues config.security.pam.services);
+in
+
+{
+ options = {
+
+ security.pam.mount = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable PAM mount system to mount fileystems on user login.
+ '';
+ };
+
+ extraVolumes = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ List of volume definitions for pam_mount.
+ For more information, visit <link
+ xlink:href="http://pam-mount.sourceforge.net/pam_mount.conf.5.html" />.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf (cfg.enable || anyPamMount) {
+
+ environment.systemPackages = [ pkgs.pam_mount ];
+ environment.etc = [{
+ target = "security/pam_mount.conf.xml";
+ source =
+ let
+ extraUserVolumes = filterAttrs (n: u: u.cryptHomeLuks != null) config.users.users;
+ userVolumeEntry = user: "<volume user=\"${user.name}\" path=\"${user.cryptHomeLuks}\" mountpoint=\"${user.home}\" />\n";
+ in
+ pkgs.writeText "pam_mount.conf.xml" ''
+ <?xml version="1.0" encoding="utf-8" ?>
+ <!DOCTYPE pam_mount SYSTEM "pam_mount.conf.xml.dtd">
+ <!-- auto generated from Nixos: modules/config/users-groups.nix -->
+ <pam_mount>
+ <debug enable="0" />
+
+ ${concatStrings (map userVolumeEntry (attrValues extraUserVolumes))}
+ ${concatStringsSep "\n" cfg.extraVolumes}
+
+ <!-- if activated, requires ofl from hxtools to be present -->
+ <logout wait="0" hup="no" term="no" kill="no" />
+ <!-- set PATH variable for pam_mount module -->
+ <path>${pkgs.utillinux}/bin</path>
+ <!-- create mount point if not present -->
+ <mkmountpoint enable="1" remove="true" />
+
+ <!-- specify the binaries to be called -->
+ <cryptmount>${pkgs.pam_mount}/bin/mount.crypt %(VOLUME) %(MNTPT)</cryptmount>
+ <cryptumount>${pkgs.pam_mount}/bin/umount.crypt %(MNTPT)</cryptumount>
+ <pmvarrun>${pkgs.pam_mount}/bin/pmvarrun -u %(USER) -o %(OPERATION)</pmvarrun>
+ </pam_mount>
+ '';
+ }];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/pam_usb.nix b/nixpkgs/nixos/modules/security/pam_usb.nix
new file mode 100644
index 00000000000..c695ba075ca
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/pam_usb.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.security.pam.usb;
+
+ anyUsbAuth = any (attrByPath ["usbAuth"] false) (attrValues config.security.pam.services);
+
+in
+
+{
+ options = {
+
+ security.pam.usb = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable USB login for all login systems that support it. For
+ more information, visit <link
+ xlink:href="https://github.com/aluzzardi/pam_usb/wiki/Getting-Started#setting-up-devices-and-users" />.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf (cfg.enable || anyUsbAuth) {
+
+ # Make sure pmount and pumount are setuid wrapped.
+ security.wrappers = {
+ pmount.source = "${pkgs.pmount.out}/bin/pmount";
+ pumount.source = "${pkgs.pmount.out}/bin/pumount";
+ };
+
+ environment.systemPackages = [ pkgs.pmount ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/polkit.nix b/nixpkgs/nixos/modules/security/polkit.nix
new file mode 100644
index 00000000000..f2b2df4004c
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/polkit.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.security.polkit;
+
+in
+
+{
+
+ options = {
+
+ security.polkit.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to enable PolKit.";
+ };
+
+ security.polkit.extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ /* Log authorization checks. */
+ polkit.addRule(function(action, subject) {
+ polkit.log("user " + subject.user + " is attempting action " + action.id + " from PID " + subject.pid);
+ });
+
+ /* Allow any local user to do anything (dangerous!). */
+ polkit.addRule(function(action, subject) {
+ if (subject.local) return "yes";
+ });
+ '';
+ description =
+ ''
+ Any polkit rules to be added to config (in JavaScript ;-). See:
+ http://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html#polkit-rules
+ '';
+ };
+
+ security.polkit.adminIdentities = mkOption {
+ type = types.listOf types.str;
+ default = [ "unix-user:0" "unix-group:wheel" ];
+ example = [ "unix-user:alice" "unix-group:admin" ];
+ description =
+ ''
+ Specifies which users are considered “administrators”, for those
+ actions that require the user to authenticate as an
+ administrator (i.e. have an <literal>auth_admin</literal>
+ value). By default, this is the <literal>root</literal>
+ user and all users in the <literal>wheel</literal> group.
+ '';
+ };
+
+ };
+
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.polkit.bin pkgs.polkit.out ];
+
+ systemd.packages = [ pkgs.polkit.out ];
+
+ systemd.services.polkit.restartTriggers = [ config.system.path ];
+ systemd.services.polkit.stopIfChanged = false;
+
+ # The polkit daemon reads action/rule files
+ environment.pathsToLink = [ "/share/polkit-1" ];
+
+ # PolKit rules for NixOS.
+ environment.etc."polkit-1/rules.d/10-nixos.rules".text =
+ ''
+ polkit.addAdminRule(function(action, subject) {
+ return [${concatStringsSep ", " (map (i: "\"${i}\"") cfg.adminIdentities)}];
+ });
+
+ ${cfg.extraConfig}
+ ''; #TODO: validation on compilation (at least against typos)
+
+ services.dbus.packages = [ pkgs.polkit.out ];
+
+ security.pam.services.polkit-1 = {};
+
+ security.wrappers = {
+ pkexec.source = "${pkgs.polkit.bin}/bin/pkexec";
+ polkit-agent-helper-1.source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1";
+ };
+
+ systemd.tmpfiles.rules = [
+ # Probably no more needed, clean up
+ "R /var/lib/polkit-1"
+ "R /var/lib/PolicyKit"
+ ];
+
+ users.users.polkituser = {
+ description = "PolKit daemon";
+ uid = config.ids.uids.polkituser;
+ };
+
+ };
+
+}
+
diff --git a/nixpkgs/nixos/modules/security/prey.nix b/nixpkgs/nixos/modules/security/prey.nix
new file mode 100644
index 00000000000..b899ccb6c3e
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/prey.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.prey;
+ myPrey = pkgs.prey-bash-client.override {
+ apiKey = cfg.apiKey;
+ deviceKey = cfg.deviceKey;
+ };
+in {
+ options = {
+
+ services.prey = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enables the <link xlink:href="http://preyproject.com/" />
+ shell client. Be sure to specify both API and device keys.
+ Once enabled, a <command>cron</command> job will run every 15
+ minutes to report status information.
+ '';
+ };
+
+ deviceKey = mkOption {
+ type = types.str;
+ description = ''
+ <literal>Device key</literal> obtained by visiting
+ <link xlink:href="https://panel.preyproject.com/devices" />
+ and clicking on your device.
+ '';
+ };
+
+ apiKey = mkOption {
+ type = types.str;
+ description = ''
+ <literal>API key</literal> obtained from
+ <link xlink:href="https://panel.preyproject.com/profile" />.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ myPrey ];
+ services.cron.systemCronJobs = [ "*/15 * * * * root ${myPrey}/prey.sh" ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/security/rngd.nix b/nixpkgs/nixos/modules/security/rngd.nix
new file mode 100644
index 00000000000..d9d6d9c9f25
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/rngd.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.security.rngd;
+in
+{
+ options = {
+ security.rngd = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable the rng daemon, which adds entropy from
+ hardware sources of randomness to the kernel entropy pool when
+ available.
+ '';
+ };
+ debug = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable debug output (-d).";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.udev.extraRules = ''
+ KERNEL=="random", TAG+="systemd"
+ SUBSYSTEM=="cpu", ENV{MODALIAS}=="cpu:type:x86,*feature:*009E*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service"
+ KERNEL=="hw_random", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service"
+ '';
+
+ systemd.services.rngd = {
+ bindsTo = [ "dev-random.device" ];
+
+ after = [ "dev-random.device" ];
+
+ description = "Hardware RNG Entropy Gatherer Daemon";
+
+ serviceConfig = {
+ ExecStart = "${pkgs.rng-tools}/sbin/rngd -f"
+ + optionalString cfg.debug " -d";
+ NoNewPrivileges = true;
+ PrivateNetwork = true;
+ PrivateTmp = true;
+ ProtectSystem = "full";
+ ProtectHome = true;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/rtkit.nix b/nixpkgs/nixos/modules/security/rtkit.nix
new file mode 100644
index 00000000000..f6dda21c600
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/rtkit.nix
@@ -0,0 +1,45 @@
+# A module for ‘rtkit’, a DBus system service that hands out realtime
+# scheduling priority to processes that ask for it.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ options = {
+
+ security.rtkit.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the RealtimeKit system service, which hands
+ out realtime scheduling priority to user processes on
+ demand. For example, the PulseAudio server uses this to
+ acquire realtime priority.
+ '';
+ };
+
+ };
+
+
+ config = mkIf config.security.rtkit.enable {
+
+ security.polkit.enable = true;
+
+ # To make polkit pickup rtkit policies
+ environment.systemPackages = [ pkgs.rtkit ];
+
+ systemd.packages = [ pkgs.rtkit ];
+
+ services.dbus.packages = [ pkgs.rtkit ];
+
+ users.users = singleton
+ { name = "rtkit";
+ uid = config.ids.uids.rtkit;
+ description = "RealtimeKit daemon";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/security/sudo.nix b/nixpkgs/nixos/modules/security/sudo.nix
new file mode 100644
index 00000000000..10ee036be84
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/sudo.nix
@@ -0,0 +1,231 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.security.sudo;
+
+ inherit (pkgs) sudo;
+
+ toUserString = user: if (isInt user) then "#${toString user}" else "${user}";
+ toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}";
+
+ toCommandOptionsString = options:
+ "${concatStringsSep ":" options}${optionalString (length options != 0) ":"} ";
+
+ toCommandsString = commands:
+ concatStringsSep ", " (
+ map (command:
+ if (isString command) then
+ command
+ else
+ "${toCommandOptionsString command.options}${command.command}"
+ ) commands
+ );
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ security.sudo.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Whether to enable the <command>sudo</command> command, which
+ allows non-root users to execute commands as root.
+ '';
+ };
+
+ security.sudo.wheelNeedsPassword = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Whether users of the <code>wheel</code> group must
+ provide a password to run commands as super user via <command>sudo</command>.
+ '';
+ };
+
+ security.sudo.configFile = mkOption {
+ type = types.lines;
+ # Note: if syntax errors are detected in this file, the NixOS
+ # configuration will fail to build.
+ description =
+ ''
+ This string contains the contents of the
+ <filename>sudoers</filename> file.
+ '';
+ };
+
+ security.sudo.extraRules = mkOption {
+ description = ''
+ Define specific rules to be in the <filename>sudoers</filename> file.
+ More specific rules should come after more general ones in order to
+ yield the expected behavior. You can use mkBefore/mkAfter to ensure
+ this is the case when configuration options are merged.
+ '';
+ default = [];
+ example = [
+ # Allow execution of any command by all users in group sudo,
+ # requiring a password.
+ { groups = [ "sudo" ]; commands = [ "ALL" ]; }
+
+ # Allow execution of "/home/root/secret.sh" by user `backup`, `database`
+ # and the group with GID `1006` without a password.
+ { users = [ "backup" "database" ]; groups = [ 1006 ];
+ commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; }
+
+ # Allow all users of group `bar` to run two executables as user `foo`
+ # with arguments being pre-set.
+ { groups = [ "bar" ]; runAs = "foo";
+ commands =
+ [ "/home/baz/cmd1.sh hello-sudo"
+ { command = ''/home/baz/cmd2.sh ""''; options = [ "SETENV" ]; } ]; }
+ ];
+ type = with types; listOf (submodule {
+ options = {
+ users = mkOption {
+ type = with types; listOf (either str int);
+ description = ''
+ The usernames / UIDs this rule should apply for.
+ '';
+ default = [];
+ };
+
+ groups = mkOption {
+ type = with types; listOf (either str int);
+ description = ''
+ The groups / GIDs this rule should apply for.
+ '';
+ default = [];
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "ALL";
+ description = ''
+ For what host this rule should apply.
+ '';
+ };
+
+ runAs = mkOption {
+ type = with types; str;
+ default = "ALL:ALL";
+ description = ''
+ Under which user/group the specified command is allowed to run.
+
+ A user can be specified using just the username: <code>"foo"</code>.
+ It is also possible to specify a user/group combination using <code>"foo:bar"</code>
+ or to only allow running as a specific group with <code>":bar"</code>.
+ '';
+ };
+
+ commands = mkOption {
+ description = ''
+ The commands for which the rule should apply.
+ '';
+ type = with types; listOf (either str (submodule {
+
+ options = {
+ command = mkOption {
+ type = with types; str;
+ description = ''
+ A command being either just a path to a binary to allow any arguments,
+ the full command with arguments pre-set or with <code>""</code> used as the argument,
+ not allowing arguments to the command at all.
+ '';
+ };
+
+ options = mkOption {
+ type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]);
+ description = ''
+ Options for running the command. Refer to the <a href="https://www.sudo.ws/man/1.7.10/sudoers.man.html">sudo manual</a>.
+ '';
+ default = [];
+ };
+ };
+
+ }));
+ };
+ };
+ });
+ };
+
+ security.sudo.extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration text appended to <filename>sudoers</filename>.
+ '';
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ security.sudo.extraRules = [
+ { groups = [ "wheel" ];
+ commands = [ { command = "ALL"; options = (if cfg.wheelNeedsPassword then [ "SETENV" ] else [ "NOPASSWD" "SETENV" ]); } ];
+ }
+ ];
+
+ security.sudo.configFile =
+ ''
+ # Don't edit this file. Set the NixOS options ‘security.sudo.configFile’
+ # or ‘security.sudo.extraRules’ instead.
+
+ # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
+ Defaults env_keep+=SSH_AUTH_SOCK
+
+ # "root" is allowed to do anything.
+ root ALL=(ALL:ALL) SETENV: ALL
+
+ # extraRules
+ ${concatStringsSep "\n" (
+ lists.flatten (
+ map (
+ rule: if (length rule.commands != 0) then [
+ (map (user: "${toUserString user} ${rule.host}=(${rule.runAs}) ${toCommandsString rule.commands}") rule.users)
+ (map (group: "${toGroupString group} ${rule.host}=(${rule.runAs}) ${toCommandsString rule.commands}") rule.groups)
+ ] else []
+ ) cfg.extraRules
+ )
+ )}
+
+ ${cfg.extraConfig}
+ '';
+
+ security.wrappers = {
+ sudo.source = "${pkgs.sudo.out}/bin/sudo";
+ sudoedit.source = "${pkgs.sudo.out}/bin/sudoedit";
+ };
+
+ environment.systemPackages = [ sudo ];
+
+ security.pam.services.sudo = { sshAgentAuth = true; };
+
+ environment.etc = singleton
+ { source =
+ pkgs.runCommand "sudoers"
+ {
+ src = pkgs.writeText "sudoers-in" cfg.configFile;
+ preferLocalBuild = true;
+ }
+ # Make sure that the sudoers file is syntactically valid.
+ # (currently disabled - NIXOS-66)
+ "${pkgs.buildPackages.sudo}/sbin/visudo -f $src -c && cp $src $out";
+ target = "sudoers";
+ mode = "0440";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/security/systemd-confinement.nix b/nixpkgs/nixos/modules/security/systemd-confinement.nix
new file mode 100644
index 00000000000..cd4eb81dbe1
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/systemd-confinement.nix
@@ -0,0 +1,199 @@
+{ config, pkgs, lib, ... }:
+
+let
+ toplevelConfig = config;
+ inherit (lib) types;
+ inherit (import ../system/boot/systemd-lib.nix {
+ inherit config pkgs lib;
+ }) mkPathSafeName;
+in {
+ options.systemd.services = lib.mkOption {
+ type = types.attrsOf (types.submodule ({ name, config, ... }: {
+ options.confinement.enable = lib.mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If set, all the required runtime store paths for this service are
+ bind-mounted into a <literal>tmpfs</literal>-based <citerefentry>
+ <refentrytitle>chroot</refentrytitle>
+ <manvolnum>2</manvolnum>
+ </citerefentry>.
+ '';
+ };
+
+ options.confinement.fullUnit = lib.mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to include the full closure of the systemd unit file into the
+ chroot, instead of just the dependencies for the executables.
+
+ <warning><para>While it may be tempting to just enable this option to
+ make things work quickly, please be aware that this might add paths
+ to the closure of the chroot that you didn't anticipate. It's better
+ to use <option>confinement.packages</option> to <emphasis
+ role="strong">explicitly</emphasis> add additional store paths to the
+ chroot.</para></warning>
+ '';
+ };
+
+ options.confinement.packages = lib.mkOption {
+ type = types.listOf (types.either types.str types.package);
+ default = [];
+ description = let
+ mkScOption = optName: "<option>serviceConfig.${optName}</option>";
+ in ''
+ Additional packages or strings with context to add to the closure of
+ the chroot. By default, this includes all the packages from the
+ ${lib.concatMapStringsSep ", " mkScOption [
+ "ExecReload" "ExecStartPost" "ExecStartPre" "ExecStop"
+ "ExecStopPost"
+ ]} and ${mkScOption "ExecStart"} options. If you want to have all the
+ dependencies of this systemd unit, you can use
+ <option>confinement.fullUnit</option>.
+
+ <note><para>The store paths listed in <option>path</option> are
+ <emphasis role="strong">not</emphasis> included in the closure as
+ well as paths from other options except those listed
+ above.</para></note>
+ '';
+ };
+
+ options.confinement.binSh = lib.mkOption {
+ type = types.nullOr types.path;
+ default = toplevelConfig.environment.binsh;
+ defaultText = "config.environment.binsh";
+ example = lib.literalExample "\${pkgs.dash}/bin/dash";
+ description = ''
+ The program to make available as <filename>/bin/sh</filename> inside
+ the chroot. If this is set to <literal>null</literal>, no
+ <filename>/bin/sh</filename> is provided at all.
+
+ This is useful for some applications, which for example use the
+ <citerefentry>
+ <refentrytitle>system</refentrytitle>
+ <manvolnum>3</manvolnum>
+ </citerefentry> library function to execute commands.
+ '';
+ };
+
+ options.confinement.mode = lib.mkOption {
+ type = types.enum [ "full-apivfs" "chroot-only" ];
+ default = "full-apivfs";
+ description = ''
+ The value <literal>full-apivfs</literal> (the default) sets up
+ private <filename class="directory">/dev</filename>, <filename
+ class="directory">/proc</filename>, <filename
+ class="directory">/sys</filename> and <filename
+ class="directory">/tmp</filename> file systems in a separate user
+ name space.
+
+ If this is set to <literal>chroot-only</literal>, only the file
+ system name space is set up along with the call to <citerefentry>
+ <refentrytitle>chroot</refentrytitle>
+ <manvolnum>2</manvolnum>
+ </citerefentry>.
+
+ <note><para>This doesn't cover network namespaces and is solely for
+ file system level isolation.</para></note>
+ '';
+ };
+
+ config = let
+ rootName = "${mkPathSafeName name}-chroot";
+ inherit (config.confinement) binSh fullUnit;
+ wantsAPIVFS = lib.mkDefault (config.confinement.mode == "full-apivfs");
+ in lib.mkIf config.confinement.enable {
+ serviceConfig = {
+ RootDirectory = pkgs.runCommand rootName {} "mkdir \"$out\"";
+ TemporaryFileSystem = "/";
+ PrivateMounts = lib.mkDefault true;
+
+ # https://github.com/NixOS/nixpkgs/issues/14645 is a future attempt
+ # to change some of these to default to true.
+ #
+ # If we run in chroot-only mode, having something like PrivateDevices
+ # set to true by default will mount /dev within the chroot, whereas
+ # with "chroot-only" it's expected that there are no /dev, /proc and
+ # /sys file systems available.
+ #
+ # However, if this suddenly becomes true, the attack surface will
+ # increase, so let's explicitly set these options to true/false
+ # depending on the mode.
+ MountAPIVFS = wantsAPIVFS;
+ PrivateDevices = wantsAPIVFS;
+ PrivateTmp = wantsAPIVFS;
+ PrivateUsers = wantsAPIVFS;
+ ProtectControlGroups = wantsAPIVFS;
+ ProtectKernelModules = wantsAPIVFS;
+ ProtectKernelTunables = wantsAPIVFS;
+ };
+ confinement.packages = let
+ execOpts = [
+ "ExecReload" "ExecStart" "ExecStartPost" "ExecStartPre" "ExecStop"
+ "ExecStopPost"
+ ];
+ execPkgs = lib.concatMap (opt: let
+ isSet = config.serviceConfig ? ${opt};
+ in lib.optional isSet config.serviceConfig.${opt}) execOpts;
+ unitAttrs = toplevelConfig.systemd.units."${name}.service";
+ allPkgs = lib.singleton (builtins.toJSON unitAttrs);
+ unitPkgs = if fullUnit then allPkgs else execPkgs;
+ in unitPkgs ++ lib.optional (binSh != null) binSh;
+ };
+ }));
+ };
+
+ config.assertions = lib.concatLists (lib.mapAttrsToList (name: cfg: let
+ whatOpt = optName: "The 'serviceConfig' option '${optName}' for"
+ + " service '${name}' is enabled in conjunction with"
+ + " 'confinement.enable'";
+ in lib.optionals cfg.confinement.enable [
+ { assertion = !cfg.serviceConfig.RootDirectoryStartOnly or false;
+ message = "${whatOpt "RootDirectoryStartOnly"}, but right now systemd"
+ + " doesn't support restricting bind-mounts to 'ExecStart'."
+ + " Please either define a separate service or find a way to run"
+ + " commands other than ExecStart within the chroot.";
+ }
+ { assertion = !cfg.serviceConfig.DynamicUser or false;
+ message = "${whatOpt "DynamicUser"}. Please create a dedicated user via"
+ + " the 'users.users' option instead as this combination is"
+ + " currently not supported.";
+ }
+ ]) config.systemd.services);
+
+ config.systemd.packages = lib.concatLists (lib.mapAttrsToList (name: cfg: let
+ rootPaths = let
+ contents = lib.concatStringsSep "\n" cfg.confinement.packages;
+ in pkgs.writeText "${mkPathSafeName name}-string-contexts.txt" contents;
+
+ chrootPaths = pkgs.runCommand "${mkPathSafeName name}-chroot-paths" {
+ closureInfo = pkgs.closureInfo { inherit rootPaths; };
+ serviceName = "${name}.service";
+ excludedPath = rootPaths;
+ } ''
+ mkdir -p "$out/lib/systemd/system"
+ serviceFile="$out/lib/systemd/system/$serviceName"
+
+ echo '[Service]' > "$serviceFile"
+
+ # /bin/sh is special here, because the option value could contain a
+ # symlink and we need to properly resolve it.
+ ${lib.optionalString (cfg.confinement.binSh != null) ''
+ binsh=${lib.escapeShellArg cfg.confinement.binSh}
+ realprog="$(readlink -e "$binsh")"
+ echo "BindReadOnlyPaths=$realprog:/bin/sh" >> "$serviceFile"
+ ''}
+
+ while read storePath; do
+ if [ -L "$storePath" ]; then
+ # Currently, systemd can't cope with symlinks in Bind(ReadOnly)Paths,
+ # so let's just bind-mount the target to that location.
+ echo "BindReadOnlyPaths=$(readlink -e "$storePath"):$storePath"
+ elif [ "$storePath" != "$excludedPath" ]; then
+ echo "BindReadOnlyPaths=$storePath"
+ fi
+ done < "$closureInfo/store-paths" >> "$serviceFile"
+ '';
+ in lib.optional cfg.confinement.enable chrootPaths) config.systemd.services);
+}
diff --git a/nixpkgs/nixos/modules/security/wrappers/default.nix b/nixpkgs/nixos/modules/security/wrappers/default.nix
new file mode 100644
index 00000000000..47738e7962e
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/wrappers/default.nix
@@ -0,0 +1,202 @@
+{ config, lib, pkgs, ... }:
+let
+
+ inherit (config.security) wrapperDir wrappers;
+
+ parentWrapperDir = dirOf wrapperDir;
+
+ programs =
+ (lib.mapAttrsToList
+ (n: v: (if v ? program then v else v // {program=n;}))
+ wrappers);
+
+ securityWrapper = pkgs.stdenv.mkDerivation {
+ name = "security-wrapper";
+ phases = [ "installPhase" "fixupPhase" ];
+ buildInputs = [ pkgs.libcap pkgs.libcap_ng pkgs.linuxHeaders ];
+ hardeningEnable = [ "pie" ];
+ installPhase = ''
+ mkdir -p $out/bin
+ $CC -Wall -O2 -DWRAPPER_DIR=\"${parentWrapperDir}\" \
+ -lcap-ng -lcap ${./wrapper.c} -o $out/bin/security-wrapper
+ '';
+ };
+
+ ###### Activation script for the setcap wrappers
+ mkSetcapProgram =
+ { program
+ , capabilities
+ , source
+ , owner ? "nobody"
+ , group ? "nogroup"
+ , permissions ? "u+rx,g+x,o+x"
+ , ...
+ }:
+ assert (lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3");
+ ''
+ cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program}
+ echo -n "${source}" > $wrapperDir/${program}.real
+
+ # Prevent races
+ chmod 0000 $wrapperDir/${program}
+ chown ${owner}.${group} $wrapperDir/${program}
+
+ # Set desired capabilities on the file plus cap_setpcap so
+ # the wrapper program can elevate the capabilities set on
+ # its file into the Ambient set.
+ ${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" $wrapperDir/${program}
+
+ # Set the executable bit
+ chmod ${permissions} $wrapperDir/${program}
+ '';
+
+ ###### Activation script for the setuid wrappers
+ mkSetuidProgram =
+ { program
+ , source
+ , owner ? "nobody"
+ , group ? "nogroup"
+ , setuid ? false
+ , setgid ? false
+ , permissions ? "u+rx,g+x,o+x"
+ , ...
+ }:
+ ''
+ cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program}
+ echo -n "${source}" > $wrapperDir/${program}.real
+
+ # Prevent races
+ chmod 0000 $wrapperDir/${program}
+ chown ${owner}.${group} $wrapperDir/${program}
+
+ chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" $wrapperDir/${program}
+ '';
+
+ mkWrappedPrograms =
+ builtins.map
+ (s: if (s ? capabilities)
+ then mkSetcapProgram
+ ({ owner = "root";
+ group = "root";
+ } // s)
+ else if
+ (s ? setuid && s.setuid) ||
+ (s ? setgid && s.setgid) ||
+ (s ? permissions)
+ then mkSetuidProgram s
+ else mkSetuidProgram
+ ({ owner = "root";
+ group = "root";
+ setuid = true;
+ setgid = false;
+ permissions = "u+rx,g+x,o+x";
+ } // s)
+ ) programs;
+in
+{
+
+ ###### interface
+
+ options = {
+ security.wrappers = lib.mkOption {
+ type = lib.types.attrs;
+ default = {};
+ example = lib.literalExample
+ ''
+ { sendmail.source = "/nix/store/.../bin/sendmail";
+ ping = {
+ source = "${pkgs.iputils.out}/bin/ping";
+ owner = "nobody";
+ group = "nogroup";
+ capabilities = "cap_net_raw+ep";
+ };
+ }
+ '';
+ description = ''
+ This option allows the ownership and permissions on the setuid
+ wrappers for specific programs to be overridden from the
+ default (setuid root, but not setgid root).
+
+ <note>
+ <para>The sub-attribute <literal>source</literal> is mandatory,
+ it must be the absolute path to the program to be wrapped.
+ </para>
+
+ <para>The sub-attribute <literal>program</literal> is optional and
+ can give the wrapper program a new name. The default name is the same
+ as the attribute name itself.</para>
+
+ <para>Additionally, this option can set capabilities on a
+ wrapper program that propagates those capabilities down to the
+ wrapped, real program.</para>
+
+ <para>NOTE: cap_setpcap, which is required for the wrapper
+ program to be able to raise caps into the Ambient set is NOT
+ raised to the Ambient set so that the real program cannot
+ modify its own capabilities!! This may be too restrictive for
+ cases in which the real program needs cap_setpcap but it at
+ least leans on the side security paranoid vs. too
+ relaxed.</para>
+ </note>
+ '';
+ };
+
+ security.wrapperDir = lib.mkOption {
+ type = lib.types.path;
+ default = "/run/wrappers/bin";
+ internal = true;
+ description = ''
+ This option defines the path to the wrapper programs. It
+ should not be overriden.
+ '';
+ };
+ };
+
+ ###### implementation
+ config = {
+
+ security.wrappers = {
+ fusermount.source = "${pkgs.fuse}/bin/fusermount";
+ fusermount3.source = "${pkgs.fuse3}/bin/fusermount3";
+ };
+
+ boot.specialFileSystems.${parentWrapperDir} = {
+ fsType = "tmpfs";
+ options = [ "nodev" ];
+ };
+
+ # Make sure our wrapperDir exports to the PATH env variable when
+ # initializing the shell
+ environment.extraInit = ''
+ # Wrappers override other bin directories.
+ export PATH="${wrapperDir}:$PATH"
+ '';
+
+ ###### setcap activation script
+ system.activationScripts.wrappers =
+ lib.stringAfter [ "specialfs" "users" ]
+ ''
+ # Look in the system path and in the default profile for
+ # programs to be wrapped.
+ WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin
+
+ # We want to place the tmpdirs for the wrappers to the parent dir.
+ wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
+ chmod a+rx $wrapperDir
+
+ ${lib.concatStringsSep "\n" mkWrappedPrograms}
+
+ if [ -L ${wrapperDir} ]; then
+ # Atomically replace the symlink
+ # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
+ old=$(readlink -f ${wrapperDir})
+ ln --symbolic --force --no-dereference $wrapperDir ${wrapperDir}-tmp
+ mv --no-target-directory ${wrapperDir}-tmp ${wrapperDir}
+ rm --force --recursive $old
+ else
+ # For initial setup
+ ln --symbolic $wrapperDir ${wrapperDir}
+ fi
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/security/wrappers/wrapper.c b/nixpkgs/nixos/modules/security/wrappers/wrapper.c
new file mode 100644
index 00000000000..494e9e93ac2
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/wrappers/wrapper.c
@@ -0,0 +1,239 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <linux/capability.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
+#include <limits.h>
+#include <cap-ng.h>
+
+// Make sure assertions are not compiled out, we use them to codify
+// invariants about this program and we want it to fail fast and
+// loudly if they are violated.
+#undef NDEBUG
+
+extern char **environ;
+
+// The WRAPPER_DIR macro is supplied at compile time so that it cannot
+// be changed at runtime
+static char * wrapperDir = WRAPPER_DIR;
+
+// Wrapper debug variable name
+static char * wrapperDebug = "WRAPPER_DEBUG";
+
+// Update the capabilities of the running process to include the given
+// capability in the Ambient set.
+static void set_ambient_cap(cap_value_t cap)
+{
+ capng_get_caps_process();
+
+ if (capng_update(CAPNG_ADD, CAPNG_INHERITABLE, (unsigned long) cap))
+ {
+ perror("cannot raise the capability into the Inheritable set\n");
+ exit(1);
+ }
+
+ capng_apply(CAPNG_SELECT_CAPS);
+
+ if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, (unsigned long) cap, 0, 0))
+ {
+ perror("cannot raise the capability into the Ambient set\n");
+ exit(1);
+ }
+}
+
+// Given the path to this program, fetch its configured capability set
+// (as set by `setcap ... /path/to/file`) and raise those capabilities
+// into the Ambient set.
+static int make_caps_ambient(const char *selfPath)
+{
+ cap_t caps = cap_get_file(selfPath);
+
+ if(!caps)
+ {
+ if(getenv(wrapperDebug))
+ fprintf(stderr, "no caps set or could not retrieve the caps for this file, not doing anything...");
+
+ return 1;
+ }
+
+ // We use `cap_to_text` and iteration over the tokenized result
+ // string because, as of libcap's current release, there is no
+ // facility for retrieving an array of `cap_value_t`'s that can be
+ // given to `prctl` in order to lift that capability into the
+ // Ambient set.
+ //
+ // Some discussion was had around shot-gunning all of the
+ // capabilities we know about into the Ambient set but that has a
+ // security smell and I deemed the risk of the current
+ // implementation crashing the program to be lower than the risk
+ // of a privilege escalation security hole being introduced by
+ // raising all capabilities, even ones we didn't intend for the
+ // program, into the Ambient set.
+ //
+ // `cap_t` which is returned by `cap_get_*` is an opaque type and
+ // even if we could retrieve the bitmasks (which, as far as I can
+ // tell we cannot) in order to get the `cap_value_t`
+ // representation for each capability we would have to take the
+ // total number of capabilities supported and iterate over the
+ // sequence of integers up-to that maximum total, testing each one
+ // against the bitmask ((bitmask >> n) & 1) to see if it's set and
+ // aggregating each "capability integer n" that is set in the
+ // bitmask.
+ //
+ // That, combined with the fact that we can't easily get the
+ // bitmask anyway seemed much more brittle than fetching the
+ // `cap_t`, transforming it into a textual representation,
+ // tokenizing the string, and using `cap_from_name` on the token
+ // to get the `cap_value_t` that we need for `prctl`. There is
+ // indeed risk involved if the output string format of
+ // `cap_to_text` ever changes but at this time the combination of
+ // factors involving the below list have led me to the conclusion
+ // that the best implementation at this time is reading then
+ // parsing with *lots of documentation* about why we're doing it
+ // this way.
+ //
+ // 1. No explicit API for fetching an array of `cap_value_t`'s or
+ // for transforming a `cap_t` into such a representation
+ // 2. The risk of a crash is lower than lifting all capabilities
+ // into the Ambient set
+ // 3. libcap is depended on heavily in the Linux ecosystem so
+ // there is a high chance that the output representation of
+ // `cap_to_text` will not change which reduces our risk that
+ // this parsing step will cause a crash
+ //
+ // The preferred method, should it ever be available in the
+ // future, would be to use libcap API's to transform the result
+ // from a `cap_get_*` into an array of `cap_value_t`'s that can
+ // then be given to prctl.
+ //
+ // - Parnell
+ ssize_t capLen;
+ char* capstr = cap_to_text(caps, &capLen);
+ cap_free(caps);
+
+ // TODO: For now, we assume that cap_to_text always starts its
+ // result string with " =" and that the first capability is listed
+ // immediately after that. We should verify this.
+ assert(capLen >= 2);
+ capstr += 2;
+
+ char* saveptr = NULL;
+ for(char* tok = strtok_r(capstr, ",", &saveptr); tok; tok = strtok_r(NULL, ",", &saveptr))
+ {
+ cap_value_t capnum;
+ if (cap_from_name(tok, &capnum))
+ {
+ if(getenv(wrapperDebug))
+ fprintf(stderr, "cap_from_name failed, skipping: %s", tok);
+ }
+ else if (capnum == CAP_SETPCAP)
+ {
+ // Check for the cap_setpcap capability, we set this on the
+ // wrapper so it can elevate the capabilities to the Ambient
+ // set but we do not want to propagate it down into the
+ // wrapped program.
+ //
+ // TODO: what happens if that's the behavior you want
+ // though???? I'm preferring a strict vs. loose policy here.
+ if(getenv(wrapperDebug))
+ fprintf(stderr, "cap_setpcap in set, skipping it\n");
+ }
+ else
+ {
+ set_ambient_cap(capnum);
+
+ if(getenv(wrapperDebug))
+ fprintf(stderr, "raised %s into the Ambient capability set\n", tok);
+ }
+ }
+ cap_free(capstr);
+
+ return 0;
+}
+
+int main(int argc, char * * argv)
+{
+ // I *think* it's safe to assume that a path from a symbolic link
+ // should safely fit within the PATH_MAX system limit. Though I'm
+ // not positive it's safe...
+ char selfPath[PATH_MAX];
+ int selfPathSize = readlink("/proc/self/exe", selfPath, sizeof(selfPath));
+
+ assert(selfPathSize > 0);
+
+ // Assert we have room for the zero byte, this ensures the path
+ // isn't being truncated because it's too big for the buffer.
+ //
+ // A better way to handle this might be to use something like the
+ // whereami library (https://github.com/gpakosz/whereami) or a
+ // loop that resizes the buffer and re-reads the link if the
+ // contents are being truncated.
+ assert(selfPathSize < sizeof(selfPath));
+
+ // Set the zero byte since readlink doesn't do that for us.
+ selfPath[selfPathSize] = '\0';
+
+ // Make sure that we are being executed from the right location,
+ // i.e., `safeWrapperDir'. This is to prevent someone from creating
+ // hard link `X' from some other location, along with a false
+ // `X.real' file, to allow arbitrary programs from being executed
+ // with elevated capabilities.
+ int len = strlen(wrapperDir);
+ if (len > 0 && '/' == wrapperDir[len - 1])
+ --len;
+ assert(!strncmp(selfPath, wrapperDir, len));
+ assert('/' == wrapperDir[0]);
+ assert('/' == selfPath[len]);
+
+ // Make *really* *really* sure that we were executed as
+ // `selfPath', and not, say, as some other setuid program. That
+ // is, our effective uid/gid should match the uid/gid of
+ // `selfPath'.
+ struct stat st;
+ assert(lstat(selfPath, &st) != -1);
+
+ assert(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid()));
+ assert(!(st.st_mode & S_ISGID) || (st.st_gid == getegid()));
+
+ // And, of course, we shouldn't be writable.
+ assert(!(st.st_mode & (S_IWGRP | S_IWOTH)));
+
+ // Read the path of the real (wrapped) program from <self>.real.
+ char realFN[PATH_MAX + 10];
+ int realFNSize = snprintf (realFN, sizeof(realFN), "%s.real", selfPath);
+ assert (realFNSize < sizeof(realFN));
+
+ int fdSelf = open(realFN, O_RDONLY);
+ assert (fdSelf != -1);
+
+ char sourceProg[PATH_MAX];
+ len = read(fdSelf, sourceProg, PATH_MAX);
+ assert (len != -1);
+ assert (len < sizeof(sourceProg));
+ assert (len > 0);
+ sourceProg[len] = 0;
+
+ close(fdSelf);
+
+ // Read the capabilities set on the wrapper and raise them in to
+ // the Ambient set so the program we're wrapping receives the
+ // capabilities too!
+ make_caps_ambient(selfPath);
+
+ execve(sourceProg, argv, environ);
+
+ fprintf(stderr, "%s: cannot run `%s': %s\n",
+ argv[0], sourceProg, strerror(errno));
+
+ exit(1);
+}
+
+
diff --git a/nixpkgs/nixos/modules/services/admin/oxidized.nix b/nixpkgs/nixos/modules/services/admin/oxidized.nix
new file mode 100644
index 00000000000..39112c3970d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/admin/oxidized.nix
@@ -0,0 +1,116 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.oxidized;
+in
+{
+ options.services.oxidized = {
+ enable = mkEnableOption "the oxidized configuration backup service";
+
+ user = mkOption {
+ type = types.str;
+ default = "oxidized";
+ description = ''
+ User under which the oxidized service runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "oxidized";
+ description = ''
+ Group under which the oxidized service runs.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/oxidized";
+ description = "State directory for the oxidized service.";
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ example = literalExample ''
+ pkgs.writeText "oxidized-config.yml" '''
+ ---
+ debug: true
+ use_syslog: true
+ input:
+ default: ssh
+ ssh:
+ secure: true
+ interval: 3600
+ model_map:
+ dell: powerconnect
+ hp: procurve
+ source:
+ default: csv
+ csv:
+ delimiter: !ruby/regexp /:/
+ file: "/var/lib/oxidized/.config/oxidized/router.db"
+ map:
+ name: 0
+ model: 1
+ username: 2
+ password: 3
+ pid: "/var/lib/oxidized/.config/oxidized/pid"
+ rest: 127.0.0.1:8888
+ retries: 3
+ # ... additional config
+ ''';
+ '';
+ description = ''
+ Path to the oxidized configuration file.
+ '';
+ };
+
+ routerDB = mkOption {
+ type = types.path;
+ example = literalExample ''
+ pkgs.writeText "oxidized-router.db" '''
+ hostname-sw1:powerconnect:username1:password2
+ hostname-sw2:procurve:username2:password2
+ # ... additional hosts
+ '''
+ '';
+ description = ''
+ Path to the file/database which contains the targets for oxidized.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.groups.${cfg.group} = { };
+ users.users.${cfg.user} = {
+ description = "Oxidized service user";
+ group = cfg.group;
+ home = cfg.dataDir;
+ createHome = true;
+ };
+
+ systemd.services.oxidized = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ preStart = ''
+ mkdir -p ${cfg.dataDir}/.config/oxidized
+ ln -f -s ${cfg.routerDB} ${cfg.dataDir}/.config/oxidized/router.db
+ ln -f -s ${cfg.configFile} ${cfg.dataDir}/.config/oxidized/config
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkgs.oxidized}/bin/oxidized";
+ User = cfg.user;
+ Group = cfg.group;
+ UMask = "0077";
+ NoNewPrivileges = true;
+ Restart = "always";
+ WorkingDirectory = cfg.dataDir;
+ KillSignal = "SIGKILL";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/admin/salt/master.nix b/nixpkgs/nixos/modules/services/admin/salt/master.nix
new file mode 100644
index 00000000000..c6b1b0cc0bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/admin/salt/master.nix
@@ -0,0 +1,63 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.salt.master;
+
+ fullConfig = lib.recursiveUpdate {
+ # Provide defaults for some directories to allow an immutable config dir
+
+ # Default is equivalent to /etc/salt/master.d/*.conf
+ default_include = "/var/lib/salt/master.d/*.conf";
+ # Default is in /etc/salt/pki/master
+ pki_dir = "/var/lib/salt/pki/master";
+ } cfg.configuration;
+
+in
+
+{
+ options = {
+ services.salt.master = {
+ enable = mkEnableOption "Salt master service";
+ configuration = mkOption {
+ type = types.attrs;
+ default = {};
+ description = "Salt master configuration as Nix attribute set.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment = {
+ # Set this up in /etc/salt/master so `salt`, `salt-key`, etc. work.
+ # The alternatives are
+ # - passing --config-dir to all salt commands, not just the master unit,
+ # - setting a global environment variable,
+ etc."salt/master".source = pkgs.writeText "master" (
+ builtins.toJSON fullConfig
+ );
+ systemPackages = with pkgs; [ salt ];
+ };
+ systemd.services.salt-master = {
+ description = "Salt Master";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = with pkgs; [
+ utillinux # for dmesg
+ ];
+ serviceConfig = {
+ ExecStart = "${pkgs.salt}/bin/salt-master";
+ LimitNOFILE = 16384;
+ Type = "notify";
+ NotifyAccess = "all";
+ };
+ restartTriggers = [
+ config.environment.etc."salt/master".source
+ ];
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ aneeshusa ];
+}
diff --git a/nixpkgs/nixos/modules/services/admin/salt/minion.nix b/nixpkgs/nixos/modules/services/admin/salt/minion.nix
new file mode 100644
index 00000000000..c8fa9461a20
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/admin/salt/minion.nix
@@ -0,0 +1,67 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.salt.minion;
+
+ fullConfig = lib.recursiveUpdate {
+ # Provide defaults for some directories to allow an immutable config dir
+ # NOTE: the config dir being immutable prevents `minion_id` caching
+
+ # Default is equivalent to /etc/salt/minion.d/*.conf
+ default_include = "/var/lib/salt/minion.d/*.conf";
+ # Default is in /etc/salt/pki/minion
+ pki_dir = "/var/lib/salt/pki/minion";
+ } cfg.configuration;
+
+in
+
+{
+ options = {
+ services.salt.minion = {
+ enable = mkEnableOption "Salt minion service";
+ configuration = mkOption {
+ type = types.attrs;
+ default = {};
+ description = ''
+ Salt minion configuration as Nix attribute set.
+ See <link xlink:href="https://docs.saltstack.com/en/latest/ref/configuration/minion.html"/>
+ for details.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment = {
+ # Set this up in /etc/salt/minion so `salt-call`, etc. work.
+ # The alternatives are
+ # - passing --config-dir to all salt commands, not just the minion unit,
+ # - setting aglobal environment variable.
+ etc."salt/minion".source = pkgs.writeText "minion" (
+ builtins.toJSON fullConfig
+ );
+ systemPackages = with pkgs; [ salt ];
+ };
+ systemd.services.salt-minion = {
+ description = "Salt Minion";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = with pkgs; [
+ utillinux
+ ];
+ serviceConfig = {
+ ExecStart = "${pkgs.salt}/bin/salt-minion";
+ LimitNOFILE = 8192;
+ Type = "notify";
+ NotifyAccess = "all";
+ };
+ restartTriggers = [
+ config.environment.etc."salt/minion".source
+ ];
+ };
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/amqp/activemq/ActiveMQBroker.java b/nixpkgs/nixos/modules/services/amqp/activemq/ActiveMQBroker.java
new file mode 100644
index 00000000000..c0f5d16ea11
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/amqp/activemq/ActiveMQBroker.java
@@ -0,0 +1,19 @@
+import org.apache.activemq.broker.BrokerService;
+import org.apache.activemq.broker.BrokerFactory;
+import java.net.URI;
+
+public class ActiveMQBroker {
+
+ public static void main(String[] args) throws Throwable {
+ URI uri = new URI((args.length > 0) ? args[0] : "xbean:activemq.xml");
+ BrokerService broker = BrokerFactory.createBroker(uri);
+ broker.start();
+ if (broker.waitUntilStarted()) {
+ broker.waitUntilStopped();
+ } else {
+ System.out.println("Failed starting broker");
+ System.exit(-1);
+ };
+ }
+
+}
diff --git a/nixpkgs/nixos/modules/services/amqp/activemq/default.nix b/nixpkgs/nixos/modules/services/amqp/activemq/default.nix
new file mode 100644
index 00000000000..7729da27304
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/amqp/activemq/default.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+ cfg = config.services.activemq;
+
+ activemqBroker = stdenv.mkDerivation {
+ name = "activemq-broker";
+ phases = [ "installPhase" ];
+ buildInputs = [ jdk ];
+ installPhase = ''
+ mkdir -p $out/lib
+ source ${activemq}/lib/classpath.env
+ export CLASSPATH
+ ln -s "${./ActiveMQBroker.java}" ActiveMQBroker.java
+ javac -d $out/lib ActiveMQBroker.java
+ '';
+ };
+
+in {
+
+ options = {
+ services.activemq = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the Apache ActiveMQ message broker service.
+ '';
+ };
+ configurationDir = mkOption {
+ default = "${activemq}/conf";
+ description = ''
+ The base directory for ActiveMQ's configuration.
+ By default, this directory is searched for a file named activemq.xml,
+ which should contain the configuration for the broker service.
+ '';
+ };
+ configurationURI = mkOption {
+ type = types.str;
+ default = "xbean:activemq.xml";
+ description = ''
+ The URI that is passed along to the BrokerFactory to
+ set up the configuration of the ActiveMQ broker service.
+ You should not need to change this. For custom configuration,
+ set the <literal>configurationDir</literal> instead, and create
+ an activemq.xml configuration file in it.
+ '';
+ };
+ baseDir = mkOption {
+ type = types.str;
+ default = "/var/activemq";
+ description = ''
+ The base directory where ActiveMQ stores its persistent data and logs.
+ This will be overridden if you set "activemq.base" and "activemq.data"
+ in the <literal>javaProperties</literal> option. You can also override
+ this in activemq.xml.
+ '';
+ };
+ javaProperties = mkOption {
+ type = types.attrs;
+ default = { };
+ example = {
+ "java.net.preferIPv4Stack" = "true";
+ };
+ apply = attrs: {
+ "activemq.base" = "${cfg.baseDir}";
+ "activemq.data" = "${cfg.baseDir}/data";
+ "activemq.conf" = "${cfg.configurationDir}";
+ "activemq.home" = "${activemq}";
+ } // attrs;
+ description = ''
+ Specifies Java properties that are sent to the ActiveMQ
+ broker service with the "-D" option. You can set properties
+ here to change the behaviour and configuration of the broker.
+ All essential properties that are not set here are automatically
+ given reasonable defaults.
+ '';
+ };
+ extraJavaOptions = mkOption {
+ type = types.separatedString " ";
+ default = "";
+ example = "-Xmx2G -Xms2G -XX:MaxPermSize=512M";
+ description = ''
+ Add extra options here that you want to be sent to the
+ Java runtime when the broker service is started.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.activemq = {
+ description = "ActiveMQ server user";
+ group = "activemq";
+ uid = config.ids.uids.activemq;
+ };
+
+ users.groups.activemq.gid = config.ids.gids.activemq;
+
+ systemd.services.activemq_init = {
+ wantedBy = [ "activemq.service" ];
+ partOf = [ "activemq.service" ];
+ before = [ "activemq.service" ];
+ serviceConfig.Type = "oneshot";
+ script = ''
+ mkdir -p "${cfg.javaProperties."activemq.data"}"
+ chown -R activemq "${cfg.javaProperties."activemq.data"}"
+ '';
+ };
+
+ systemd.services.activemq = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = [ jre ];
+ serviceConfig.User = "activemq";
+ script = ''
+ source ${activemq}/lib/classpath.env
+ export CLASSPATH=${activemqBroker}/lib:${cfg.configurationDir}:$CLASSPATH
+ exec java \
+ ${concatStringsSep " \\\n" (mapAttrsToList (name: value: "-D${name}=${value}") cfg.javaProperties)} \
+ ${cfg.extraJavaOptions} ActiveMQBroker "${cfg.configurationURI}"
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/amqp/rabbitmq.nix b/nixpkgs/nixos/modules/services/amqp/rabbitmq.nix
new file mode 100644
index 00000000000..302b94de196
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/amqp/rabbitmq.nix
@@ -0,0 +1,207 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.rabbitmq;
+
+ inherit (builtins) concatStringsSep;
+
+ config_file_content = lib.generators.toKeyValue {} cfg.configItems;
+ config_file = pkgs.writeText "rabbitmq.conf" config_file_content;
+
+ advanced_config_file = pkgs.writeText "advanced.config" cfg.config;
+
+in {
+ ###### interface
+ options = {
+ services.rabbitmq = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable the RabbitMQ server, an Advanced Message
+ Queuing Protocol (AMQP) broker.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.rabbitmq-server;
+ type = types.package;
+ defaultText = "pkgs.rabbitmq-server";
+ description = ''
+ Which rabbitmq package to use.
+ '';
+ };
+
+ listenAddress = mkOption {
+ default = "127.0.0.1";
+ example = "";
+ description = ''
+ IP address on which RabbitMQ will listen for AMQP
+ connections. Set to the empty string to listen on all
+ interfaces. Note that RabbitMQ creates a user named
+ <literal>guest</literal> with password
+ <literal>guest</literal> by default, so you should delete
+ this user if you intend to allow external access.
+
+ Together with 'port' setting it's mostly an alias for
+ configItems."listeners.tcp.1" and it's left for backwards
+ compatibility with previous version of this module.
+ '';
+ type = types.str;
+ };
+
+ port = mkOption {
+ default = 5672;
+ description = ''
+ Port on which RabbitMQ will listen for AMQP connections.
+ '';
+ type = types.int;
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/rabbitmq";
+ description = ''
+ Data directory for rabbitmq.
+ '';
+ };
+
+ cookie = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Erlang cookie is a string of arbitrary length which must
+ be the same for several nodes to be allowed to communicate.
+ Leave empty to generate automatically.
+ '';
+ };
+
+ configItems = mkOption {
+ default = {};
+ type = types.attrsOf types.str;
+ example = ''
+ {
+ "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 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
+ '';
+ };
+
+ plugins = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = "The names of plugins to enable";
+ };
+
+ pluginDirs = mkOption {
+ default = [];
+ type = types.listOf types.path;
+ description = "The list of directories containing external plugins";
+ };
+ };
+ };
+
+
+ ###### implementation
+ config = mkIf cfg.enable {
+
+ # This is needed so we will have 'rabbitmqctl' in our PATH
+ environment.systemPackages = [ cfg.package ];
+
+ services.epmd.enable = true;
+
+ users.users.rabbitmq = {
+ description = "RabbitMQ server user";
+ home = "${cfg.dataDir}";
+ createHome = true;
+ group = "rabbitmq";
+ uid = config.ids.uids.rabbitmq;
+ };
+
+ users.groups.rabbitmq.gid = config.ids.gids.rabbitmq;
+
+ services.rabbitmq.configItems = {
+ "listeners.tcp.1" = mkDefault "${cfg.listenAddress}:${toString cfg.port}";
+ };
+
+ systemd.services.rabbitmq = {
+ description = "RabbitMQ Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "epmd.socket" ];
+ wants = [ "network.target" "epmd.socket" ];
+
+ path = [ cfg.package pkgs.procps ];
+
+ environment = {
+ RABBITMQ_MNESIA_BASE = "${cfg.dataDir}/mnesia";
+ RABBITMQ_LOGS = "-";
+ SYS_PREFIX = "";
+ RABBITMQ_CONFIG_FILE = config_file;
+ RABBITMQ_PLUGINS_DIR = concatStringsSep ":" cfg.pluginDirs;
+ RABBITMQ_ENABLED_PLUGINS_FILE = pkgs.writeText "enabled_plugins" ''
+ [ ${concatStringsSep "," cfg.plugins} ].
+ '';
+ } // optionalAttrs (cfg.config != "") { RABBITMQ_ADVANCED_CONFIG_FILE = advanced_config_file; };
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/sbin/rabbitmq-server";
+ ExecStop = "${cfg.package}/sbin/rabbitmqctl shutdown";
+ User = "rabbitmq";
+ Group = "rabbitmq";
+ LogsDirectory = "rabbitmq";
+ WorkingDirectory = cfg.dataDir;
+ Type = "notify";
+ NotifyAccess = "all";
+ UMask = "0027";
+ LimitNOFILE = "100000";
+ Restart = "on-failure";
+ RestartSec = "10";
+ TimeoutStartSec = "3600";
+ };
+
+ preStart = ''
+ ${optionalString (cfg.cookie != "") ''
+ echo -n ${cfg.cookie} > ${cfg.dataDir}/.erlang.cookie
+ chmod 600 ${cfg.dataDir}/.erlang.cookie
+ ''}
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/alsa.nix b/nixpkgs/nixos/modules/services/audio/alsa.nix
new file mode 100644
index 00000000000..f632644af09
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/alsa.nix
@@ -0,0 +1,134 @@
+# ALSA sound support.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs) alsaUtils;
+
+ pulseaudioEnabled = config.hardware.pulseaudio.enable;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ sound = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable ALSA sound.
+ '';
+ };
+
+ enableOSSEmulation = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable ALSA OSS emulation (with certain cards sound mixing may not work!).
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ defaults.pcm.!card 3
+ '';
+ description = ''
+ Set addition configuration for system-wide alsa.
+ '';
+ };
+
+ mediaKeys = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable volume and capture control with keyboard media keys.
+
+ You want to leave this disabled if you run a desktop environment
+ like KDE, Gnome, Xfce, etc, as those handle such things themselves.
+ You might want to enable this if you run a minimalistic desktop
+ environment or work from bare linux ttys/framebuffers.
+
+ Enabling this will turn on <option>services.actkbd</option>.
+ '';
+ };
+
+ volumeStep = mkOption {
+ type = types.str;
+ default = "1";
+ example = "1%";
+ description = ''
+ The value by which to increment/decrement volume on media keys.
+
+ See amixer(1) for allowed values.
+ '';
+ };
+
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.sound.enable {
+
+ environment.systemPackages = [ alsaUtils ];
+
+ environment.etc = mkIf (!pulseaudioEnabled && config.sound.extraConfig != "")
+ [
+ { source = pkgs.writeText "asound.conf" config.sound.extraConfig;
+ target = "asound.conf";
+ }
+ ];
+
+ # ALSA provides a udev rule for restoring volume settings.
+ services.udev.packages = [ alsaUtils ];
+
+ boot.kernelModules = optional config.sound.enableOSSEmulation "snd_pcm_oss";
+
+ systemd.services.alsa-store =
+ { description = "Store Sound Card State";
+ wantedBy = [ "multi-user.target" ];
+ unitConfig.RequiresMountsFor = "/var/lib/alsa";
+ unitConfig.ConditionVirtualization = "!systemd-nspawn";
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${pkgs.coreutils}/bin/mkdir -p /var/lib/alsa";
+ ExecStop = "${alsaUtils}/sbin/alsactl store --ignore";
+ };
+ };
+
+ services.actkbd = mkIf config.sound.mediaKeys.enable {
+ enable = true;
+ bindings = [
+ # "Mute" media key
+ { keys = [ 113 ]; events = [ "key" ]; command = "${alsaUtils}/bin/amixer -q set Master toggle"; }
+
+ # "Lower Volume" media key
+ { keys = [ 114 ]; events = [ "key" "rep" ]; command = "${alsaUtils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}- unmute"; }
+
+ # "Raise Volume" media key
+ { keys = [ 115 ]; events = [ "key" "rep" ]; command = "${alsaUtils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}+ unmute"; }
+
+ # "Mic Mute" media key
+ { keys = [ 190 ]; events = [ "key" ]; command = "${alsaUtils}/bin/amixer -q set Capture toggle"; }
+ ];
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/icecast.nix b/nixpkgs/nixos/modules/services/audio/icecast.nix
new file mode 100644
index 00000000000..6a8a0f9975b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/icecast.nix
@@ -0,0 +1,130 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.icecast;
+ configFile = pkgs.writeText "icecast.xml" ''
+ <icecast>
+ <hostname>${cfg.hostname}</hostname>
+
+ <authentication>
+ <admin-user>${cfg.admin.user}</admin-user>
+ <admin-password>${cfg.admin.password}</admin-password>
+ </authentication>
+
+ <paths>
+ <logdir>${cfg.logDir}</logdir>
+ <adminroot>${pkgs.icecast}/share/icecast/admin</adminroot>
+ <webroot>${pkgs.icecast}/share/icecast/web</webroot>
+ <alias source="/" dest="/status.xsl"/>
+ </paths>
+
+ <listen-socket>
+ <port>${toString cfg.listen.port}</port>
+ <bind-address>${cfg.listen.address}</bind-address>
+ </listen-socket>
+
+ <security>
+ <chroot>0</chroot>
+ <changeowner>
+ <user>${cfg.user}</user>
+ <group>${cfg.group}</group>
+ </changeowner>
+ </security>
+
+ ${cfg.extraConf}
+ </icecast>
+ '';
+in {
+
+ ###### interface
+
+ options = {
+
+ services.icecast = {
+
+ enable = mkEnableOption "Icecast server";
+
+ hostname = mkOption {
+ type = types.str;
+ description = "DNS name or IP address that will be used for the stream directory lookups or possibily the playlist generation if a Host header is not provided.";
+ default = config.networking.domain;
+ };
+
+ admin = {
+ user = mkOption {
+ type = types.str;
+ description = "Username used for all administration functions.";
+ default = "admin";
+ };
+
+ password = mkOption {
+ type = types.str;
+ description = "Password used for all administration functions.";
+ };
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ description = "Base directory used for logging.";
+ default = "/var/log/icecast";
+ };
+
+ listen = {
+ port = mkOption {
+ type = types.int;
+ description = "TCP port that will be used to accept client connections.";
+ default = 8000;
+ };
+
+ address = mkOption {
+ type = types.str;
+ description = "Address Icecast will listen on.";
+ default = "::";
+ };
+ };
+
+ user = mkOption {
+ type = types.str;
+ description = "User privileges for the server.";
+ default = "nobody";
+ };
+
+ group = mkOption {
+ type = types.str;
+ description = "Group privileges for the server.";
+ default = "nogroup";
+ };
+
+ extraConf = mkOption {
+ type = types.lines;
+ description = "icecast.xml content.";
+ default = "";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.icecast = {
+ after = [ "network.target" ];
+ description = "Icecast Network Audio Streaming Server";
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = "mkdir -p ${cfg.logDir} && chown ${cfg.user}:${cfg.group} ${cfg.logDir}";
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${pkgs.icecast}/bin/icecast -c ${configFile}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/jack.nix b/nixpkgs/nixos/modules/services/audio/jack.nix
new file mode 100644
index 00000000000..aa3351f401a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/jack.nix
@@ -0,0 +1,290 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.jack;
+
+ pcmPlugin = cfg.jackd.enable && cfg.alsa.enable;
+ loopback = cfg.jackd.enable && cfg.loopback.enable;
+
+ enable32BitAlsaPlugins = cfg.alsa.support32Bit && pkgs.stdenv.isx86_64 && pkgs.pkgsi686Linux.alsaLib != null;
+
+ umaskNeeded = versionOlder cfg.jackd.package.version "1.9.12";
+ bridgeNeeded = versionAtLeast cfg.jackd.package.version "1.9.12";
+in {
+ options = {
+ services.jack = {
+ jackd = {
+ enable = mkEnableOption ''
+ JACK Audio Connection Kit. You need to add yourself to the "jackaudio" group
+ '';
+
+ package = mkOption {
+ # until jack1 promiscuous mode is fixed
+ internal = true;
+ type = types.package;
+ default = pkgs.jack2;
+ defaultText = "pkgs.jack2";
+ example = literalExample "pkgs.jack1";
+ description = ''
+ The JACK package to use.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "-dalsa"
+ ];
+ example = literalExample ''
+ [ "-dalsa" "--device" "hw:1" ];
+ '';
+ description = ''
+ Specifies startup command line arguments to pass to JACK server.
+ '';
+ };
+
+ session = mkOption {
+ type = types.lines;
+ description = ''
+ Commands to run after JACK is started.
+ '';
+ };
+
+ };
+
+ alsa = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Route audio to/from generic ALSA-using applications using ALSA JACK PCM plugin.
+ '';
+ };
+
+ support32Bit = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to support sound for 32-bit ALSA applications on 64-bit system.
+ '';
+ };
+ };
+
+ loopback = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Create ALSA loopback device, instead of using PCM plugin. Has broader
+ application support (things like Steam will work), but may need fine-tuning
+ for concrete hardware.
+ '';
+ };
+
+ index = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ Index of an ALSA loopback device.
+ '';
+ };
+
+ config = mkOption {
+ type = types.lines;
+ description = ''
+ ALSA config for loopback device.
+ '';
+ };
+
+ dmixConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ period_size 2048
+ periods 2
+ '';
+ description = ''
+ For music production software that still doesn't support JACK natively you
+ would like to put buffer/period adjustments here
+ to decrease dmix device latency.
+ '';
+ };
+
+ session = mkOption {
+ type = types.lines;
+ description = ''
+ Additional commands to run to setup loopback device.
+ '';
+ };
+ };
+
+ };
+
+ };
+
+ config = mkMerge [
+
+ (mkIf pcmPlugin {
+ sound.extraConfig = ''
+ pcm_type.jack {
+ libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;
+ ${lib.optionalString enable32BitAlsaPlugins
+ "libs.32Bit = ${pkgs.pkgsi686Linux.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;"}
+ }
+ pcm.!default {
+ @func getenv
+ vars [ PCM ]
+ default "plug:jack"
+ }
+ '';
+ })
+
+ (mkIf loopback {
+ boot.kernelModules = [ "snd-aloop" ];
+ boot.kernelParams = [ "snd-aloop.index=${toString cfg.loopback.index}" ];
+ sound.extraConfig = cfg.loopback.config;
+ })
+
+ (mkIf cfg.jackd.enable {
+ services.jack.jackd.session = ''
+ ${lib.optionalString bridgeNeeded "${pkgs.a2jmidid}/bin/a2jmidid -e &"}
+ '';
+ # https://alsa.opensrc.org/Jack_and_Loopback_device_as_Alsa-to-Jack_bridge#id06
+ services.jack.loopback.config = ''
+ pcm.loophw00 {
+ type hw
+ card ${toString cfg.loopback.index}
+ device 0
+ subdevice 0
+ }
+ pcm.amix {
+ type dmix
+ ipc_key 219345
+ slave {
+ pcm loophw00
+ ${cfg.loopback.dmixConfig}
+ }
+ }
+ pcm.asoftvol {
+ type softvol
+ slave.pcm "amix"
+ control { name Master }
+ }
+ pcm.cloop {
+ type hw
+ card ${toString cfg.loopback.index}
+ device 1
+ subdevice 0
+ format S32_LE
+ }
+ pcm.loophw01 {
+ type hw
+ card ${toString cfg.loopback.index}
+ device 0
+ subdevice 1
+ }
+ pcm.ploop {
+ type hw
+ card ${toString cfg.loopback.index}
+ device 1
+ subdevice 1
+ format S32_LE
+ }
+ pcm.aduplex {
+ type asym
+ playback.pcm "asoftvol"
+ capture.pcm "loophw01"
+ }
+ pcm.!default {
+ type plug
+ slave.pcm aduplex
+ }
+ '';
+ services.jack.loopback.session = ''
+ alsa_in -j cloop -dcloop &
+ alsa_out -j ploop -dploop &
+ while [ "$(jack_lsp cloop)" == "" ] || [ "$(jack_lsp ploop)" == "" ]; do sleep 1; done
+ jack_connect cloop:capture_1 system:playback_1
+ jack_connect cloop:capture_2 system:playback_2
+ jack_connect system:capture_1 ploop:playback_1
+ jack_connect system:capture_2 ploop:playback_2
+ '';
+
+ assertions = [
+ {
+ assertion = !(cfg.alsa.enable && cfg.loopback.enable);
+ message = "For JACK both alsa and loopback options shouldn't be used at the same time.";
+ }
+ ];
+
+ users.users.jackaudio = {
+ group = "jackaudio";
+ extraGroups = [ "audio" ];
+ description = "JACK Audio system service user";
+ };
+ # http://jackaudio.org/faq/linux_rt_config.html
+ security.pam.loginLimits = [
+ { domain = "@jackaudio"; type = "-"; item = "rtprio"; value = "99"; }
+ { domain = "@jackaudio"; type = "-"; item = "memlock"; value = "unlimited"; }
+ ];
+ users.groups.jackaudio = {};
+
+ environment = {
+ systemPackages = [ cfg.jackd.package ];
+ etc."alsa/conf.d/50-jack.conf".source = "${pkgs.alsaPlugins}/etc/alsa/conf.d/50-jack.conf";
+ variables.JACK_PROMISCUOUS_SERVER = "jackaudio";
+ };
+
+ services.udev.extraRules = ''
+ ACTION=="add", SUBSYSTEM=="sound", ATTRS{id}!="Loopback", TAG+="systemd", ENV{SYSTEMD_WANTS}="jack.service"
+ '';
+
+ systemd.services.jack = {
+ description = "JACK Audio Connection Kit";
+ serviceConfig = {
+ User = "jackaudio";
+ ExecStart = "${cfg.jackd.package}/bin/jackd ${lib.escapeShellArgs cfg.jackd.extraOptions}";
+ LimitRTPRIO = 99;
+ LimitMEMLOCK = "infinity";
+ } // optionalAttrs umaskNeeded {
+ UMask = "007";
+ };
+ path = [ cfg.jackd.package ];
+ environment = {
+ JACK_PROMISCUOUS_SERVER = "jackaudio";
+ JACK_NO_AUDIO_RESERVATION = "1";
+ };
+ restartIfChanged = false;
+ };
+ systemd.services.jack-session = {
+ description = "JACK session";
+ script = ''
+ jack_wait -w
+ ${cfg.jackd.session}
+ ${lib.optionalString cfg.loopback.enable cfg.loopback.session}
+ '';
+ serviceConfig = {
+ RemainAfterExit = true;
+ User = "jackaudio";
+ StateDirectory = "jack";
+ LimitRTPRIO = 99;
+ LimitMEMLOCK = "infinity";
+ };
+ path = [ cfg.jackd.package ];
+ environment = {
+ JACK_PROMISCUOUS_SERVER = "jackaudio";
+ HOME = "/var/lib/jack";
+ };
+ wantedBy = [ "jack.service" ];
+ partOf = [ "jack.service" ];
+ after = [ "jack.service" ];
+ restartIfChanged = false;
+ };
+ })
+
+ ];
+
+ meta.maintainers = [ maintainers.gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/services/audio/liquidsoap.nix b/nixpkgs/nixos/modules/services/audio/liquidsoap.nix
new file mode 100644
index 00000000000..3a047d10a63
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/liquidsoap.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ streams = builtins.attrNames config.services.liquidsoap.streams;
+
+ streamService =
+ name:
+ let stream = builtins.getAttr name config.services.liquidsoap.streams; in
+ { inherit name;
+ value = {
+ after = [ "network-online.target" "sound.target" ];
+ description = "${name} liquidsoap stream";
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.wget ];
+ serviceConfig = {
+ ExecStart = "${pkgs.liquidsoap}/bin/liquidsoap ${stream}";
+ User = "liquidsoap";
+ LogsDirectory = "liquidsoap";
+ };
+ };
+ };
+in
+{
+
+ ##### interface
+
+ options = {
+
+ services.liquidsoap.streams = mkOption {
+
+ description =
+ ''
+ Set of Liquidsoap streams to start,
+ one systemd service per stream.
+ '';
+
+ default = {};
+
+ example = {
+ myStream1 = literalExample "\"/etc/liquidsoap/myStream1.liq\"";
+ myStream2 = literalExample "./myStream2.liq";
+ myStream3 = literalExample "\"out(playlist(\\\"/srv/music/\\\"))\"";
+ };
+
+ type = types.attrsOf (types.either types.path types.str);
+ };
+
+ };
+ ##### implementation
+
+ config = mkIf (builtins.length streams != 0) {
+
+ users.users.liquidsoap = {
+ uid = config.ids.uids.liquidsoap;
+ group = "liquidsoap";
+ extraGroups = [ "audio" ];
+ description = "Liquidsoap streaming user";
+ home = "/var/lib/liquidsoap";
+ createHome = true;
+ };
+
+ users.groups.liquidsoap.gid = config.ids.gids.liquidsoap;
+
+ systemd.services = builtins.listToAttrs ( map streamService streams );
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/mopidy.nix b/nixpkgs/nixos/modules/services/audio/mopidy.nix
new file mode 100644
index 00000000000..a534b692f17
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/mopidy.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with pkgs;
+with lib;
+
+let
+ uid = config.ids.uids.mopidy;
+ gid = config.ids.gids.mopidy;
+ cfg = config.services.mopidy;
+
+ mopidyConf = writeText "mopidy.conf" cfg.configuration;
+
+ mopidyEnv = buildEnv {
+ name = "mopidy-with-extensions-${mopidy.version}";
+ paths = closePropagation cfg.extensionPackages;
+ pathsToLink = [ "/${python.sitePackages}" ];
+ buildInputs = [ makeWrapper ];
+ postBuild = ''
+ makeWrapper ${mopidy}/bin/mopidy $out/bin/mopidy \
+ --prefix PYTHONPATH : $out/${python.sitePackages}
+ '';
+ };
+in {
+
+ options = {
+
+ services.mopidy = {
+
+ enable = mkEnableOption "Mopidy, a music player daemon";
+
+ dataDir = mkOption {
+ default = "/var/lib/mopidy";
+ type = types.str;
+ description = ''
+ The directory where Mopidy stores its state.
+ '';
+ };
+
+ extensionPackages = mkOption {
+ default = [];
+ type = types.listOf types.package;
+ example = literalExample "[ pkgs.mopidy-spotify ]";
+ description = ''
+ Mopidy extensions that should be loaded by the service.
+ '';
+ };
+
+ configuration = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ The configuration that Mopidy should use.
+ '';
+ };
+
+ extraConfigFiles = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ Extra config file read by Mopidy when the service starts.
+ Later files in the list overrides earlier configuration.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - mopidy mopidy - -"
+ ];
+
+ systemd.services.mopidy = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "sound.target" ];
+ description = "mopidy music player daemon";
+ serviceConfig = {
+ ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)}";
+ User = "mopidy";
+ };
+ };
+
+ systemd.services.mopidy-scan = {
+ description = "mopidy local files scanner";
+ serviceConfig = {
+ ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)} local scan";
+ User = "mopidy";
+ Type = "oneshot";
+ };
+ };
+
+ users.users.mopidy = {
+ inherit uid;
+ group = "mopidy";
+ extraGroups = [ "audio" ];
+ description = "Mopidy daemon user";
+ home = cfg.dataDir;
+ };
+
+ users.groups.mopidy.gid = gid;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/mpd.nix b/nixpkgs/nixos/modules/services/audio/mpd.nix
new file mode 100644
index 00000000000..0df8f9688d2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/mpd.nix
@@ -0,0 +1,202 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ name = "mpd";
+
+ uid = config.ids.uids.mpd;
+ gid = config.ids.gids.mpd;
+ cfg = config.services.mpd;
+
+ mpdConf = pkgs.writeText "mpd.conf" ''
+ music_directory "${cfg.musicDirectory}"
+ playlist_directory "${cfg.playlistDirectory}"
+ ${lib.optionalString (cfg.dbFile != null) ''
+ db_file "${cfg.dbFile}"
+ ''}
+ state_file "${cfg.dataDir}/state"
+ sticker_file "${cfg.dataDir}/sticker.sql"
+ log_file "syslog"
+ user "${cfg.user}"
+ group "${cfg.group}"
+
+ ${optionalString (cfg.network.listenAddress != "any") ''bind_to_address "${cfg.network.listenAddress}"''}
+ ${optionalString (cfg.network.port != 6600) ''port "${toString cfg.network.port}"''}
+
+ ${cfg.extraConfig}
+ '';
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.mpd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable MPD, the music player daemon.
+ '';
+ };
+
+ startWhenNeeded = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If set, <command>mpd</command> is socket-activated; that
+ is, instead of having it permanently running as a daemon,
+ systemd will start it on the first incoming connection.
+ '';
+ };
+
+ musicDirectory = mkOption {
+ type = with types; either path (strMatching "(http|https|nfs|smb)://.+");
+ default = "${cfg.dataDir}/music";
+ defaultText = ''''${dataDir}/music'';
+ description = ''
+ The directory or NFS/SMB network share where mpd reads music from.
+ '';
+ };
+
+ playlistDirectory = mkOption {
+ type = types.path;
+ default = "${cfg.dataDir}/playlists";
+ defaultText = ''''${dataDir}/playlists'';
+ description = ''
+ The directory where mpd stores playlists.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra directives added to to the end of MPD's configuration file,
+ mpd.conf. Basic configuration like file location and uid/gid
+ is added automatically to the beginning of the file. For available
+ options see <literal>man 5 mpd.conf</literal>'.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/${name}";
+ description = ''
+ The directory where MPD stores its state, tag cache,
+ playlists etc.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = name;
+ description = "User account under which MPD runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = name;
+ description = "Group account under which MPD runs.";
+ };
+
+ network = {
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ example = "any";
+ description = ''
+ The address for the daemon to listen on.
+ Use <literal>any</literal> to listen on all addresses.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 6600;
+ description = ''
+ This setting is the TCP port that is desired for the daemon to get assigned
+ to.
+ '';
+ };
+
+ };
+
+ dbFile = mkOption {
+ type = types.nullOr types.str;
+ default = "${cfg.dataDir}/tag_cache";
+ defaultText = ''''${dataDir}/tag_cache'';
+ description = ''
+ The path to MPD's database. If set to <literal>null</literal> the
+ parameter is omitted from the configuration.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.sockets.mpd = mkIf cfg.startWhenNeeded {
+ description = "Music Player Daemon Socket";
+ wantedBy = [ "sockets.target" ];
+ listenStreams = [
+ "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}"
+ ];
+ socketConfig = {
+ Backlog = 5;
+ KeepAlive = true;
+ PassCredentials = true;
+ };
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.playlistDirectory}' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.mpd = {
+ after = [ "network.target" "sound.target" ];
+ description = "Music Player Daemon";
+ wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
+
+ serviceConfig = {
+ User = "${cfg.user}";
+ ExecStart = "${pkgs.mpd}/bin/mpd --no-daemon ${mpdConf}";
+ Type = "notify";
+ LimitRTPRIO = 50;
+ LimitRTTIME = "infinity";
+ ProtectSystem = true;
+ NoNewPrivileges = true;
+ ProtectKernelTunables = true;
+ ProtectControlGroups = true;
+ ProtectKernelModules = true;
+ RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
+ RestrictNamespaces = true;
+ };
+ };
+
+ users.users = optionalAttrs (cfg.user == name) (singleton {
+ inherit uid;
+ inherit name;
+ group = cfg.group;
+ extraGroups = [ "audio" ];
+ description = "Music Player Daemon user";
+ home = "${cfg.dataDir}";
+ });
+
+ users.groups = optionalAttrs (cfg.group == name) (singleton {
+ inherit name;
+ gid = gid;
+ });
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/roon-server.nix b/nixpkgs/nixos/modules/services/audio/roon-server.nix
new file mode 100644
index 00000000000..4eda3c5708d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/roon-server.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ name = "roon-server";
+ cfg = config.services.roon-server;
+in {
+ options = {
+ services.roon-server = {
+ enable = mkEnableOption "Roon Server";
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for the server.
+
+ UDP: 9003
+ TCP: 9100 - 9200
+ '';
+ };
+ user = mkOption {
+ type = types.str;
+ default = "roon-server";
+ description = ''
+ User to run the Roon Server as.
+ '';
+ };
+ group = mkOption {
+ type = types.str;
+ default = "roon-server";
+ description = ''
+ Group to run the Roon Server as.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.roon-server = {
+ after = [ "network.target" ];
+ description = "Roon Server";
+ wantedBy = [ "multi-user.target" ];
+
+ environment.ROON_DATAROOT = "/var/lib/${name}";
+
+ serviceConfig = {
+ ExecStart = "${pkgs.roon-server}/opt/start.sh";
+ LimitNOFILE = 8192;
+ User = cfg.user;
+ Group = cfg.group;
+ StateDirectory = name;
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPortRanges = [
+ { from = 9100; to = 9200; }
+ ];
+ allowedUDPPorts = [ 9003 ];
+ };
+
+
+ users.groups.${cfg.group} = {};
+ users.users.${cfg.user} =
+ if cfg.user == "roon-server" then {
+ isSystemUser = true;
+ description = "Roon Server user";
+ groups = [ cfg.group "audio" ];
+ }
+ else {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/audio/slimserver.nix b/nixpkgs/nixos/modules/services/audio/slimserver.nix
new file mode 100644
index 00000000000..8f94a2b4940
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/slimserver.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.slimserver;
+
+in {
+ options = {
+
+ services.slimserver = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable slimserver.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.slimserver;
+ defaultText = "pkgs.slimserver";
+ description = "Slimserver package to use.";
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/slimserver";
+ description = ''
+ The directory where slimserver stores its state, tag cache,
+ playlists etc.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - slimserver slimserver - -"
+ ];
+
+ systemd.services.slimserver = {
+ after = [ "network.target" ];
+ description = "Slim Server for Logitech Squeezebox Players";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = "slimserver";
+ # Issue 40589: Disable broken image/video support (audio still works!)
+ ExecStart = "${cfg.package}/slimserver.pl --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
+ };
+ };
+
+ users = {
+ users.slimserver = {
+ description = "Slimserver daemon user";
+ home = cfg.dataDir;
+ group = "slimserver";
+ };
+ groups.slimserver = {};
+ };
+ };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/audio/snapserver.nix b/nixpkgs/nixos/modules/services/audio/snapserver.nix
new file mode 100644
index 00000000000..b0b9264e816
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/snapserver.nix
@@ -0,0 +1,216 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ name = "snapserver";
+
+ cfg = config.services.snapserver;
+
+ # Using types.nullOr to inherit upstream defaults.
+ sampleFormat = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Default sample format.
+ '';
+ example = "48000:16:2";
+ };
+
+ codec = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Default audio compression method.
+ '';
+ example = "flac";
+ };
+
+ streamToOption = name: opt:
+ let
+ os = val:
+ optionalString (val != null) "${val}";
+ os' = prefixx: val:
+ optionalString (val != null) (prefixx + "${val}");
+ flatten = key: value:
+ "&${key}=${value}";
+ in
+ "-s ${opt.type}://" + os opt.location + "?" + os' "name=" name
+ + concatStrings (mapAttrsToList flatten opt.query);
+
+ optionalNull = val: ret:
+ optional (val != null) ret;
+
+ optionString = concatStringsSep " " (mapAttrsToList streamToOption cfg.streams
+ ++ ["-p ${toString cfg.port}"]
+ ++ ["--controlPort ${toString cfg.controlPort}"]
+ ++ optionalNull cfg.sampleFormat "--sampleFormat ${cfg.sampleFormat}"
+ ++ optionalNull cfg.codec "-c ${cfg.codec}"
+ ++ optionalNull cfg.streamBuffer "--streamBuffer ${cfg.streamBuffer}"
+ ++ optionalNull cfg.buffer "-b ${cfg.buffer}"
+ ++ optional cfg.sendToMuted "--sendToMuted");
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.snapserver = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable snapserver.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 1704;
+ description = ''
+ The port that snapclients can connect to.
+ '';
+ };
+
+ controlPort = mkOption {
+ type = types.port;
+ default = 1705;
+ description = ''
+ The port for control connections (JSON-RPC).
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to automatically open the specified ports in the firewall.
+ '';
+ };
+
+ inherit sampleFormat;
+ inherit codec;
+
+ streams = mkOption {
+ type = with types; attrsOf (submodule {
+ options = {
+ location = mkOption {
+ type = types.path;
+ description = ''
+ The location of the pipe.
+ '';
+ };
+ type = mkOption {
+ type = types.enum [ "pipe" "file" "process" "spotify" "airplay" ];
+ default = "pipe";
+ description = ''
+ The type of input stream.
+ '';
+ };
+ query = mkOption {
+ type = attrsOf str;
+ default = {};
+ description = ''
+ Key-value pairs that convey additional parameters about a stream.
+ '';
+ example = literalExample ''
+ # for type == "pipe":
+ {
+ mode = "listen";
+ };
+ # for type == "process":
+ {
+ params = "--param1 --param2";
+ logStderr = "true";
+ };
+ '';
+ };
+ inherit sampleFormat;
+ inherit codec;
+ };
+ });
+ default = { default = {}; };
+ description = ''
+ The definition for an input source.
+ '';
+ example = literalExample ''
+ {
+ mpd = {
+ type = "pipe";
+ location = "/run/snapserver/mpd";
+ sampleFormat = "48000:16:2";
+ codec = "pcm";
+ };
+ };
+ '';
+ };
+
+ streamBuffer = mkOption {
+ type = with types; nullOr int;
+ default = null;
+ description = ''
+ Stream read (input) buffer in ms.
+ '';
+ example = 20;
+ };
+
+ buffer = mkOption {
+ type = with types; nullOr int;
+ default = null;
+ description = ''
+ Network buffer in ms.
+ '';
+ example = 1000;
+ };
+
+ sendToMuted = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Send audio to muted clients.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.snapserver = {
+ after = [ "network.target" ];
+ description = "Snapserver";
+ wantedBy = [ "multi-user.target" ];
+ before = [ "mpd.service" "mopidy.service" ];
+
+ serviceConfig = {
+ DynamicUser = true;
+ ExecStart = "${pkgs.snapcast}/bin/snapserver --daemon ${optionString}";
+ Type = "forking";
+ LimitRTPRIO = 50;
+ LimitRTTIME = "infinity";
+ NoNewPrivileges = true;
+ PIDFile = "/run/${name}/pid";
+ ProtectKernelTunables = true;
+ ProtectControlGroups = true;
+ ProtectKernelModules = true;
+ RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
+ RestrictNamespaces = true;
+ RuntimeDirectory = name;
+ StateDirectory = name;
+ };
+ };
+
+ networking.firewall.allowedTCPPorts = optionals cfg.openFirewall [ cfg.port cfg.controlPort ];
+ };
+
+ meta = {
+ maintainers = with maintainers; [ tobim ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/spotifyd.nix b/nixpkgs/nixos/modules/services/audio/spotifyd.nix
new file mode 100644
index 00000000000..4b74e753279
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/spotifyd.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.spotifyd;
+ spotifydConf = pkgs.writeText "spotifyd.conf" cfg.config;
+in
+{
+ options = {
+ services.spotifyd = {
+ enable = mkEnableOption "spotifyd, a Spotify playing daemon";
+
+ config = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Configuration for Spotifyd. For syntax and directives, see
+ https://github.com/Spotifyd/spotifyd#Configuration.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.spotifyd = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target" "sound.target" ];
+ description = "spotifyd, a Spotify playing daemon";
+ serviceConfig = {
+ ExecStart = "${pkgs.spotifyd}/bin/spotifyd --no-daemon --cache-path /var/cache/spotifyd --config-path ${spotifydConf}";
+ Restart = "always";
+ RestartSec = 12;
+ DynamicUser = true;
+ CacheDirectory = "spotifyd";
+ SupplementaryGroups = ["audio"];
+ };
+ };
+ };
+
+ meta.maintainers = [ maintainers.anderslundstedt ];
+}
diff --git a/nixpkgs/nixos/modules/services/audio/squeezelite.nix b/nixpkgs/nixos/modules/services/audio/squeezelite.nix
new file mode 100644
index 00000000000..05506f5bcc7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/squeezelite.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ dataDir = "/var/lib/squeezelite";
+ cfg = config.services.squeezelite;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.squeezelite= {
+
+ enable = mkEnableOption "Squeezelite, a software Squeezebox emulator";
+
+ extraArguments = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Additional command line arguments to pass to Squeezelite.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.squeezelite= {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "sound.target" ];
+ description = "Software Squeezebox emulator";
+ serviceConfig = {
+ DynamicUser = true;
+ ExecStart = "${pkgs.squeezelite}/bin/squeezelite -N ${dataDir}/player-name ${cfg.extraArguments}";
+ StateDirectory = builtins.baseNameOf dataDir;
+ SupplementaryGroups = "audio";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/ympd.nix b/nixpkgs/nixos/modules/services/audio/ympd.nix
new file mode 100644
index 00000000000..551bd941fe6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/ympd.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.ympd;
+in {
+
+ ###### interface
+
+ options = {
+
+ services.ympd = {
+
+ enable = mkEnableOption "ympd, the MPD Web GUI";
+
+ webPort = mkOption {
+ type = types.either types.str types.port; # string for backwards compat
+ default = "8080";
+ description = "The port where ympd's web interface will be available.";
+ example = "ssl://8080:/path/to/ssl-private-key.pem";
+ };
+
+ mpd = {
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "The host where MPD is listening.";
+ example = "localhost";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = config.services.mpd.network.port;
+ description = "The port where MPD is listening.";
+ example = 6600;
+ };
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.ympd = {
+ description = "Standalone MPD Web GUI written in C";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${pkgs.ympd}/bin/ympd --host ${cfg.mpd.host} --port ${toString cfg.mpd.port} --webport ${toString cfg.webPort} --user nobody";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/backup/automysqlbackup.nix b/nixpkgs/nixos/modules/services/backup/automysqlbackup.nix
new file mode 100644
index 00000000000..1884f3536a9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/automysqlbackup.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+ inherit (lib) concatMapStringsSep concatStringsSep isInt isList literalExample;
+ inherit (lib) mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkOption optional types;
+
+ cfg = config.services.automysqlbackup;
+ pkg = pkgs.automysqlbackup;
+ user = "automysqlbackup";
+ group = "automysqlbackup";
+
+ toStr = val:
+ if isList val then "( ${concatMapStringsSep " " (val: "'${val}'") val} )"
+ else if isInt val then toString val
+ else if true == val then "'yes'"
+ else if false == val then "'no'"
+ else "'${toString val}'";
+
+ configFile = pkgs.writeText "automysqlbackup.conf" ''
+ #version=${pkg.version}
+ # DONT'T REMOVE THE PREVIOUS VERSION LINE!
+ #
+ ${concatStringsSep "\n" (mapAttrsToList (name: value: "CONFIG_${name}=${toStr value}") cfg.config)}
+ '';
+
+in
+{
+ # interface
+ options = {
+ services.automysqlbackup = {
+
+ enable = mkEnableOption "AutoMySQLBackup";
+
+ calendar = mkOption {
+ type = types.str;
+ default = "01:15:00";
+ description = ''
+ Configured when to run the backup service systemd unit (DayOfWeek Year-Month-Day Hour:Minute:Second).
+ '';
+ };
+
+ config = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
+ default = {};
+ description = ''
+ automysqlbackup configuration. Refer to
+ <filename>''${pkgs.automysqlbackup}/etc/automysqlbackup.conf</filename>
+ for details on supported values.
+ '';
+ example = literalExample ''
+ {
+ db_names = [ "nextcloud" "matomo" ];
+ table_exclude = [ "nextcloud.oc_users" "nextcloud.oc_whats_new" ];
+ mailcontent = "log";
+ mail_address = "admin@example.org";
+ }
+ '';
+ };
+
+ };
+ };
+
+ # implementation
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = !config.services.mysqlBackup.enable;
+ message = "Please choose one of services.mysqlBackup or services.automysqlbackup.";
+ }
+ ];
+
+ services.automysqlbackup.config = mapAttrs (name: mkDefault) {
+ mysql_dump_username = user;
+ mysql_dump_host = "localhost";
+ backup_dir = "/var/backup/mysql";
+ db_exclude = [ "information_schema" "performance_schema" ];
+ mailcontent = "stdout";
+ mysql_dump_single_transaction = true;
+ };
+
+ systemd.timers.automysqlbackup = {
+ description = "automysqlbackup timer";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = cfg.calendar;
+ AccuracySec = "5m";
+ };
+ };
+
+ systemd.services.automysqlbackup = {
+ description = "automysqlbackup service";
+ serviceConfig = {
+ User = user;
+ Group = group;
+ ExecStart = "${pkg}/bin/automysqlbackup ${configFile}";
+ };
+ };
+
+ environment.systemPackages = [ pkg ];
+
+ users.users.${user}.group = group;
+ users.groups.${group} = { };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.config.backup_dir}' 0750 ${user} ${group} - -"
+ ];
+
+ services.mysql.ensureUsers = optional (config.services.mysql.enable && cfg.config.mysql_dump_host == "localhost") {
+ name = user;
+ ensurePermissions = { "*.*" = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES"; };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/bacula.nix b/nixpkgs/nixos/modules/services/backup/bacula.nix
new file mode 100644
index 00000000000..41bda7893a7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/bacula.nix
@@ -0,0 +1,412 @@
+{ config, lib, pkgs, ... }:
+
+# TODO: test configuration when building nixexpr (use -t parameter)
+# TODO: support sqlite3 (it's deprecate?) and mysql
+
+with lib;
+
+let
+ libDir = "/var/lib/bacula";
+
+ fd_cfg = config.services.bacula-fd;
+ fd_conf = pkgs.writeText "bacula-fd.conf"
+ ''
+ Client {
+ Name = "${fd_cfg.name}";
+ FDPort = ${toString fd_cfg.port};
+ WorkingDirectory = "${libDir}";
+ Pid Directory = "/run";
+ ${fd_cfg.extraClientConfig}
+ }
+
+ ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+ Director {
+ Name = "${name}";
+ Password = "${value.password}";
+ Monitor = "${value.monitor}";
+ }
+ '') fd_cfg.director)}
+
+ Messages {
+ Name = Standard;
+ syslog = all, !skipped, !restored
+ ${fd_cfg.extraMessagesConfig}
+ }
+ '';
+
+ sd_cfg = config.services.bacula-sd;
+ sd_conf = pkgs.writeText "bacula-sd.conf"
+ ''
+ Storage {
+ Name = "${sd_cfg.name}";
+ SDPort = ${toString sd_cfg.port};
+ WorkingDirectory = "${libDir}";
+ Pid Directory = "/run";
+ ${sd_cfg.extraStorageConfig}
+ }
+
+ ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+ Device {
+ Name = "${name}";
+ Archive Device = "${value.archiveDevice}";
+ Media Type = "${value.mediaType}";
+ ${value.extraDeviceConfig}
+ }
+ '') sd_cfg.device)}
+
+ ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+ Director {
+ Name = "${name}";
+ Password = "${value.password}";
+ Monitor = "${value.monitor}";
+ }
+ '') sd_cfg.director)}
+
+ Messages {
+ Name = Standard;
+ syslog = all, !skipped, !restored
+ ${sd_cfg.extraMessagesConfig}
+ }
+ '';
+
+ dir_cfg = config.services.bacula-dir;
+ dir_conf = pkgs.writeText "bacula-dir.conf"
+ ''
+ Director {
+ Name = "${dir_cfg.name}";
+ Password = "${dir_cfg.password}";
+ DirPort = ${toString dir_cfg.port};
+ Working Directory = "${libDir}";
+ Pid Directory = "/run/";
+ QueryFile = "${pkgs.bacula}/etc/query.sql";
+ ${dir_cfg.extraDirectorConfig}
+ }
+
+ Catalog {
+ Name = "PostgreSQL";
+ dbname = "bacula";
+ user = "bacula";
+ }
+
+ Messages {
+ Name = Standard;
+ syslog = all, !skipped, !restored
+ ${dir_cfg.extraMessagesConfig}
+ }
+
+ ${dir_cfg.extraConfig}
+ '';
+
+ directorOptions = {...}:
+ {
+ options = {
+ password = mkOption {
+ # TODO: required?
+ description = ''
+ Specifies the password that must be supplied for a Director to b
+ '';
+ };
+
+ monitor = mkOption {
+ default = "no";
+ example = "yes";
+ description = ''
+ If Monitor is set to no (default), this director will have full
+ '';
+ };
+ };
+ };
+
+ deviceOptions = {...}:
+ {
+ options = {
+ archiveDevice = mkOption {
+ # TODO: required?
+ description = ''
+ The specified name-string gives the system file name of the storage device managed by this storage daemon. This will usually be the device file name of a removable storage device (tape drive), for example " /dev/nst0" or "/dev/rmt/0mbn". For a DVD-writer, it will be for example /dev/hdc. It may also be a directory name if you are archiving to disk storage.
+ '';
+ };
+
+ mediaType = mkOption {
+ # TODO: required?
+ description = ''
+ The specified name-string names the type of media supported by this device, for example, "DLT7000". Media type names are arbitrary in that you set them to anything you want, but they must be known to the volume database to keep track of which storage daemons can read which volumes. In general, each different storage type should have a unique Media Type associated with it. The same name-string must appear in the appropriate Storage resource definition in the Director's configuration file.
+ '';
+ };
+
+ extraDeviceConfig = mkOption {
+ default = "";
+ description = ''
+ Extra configuration to be passed in Device directive.
+ '';
+ example = ''
+ LabelMedia = yes
+ Random Access = no
+ AutomaticMount = no
+ RemovableMedia = no
+ MaximumOpenWait = 60
+ AlwaysOpen = no
+ '';
+ };
+ };
+ };
+
+in {
+ options = {
+ services.bacula-fd = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the Bacula File Daemon.
+ '';
+ };
+
+ name = mkOption {
+ default = "${config.networking.hostName}-fd";
+ description = ''
+ The client name that must be used by the Director when connecting.
+ Generally, it is a good idea to use a name related to the machine
+ so that error messages can be easily identified if you have multiple
+ Clients. This directive is required.
+ '';
+ };
+
+ port = mkOption {
+ default = 9102;
+ type = types.int;
+ description = ''
+ This specifies the port number on which the Client listens for
+ Director connections. It must agree with the FDPort specified in
+ the Client resource of the Director's configuration file.
+ '';
+ };
+
+ director = mkOption {
+ default = {};
+ description = ''
+ This option defines director resources in Bacula File Daemon.
+ '';
+ type = with types; attrsOf (submodule directorOptions);
+ };
+
+ extraClientConfig = mkOption {
+ default = "";
+ description = ''
+ Extra configuration to be passed in Client directive.
+ '';
+ example = ''
+ Maximum Concurrent Jobs = 20;
+ Heartbeat Interval = 30;
+ '';
+ };
+
+ extraMessagesConfig = mkOption {
+ default = "";
+ description = ''
+ Extra configuration to be passed in Messages directive.
+ '';
+ example = ''
+ console = all
+ '';
+ };
+ };
+
+ services.bacula-sd = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Bacula Storage Daemon.
+ '';
+ };
+
+ name = mkOption {
+ default = "${config.networking.hostName}-sd";
+ description = ''
+ Specifies the Name of the Storage daemon.
+ '';
+ };
+
+ port = mkOption {
+ default = 9103;
+ type = types.int;
+ description = ''
+ Specifies port number on which the Storage daemon listens for Director connections. The default is 9103.
+ '';
+ };
+
+ director = mkOption {
+ default = {};
+ description = ''
+ This option defines Director resources in Bacula Storage Daemon.
+ '';
+ type = with types; attrsOf (submodule directorOptions);
+ };
+
+ device = mkOption {
+ default = {};
+ description = ''
+ This option defines Device resources in Bacula Storage Daemon.
+ '';
+ type = with types; attrsOf (submodule deviceOptions);
+ };
+
+ extraStorageConfig = mkOption {
+ default = "";
+ description = ''
+ Extra configuration to be passed in Storage directive.
+ '';
+ example = ''
+ Maximum Concurrent Jobs = 20;
+ Heartbeat Interval = 30;
+ '';
+ };
+
+ extraMessagesConfig = mkOption {
+ default = "";
+ description = ''
+ Extra configuration to be passed in Messages directive.
+ '';
+ example = ''
+ console = all
+ '';
+ };
+
+ };
+
+ services.bacula-dir = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Bacula Director Daemon.
+ '';
+ };
+
+ name = mkOption {
+ default = "${config.networking.hostName}-dir";
+ description = ''
+ The director name used by the system administrator. This directive is required.
+ '';
+ };
+
+ port = mkOption {
+ default = 9101;
+ type = types.int;
+ description = ''
+ Specify the port (a positive integer) on which the Director daemon will listen for Bacula Console connections. This same port number must be specified in the Director resource of the Console configuration file. The default is 9101, so normally this directive need not be specified. This directive should not be used if you specify DirAddresses (N.B plural) directive.
+ '';
+ };
+
+ password = mkOption {
+ # TODO: required?
+ description = ''
+ Specifies the password that must be supplied for a Director.
+ '';
+ };
+
+ extraMessagesConfig = mkOption {
+ default = "";
+ description = ''
+ Extra configuration to be passed in Messages directive.
+ '';
+ example = ''
+ console = all
+ '';
+ };
+
+ extraDirectorConfig = mkOption {
+ default = "";
+ description = ''
+ Extra configuration to be passed in Director directive.
+ '';
+ example = ''
+ Maximum Concurrent Jobs = 20;
+ Heartbeat Interval = 30;
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra configuration for Bacula Director Daemon.
+ '';
+ example = ''
+ TODO
+ '';
+ };
+ };
+ };
+
+ config = mkIf (fd_cfg.enable || sd_cfg.enable || dir_cfg.enable) {
+ systemd.services.bacula-fd = mkIf fd_cfg.enable {
+ after = [ "network.target" ];
+ description = "Bacula File Daemon";
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.bacula ];
+ serviceConfig = {
+ ExecStart = "${pkgs.bacula}/sbin/bacula-fd -f -u root -g bacula -c ${fd_conf}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ LogsDirectory = "bacula";
+ StateDirectory = "bacula";
+ };
+ };
+
+ systemd.services.bacula-sd = mkIf sd_cfg.enable {
+ after = [ "network.target" ];
+ description = "Bacula Storage Daemon";
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.bacula ];
+ serviceConfig = {
+ ExecStart = "${pkgs.bacula}/sbin/bacula-sd -f -u bacula -g bacula -c ${sd_conf}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ LogsDirectory = "bacula";
+ StateDirectory = "bacula";
+ };
+ };
+
+ services.postgresql.enable = dir_cfg.enable == true;
+
+ systemd.services.bacula-dir = mkIf dir_cfg.enable {
+ after = [ "network.target" "postgresql.service" ];
+ description = "Bacula Director Daemon";
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.bacula ];
+ serviceConfig = {
+ ExecStart = "${pkgs.bacula}/sbin/bacula-dir -f -u bacula -g bacula -c ${dir_conf}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ LogsDirectory = "bacula";
+ StateDirectory = "bacula";
+ };
+ preStart = ''
+ if ! test -e "${libDir}/db-created"; then
+ ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole bacula
+ #${pkgs.postgresql}/bin/createdb --owner bacula bacula
+
+ # populate DB
+ ${pkgs.bacula}/etc/create_bacula_database postgresql
+ ${pkgs.bacula}/etc/make_bacula_tables postgresql
+ ${pkgs.bacula}/etc/grant_bacula_privileges postgresql
+ touch "${libDir}/db-created"
+ else
+ ${pkgs.bacula}/etc/update_bacula_tables postgresql || true
+ fi
+ '';
+ };
+
+ environment.systemPackages = [ pkgs.bacula ];
+
+ users.users.bacula = {
+ group = "bacula";
+ uid = config.ids.uids.bacula;
+ home = "${libDir}";
+ createHome = true;
+ description = "Bacula Daemons user";
+ shell = "${pkgs.bash}/bin/bash";
+ };
+
+ users.groups.bacula.gid = config.ids.gids.bacula;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/borgbackup.nix b/nixpkgs/nixos/modules/services/backup/borgbackup.nix
new file mode 100644
index 00000000000..2ad116a7872
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/borgbackup.nix
@@ -0,0 +1,615 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ isLocalPath = x:
+ builtins.substring 0 1 x == "/" # absolute path
+ || builtins.substring 0 1 x == "." # relative path
+ || builtins.match "[.*:.*]" == null; # not machine:path
+
+ mkExcludeFile = cfg:
+ # Write each exclude pattern to a new line
+ pkgs.writeText "excludefile" (concatStringsSep "\n" cfg.exclude);
+
+ mkKeepArgs = cfg:
+ # If cfg.prune.keep e.g. has a yearly attribute,
+ # its content is passed on as --keep-yearly
+ concatStringsSep " "
+ (mapAttrsToList (x: y: "--keep-${x}=${toString y}") cfg.prune.keep);
+
+ mkBackupScript = cfg: ''
+ on_exit()
+ {
+ exitStatus=$?
+ # Reset the EXIT handler, or else we're called again on 'exit' below
+ trap - EXIT
+ ${cfg.postHook}
+ exit $exitStatus
+ }
+ trap 'on_exit' INT TERM QUIT EXIT
+
+ archiveName="${cfg.archiveBaseName}-$(date ${cfg.dateFormat})"
+ archiveSuffix="${optionalString cfg.appendFailedSuffix ".failed"}"
+ ${cfg.preHook}
+ '' + optionalString cfg.doInit ''
+ # Run borg init if the repo doesn't exist yet
+ if ! borg list $extraArgs > /dev/null; then
+ borg init $extraArgs \
+ --encryption ${cfg.encryption.mode} \
+ $extraInitArgs
+ ${cfg.postInit}
+ fi
+ '' + ''
+ borg create $extraArgs \
+ --compression ${cfg.compression} \
+ --exclude-from ${mkExcludeFile cfg} \
+ $extraCreateArgs \
+ "::$archiveName$archiveSuffix" \
+ ${escapeShellArgs cfg.paths}
+ '' + optionalString cfg.appendFailedSuffix ''
+ borg rename $extraArgs \
+ "::$archiveName$archiveSuffix" "$archiveName"
+ '' + ''
+ ${cfg.postCreate}
+ '' + optionalString (cfg.prune.keep != { }) ''
+ borg prune $extraArgs \
+ ${mkKeepArgs cfg} \
+ --prefix ${escapeShellArg cfg.prune.prefix} \
+ $extraPruneArgs
+ ${cfg.postPrune}
+ '';
+
+ mkPassEnv = cfg: with cfg.encryption;
+ if passCommand != null then
+ { BORG_PASSCOMMAND = passCommand; }
+ else if passphrase != null then
+ { BORG_PASSPHRASE = passphrase; }
+ else { };
+
+ mkBackupService = name: cfg:
+ let
+ userHome = config.users.users.${cfg.user}.home;
+ in nameValuePair "borgbackup-job-${name}" {
+ description = "BorgBackup job ${name}";
+ path = with pkgs; [
+ borgbackup openssh
+ ];
+ script = mkBackupScript cfg;
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ # Only run when no other process is using CPU or disk
+ CPUSchedulingPolicy = "idle";
+ IOSchedulingClass = "idle";
+ ProtectSystem = "strict";
+ ReadWritePaths =
+ [ "${userHome}/.config/borg" "${userHome}/.cache/borg" ]
+ ++ cfg.readWritePaths
+ # Borg needs write access to repo if it is not remote
+ ++ optional (isLocalPath cfg.repo) cfg.repo;
+ PrivateTmp = cfg.privateTmp;
+ };
+ environment = {
+ BORG_REPO = cfg.repo;
+ inherit (cfg) extraArgs extraInitArgs extraCreateArgs extraPruneArgs;
+ } // (mkPassEnv cfg) // cfg.environment;
+ inherit (cfg) startAt;
+ };
+
+ # Paths listed in ReadWritePaths must exist before service is started
+ mkActivationScript = name: cfg:
+ let
+ install = "install -o ${cfg.user} -g ${cfg.group}";
+ in
+ nameValuePair "borgbackup-job-${name}" (stringAfter [ "users" ] (''
+ # Eensure that the home directory already exists
+ # We can't assert createHome == true because that's not the case for root
+ cd "${config.users.users.${cfg.user}.home}"
+ ${install} -d .config/borg
+ ${install} -d .cache/borg
+ '' + optionalString (isLocalPath cfg.repo) ''
+ ${install} -d ${escapeShellArg cfg.repo}
+ ''));
+
+ mkPassAssertion = name: cfg: {
+ assertion = with cfg.encryption;
+ mode != "none" -> passCommand != null || passphrase != null;
+ message =
+ "passCommand or passphrase has to be specified because"
+ + '' borgbackup.jobs.${name}.encryption != "none"'';
+ };
+
+ mkRepoService = name: cfg:
+ nameValuePair "borgbackup-repo-${name}" {
+ description = "Create BorgBackup repository ${name} directory";
+ script = ''
+ mkdir -p ${escapeShellArg cfg.path}
+ chown ${cfg.user}:${cfg.group} ${escapeShellArg cfg.path}
+ '';
+ serviceConfig = {
+ # The service's only task is to ensure that the specified path exists
+ Type = "oneshot";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ mkAuthorizedKey = cfg: appendOnly: key:
+ let
+ # Because of the following line, clients do not need to specify an absolute repo path
+ cdCommand = "cd ${escapeShellArg cfg.path}";
+ restrictedArg = "--restrict-to-${if cfg.allowSubRepos then "path" else "repository"} .";
+ appendOnlyArg = optionalString appendOnly "--append-only";
+ quotaArg = optionalString (cfg.quota != null) "--storage-quota ${cfg.quota}";
+ serveCommand = "borg serve ${restrictedArg} ${appendOnlyArg} ${quotaArg}";
+ in
+ ''command="${cdCommand} && ${serveCommand}",restrict ${key}'';
+
+ mkUsersConfig = name: cfg: {
+ users.${cfg.user} = {
+ openssh.authorizedKeys.keys =
+ (map (mkAuthorizedKey cfg false) cfg.authorizedKeys
+ ++ map (mkAuthorizedKey cfg true) cfg.authorizedKeysAppendOnly);
+ useDefaultShell = true;
+ };
+ groups.${cfg.group} = { };
+ };
+
+ mkKeysAssertion = name: cfg: {
+ assertion = cfg.authorizedKeys != [ ] || cfg.authorizedKeysAppendOnly != [ ];
+ message =
+ "borgbackup.repos.${name} does not make sense"
+ + " without at least one public key";
+ };
+
+in {
+ meta.maintainers = with maintainers; [ dotlambda ];
+
+ ###### interface
+
+ options.services.borgbackup.jobs = mkOption {
+ description = "Deduplicating backups using BorgBackup.";
+ default = { };
+ example = literalExample ''
+ {
+ rootBackup = {
+ paths = "/";
+ exclude = [ "/nix" ];
+ repo = "/path/to/local/repo";
+ encryption = {
+ mode = "repokey";
+ passphrase = "secret";
+ };
+ compression = "auto,lzma";
+ startAt = "weekly";
+ };
+ }
+ '';
+ type = types.attrsOf (types.submodule (let globalConfig = config; in
+ { name, config, ... }: {
+ options = {
+
+ paths = mkOption {
+ type = with types; coercedTo str lib.singleton (listOf str);
+ description = "Path(s) to back up.";
+ example = "/home/user";
+ };
+
+ repo = mkOption {
+ type = types.str;
+ description = "Remote or local repository to back up to.";
+ example = "user@machine:/path/to/repo";
+ };
+
+ archiveBaseName = mkOption {
+ type = types.strMatching "[^/{}]+";
+ default = "${globalConfig.networking.hostName}-${name}";
+ defaultText = "\${config.networking.hostName}-<name>";
+ description = ''
+ How to name the created archives. A timestamp, whose format is
+ determined by <option>dateFormat</option>, will be appended. The full
+ name can be modified at runtime (<literal>$archiveName</literal>).
+ Placeholders like <literal>{hostname}</literal> must not be used.
+ '';
+ };
+
+ dateFormat = mkOption {
+ type = types.str;
+ description = ''
+ Arguments passed to <command>date</command>
+ to create a timestamp suffix for the archive name.
+ '';
+ default = "+%Y-%m-%dT%H:%M:%S";
+ example = "-u +%s";
+ };
+
+ startAt = mkOption {
+ type = with types; either str (listOf str);
+ default = "daily";
+ description = ''
+ When or how often the backup should run.
+ Must be in the format described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ If you do not want the backup to start
+ automatically, use <literal>[ ]</literal>.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ description = ''
+ The user <command>borg</command> is run as.
+ User or group need read permission
+ for the specified <option>paths</option>.
+ '';
+ default = "root";
+ };
+
+ group = mkOption {
+ type = types.str;
+ description = ''
+ The group borg is run as. User or group needs read permission
+ for the specified <option>paths</option>.
+ '';
+ default = "root";
+ };
+
+ encryption.mode = mkOption {
+ type = types.enum [
+ "repokey" "keyfile"
+ "repokey-blake2" "keyfile-blake2"
+ "authenticated" "authenticated-blake2"
+ "none"
+ ];
+ description = ''
+ Encryption mode to use. Setting a mode
+ other than <literal>"none"</literal> requires
+ you to specify a <option>passCommand</option>
+ or a <option>passphrase</option>.
+ '';
+ };
+
+ encryption.passCommand = mkOption {
+ type = with types; nullOr str;
+ description = ''
+ A command which prints the passphrase to stdout.
+ Mutually exclusive with <option>passphrase</option>.
+ '';
+ default = null;
+ example = "cat /path/to/passphrase_file";
+ };
+
+ encryption.passphrase = mkOption {
+ type = with types; nullOr str;
+ description = ''
+ The passphrase the backups are encrypted with.
+ Mutually exclusive with <option>passCommand</option>.
+ If you do not want the passphrase to be stored in the
+ world-readable Nix store, use <option>passCommand</option>.
+ '';
+ default = null;
+ };
+
+ compression = mkOption {
+ # "auto" is optional,
+ # compression mode must be given,
+ # compression level is optional
+ type = types.strMatching "none|(auto,)?(lz4|zstd|zlib|lzma)(,[[:digit:]]{1,2})?";
+ description = ''
+ Compression method to use. Refer to
+ <command>borg help compression</command>
+ for all available options.
+ '';
+ default = "lz4";
+ example = "auto,lzma";
+ };
+
+ exclude = mkOption {
+ type = with types; listOf str;
+ description = ''
+ Exclude paths matching any of the given patterns. See
+ <command>borg help patterns</command> for pattern syntax.
+ '';
+ default = [ ];
+ example = [
+ "/home/*/.cache"
+ "/nix"
+ ];
+ };
+
+ readWritePaths = mkOption {
+ type = with types; listOf path;
+ description = ''
+ By default, borg cannot write anywhere on the system but
+ <literal>$HOME/.config/borg</literal> and <literal>$HOME/.cache/borg</literal>.
+ If, for example, your preHook script needs to dump files
+ somewhere, put those directories here.
+ '';
+ default = [ ];
+ example = [
+ "/var/backup/mysqldump"
+ ];
+ };
+
+ privateTmp = mkOption {
+ type = types.bool;
+ description = ''
+ Set the <literal>PrivateTmp</literal> option for
+ the systemd-service. Set to false if you need sockets
+ or other files from global /tmp.
+ '';
+ default = true;
+ };
+
+ doInit = mkOption {
+ type = types.bool;
+ description = ''
+ Run <command>borg init</command> if the
+ specified <option>repo</option> does not exist.
+ You should set this to <literal>false</literal>
+ if the repository is located on an external drive
+ that might not always be mounted.
+ '';
+ default = true;
+ };
+
+ appendFailedSuffix = mkOption {
+ type = types.bool;
+ description = ''
+ Append a <literal>.failed</literal> suffix
+ to the archive name, which is only removed if
+ <command>borg create</command> has a zero exit status.
+ '';
+ default = true;
+ };
+
+ prune.keep = mkOption {
+ # Specifying e.g. `prune.keep.yearly = -1`
+ # means there is no limit of yearly archives to keep
+ # The regex is for use with e.g. --keep-within 1y
+ type = with types; attrsOf (either int (strMatching "[[:digit:]]+[Hdwmy]"));
+ description = ''
+ Prune a repository by deleting all archives not matching any of the
+ specified retention options. See <command>borg help prune</command>
+ for the available options.
+ '';
+ default = { };
+ example = literalExample ''
+ {
+ within = "1d"; # Keep all archives from the last day
+ daily = 7;
+ weekly = 4;
+ monthly = -1; # Keep at least one archive for each month
+ }
+ '';
+ };
+
+ prune.prefix = mkOption {
+ type = types.str;
+ description = ''
+ Only consider archive names starting with this prefix for pruning.
+ By default, only archives created by this job are considered.
+ Use <literal>""</literal> to consider all archives.
+ '';
+ default = config.archiveBaseName;
+ defaultText = "\${archiveBaseName}";
+ };
+
+ environment = mkOption {
+ type = with types; attrsOf str;
+ description = ''
+ Environment variables passed to the backup script.
+ You can for example specify which SSH key to use.
+ '';
+ default = { };
+ example = { BORG_RSH = "ssh -i /path/to/key"; };
+ };
+
+ preHook = mkOption {
+ type = types.lines;
+ description = ''
+ Shell commands to run before the backup.
+ This can for example be used to mount file systems.
+ '';
+ default = "";
+ example = ''
+ # To add excluded paths at runtime
+ extraCreateArgs="$extraCreateArgs --exclude /some/path"
+ '';
+ };
+
+ postInit = mkOption {
+ type = types.lines;
+ description = ''
+ Shell commands to run after <command>borg init</command>.
+ '';
+ default = "";
+ };
+
+ postCreate = mkOption {
+ type = types.lines;
+ description = ''
+ Shell commands to run after <command>borg create</command>. The name
+ of the created archive is stored in <literal>$archiveName</literal>.
+ '';
+ default = "";
+ };
+
+ postPrune = mkOption {
+ type = types.lines;
+ description = ''
+ Shell commands to run after <command>borg prune</command>.
+ '';
+ default = "";
+ };
+
+ postHook = mkOption {
+ type = types.lines;
+ description = ''
+ Shell commands to run just before exit. They are executed
+ even if a previous command exits with a non-zero exit code.
+ The latter is available as <literal>$exitStatus</literal>.
+ '';
+ default = "";
+ };
+
+ extraArgs = mkOption {
+ type = types.str;
+ description = ''
+ Additional arguments for all <command>borg</command> calls the
+ service has. Handle with care.
+ '';
+ default = "";
+ example = "--remote-path=/path/to/borg";
+ };
+
+ extraInitArgs = mkOption {
+ type = types.str;
+ description = ''
+ Additional arguments for <command>borg init</command>.
+ Can also be set at runtime using <literal>$extraInitArgs</literal>.
+ '';
+ default = "";
+ example = "--append-only";
+ };
+
+ extraCreateArgs = mkOption {
+ type = types.str;
+ description = ''
+ Additional arguments for <command>borg create</command>.
+ Can also be set at runtime using <literal>$extraCreateArgs</literal>.
+ '';
+ default = "";
+ example = "--stats --checkpoint-interval 600";
+ };
+
+ extraPruneArgs = mkOption {
+ type = types.str;
+ description = ''
+ Additional arguments for <command>borg prune</command>.
+ Can also be set at runtime using <literal>$extraPruneArgs</literal>.
+ '';
+ default = "";
+ example = "--save-space";
+ };
+
+ };
+ }
+ ));
+ };
+
+ options.services.borgbackup.repos = mkOption {
+ description = ''
+ Serve BorgBackup repositories to given public SSH keys,
+ restricting their access to the repository only.
+ Also, clients do not need to specify the absolute path when accessing the repository,
+ i.e. <literal>user@machine:.</literal> is enough. (Note colon and dot.)
+ '';
+ default = { };
+ type = types.attrsOf (types.submodule (
+ { ... }: {
+ options = {
+
+ path = mkOption {
+ type = types.path;
+ description = ''
+ Where to store the backups. Note that the directory
+ is created automatically, with correct permissions.
+ '';
+ default = "/var/lib/borgbackup";
+ };
+
+ user = mkOption {
+ type = types.str;
+ description = ''
+ The user <command>borg serve</command> is run as.
+ User or group needs write permission
+ for the specified <option>path</option>.
+ '';
+ default = "borg";
+ };
+
+ group = mkOption {
+ type = types.str;
+ description = ''
+ The group <command>borg serve</command> is run as.
+ User or group needs write permission
+ for the specified <option>path</option>.
+ '';
+ default = "borg";
+ };
+
+ authorizedKeys = mkOption {
+ type = with types; listOf str;
+ description = ''
+ Public SSH keys that are given full write access to this repository.
+ You should use a different SSH key for each repository you write to, because
+ the specified keys are restricted to running <command>borg serve</command>
+ and can only access this single repository.
+ '';
+ default = [ ];
+ };
+
+ authorizedKeysAppendOnly = mkOption {
+ type = with types; listOf str;
+ description = ''
+ Public SSH keys that can only be used to append new data (archives) to the repository.
+ Note that archives can still be marked as deleted and are subsequently removed from disk
+ upon accessing the repo with full write access, e.g. when pruning.
+ '';
+ default = [ ];
+ };
+
+ allowSubRepos = mkOption {
+ type = types.bool;
+ description = ''
+ Allow clients to create repositories in subdirectories of the
+ specified <option>path</option>. These can be accessed using
+ <literal>user@machine:path/to/subrepo</literal>. Note that a
+ <option>quota</option> applies to repositories independently.
+ Therefore, if this is enabled, clients can create multiple
+ repositories and upload an arbitrary amount of data.
+ '';
+ default = false;
+ };
+
+ quota = mkOption {
+ # See the definition of parse_file_size() in src/borg/helpers/parseformat.py
+ type = with types; nullOr (strMatching "[[:digit:].]+[KMGTP]?");
+ description = ''
+ Storage quota for the repository. This quota is ensured for all
+ sub-repositories if <option>allowSubRepos</option> is enabled
+ but not for the overall storage space used.
+ '';
+ default = null;
+ example = "100G";
+ };
+
+ };
+ }
+ ));
+ };
+
+ ###### implementation
+
+ config = mkIf (with config.services.borgbackup; jobs != { } || repos != { })
+ (with config.services.borgbackup; {
+ assertions =
+ mapAttrsToList mkPassAssertion jobs
+ ++ mapAttrsToList mkKeysAssertion repos;
+
+ system.activationScripts = mapAttrs' mkActivationScript jobs;
+
+ systemd.services =
+ # A job named "foo" is mapped to systemd.services.borgbackup-job-foo
+ mapAttrs' mkBackupService jobs
+ # A repo named "foo" is mapped to systemd.services.borgbackup-repo-foo
+ // mapAttrs' mkRepoService repos;
+
+ users = mkMerge (mapAttrsToList mkUsersConfig repos);
+
+ environment.systemPackages = with pkgs; [ borgbackup ];
+ });
+}
diff --git a/nixpkgs/nixos/modules/services/backup/duplicati.nix b/nixpkgs/nixos/modules/services/backup/duplicati.nix
new file mode 100644
index 00000000000..0ff720c5897
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/duplicati.nix
@@ -0,0 +1,67 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.duplicati;
+in
+{
+ options = {
+ services.duplicati = {
+ enable = mkEnableOption "Duplicati";
+
+ port = mkOption {
+ default = 8200;
+ type = types.int;
+ description = ''
+ Port serving the web interface
+ '';
+ };
+
+ interface = mkOption {
+ default = "127.0.0.1";
+ type = types.str;
+ description = ''
+ Listening interface for the web UI
+ Set it to "any" to listen on all available interfaces
+ '';
+ };
+
+ user = mkOption {
+ default = "duplicati";
+ type = types.str;
+ description = ''
+ Duplicati runs as it's own user. It will only be able to backup world-readable files.
+ Run as root with special care.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.duplicati ];
+
+ systemd.services.duplicati = {
+ description = "Duplicati backup";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = cfg.user;
+ Group = "duplicati";
+ StateDirectory = "duplicati";
+ ExecStart = "${pkgs.duplicati}/bin/duplicati-server --webservice-interface=${cfg.interface} --webservice-port=${toString cfg.port} --server-datafolder=/var/lib/duplicati";
+ Restart = "on-failure";
+ };
+ };
+
+ users.users.duplicati = lib.optionalAttrs (cfg.user == "duplicati") {
+ uid = config.ids.uids.duplicati;
+ home = "/var/lib/duplicati";
+ createHome = true;
+ group = "duplicati";
+ };
+ users.groups.duplicati.gid = config.ids.gids.duplicati;
+
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/backup/duplicity.nix b/nixpkgs/nixos/modules/services/backup/duplicity.nix
new file mode 100644
index 00000000000..a8d56424862
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/duplicity.nix
@@ -0,0 +1,141 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+ cfg = config.services.duplicity;
+
+ stateDirectory = "/var/lib/duplicity";
+
+ localTarget = if hasPrefix "file://" cfg.targetUrl
+ then removePrefix "file://" cfg.targetUrl else null;
+
+in {
+ options.services.duplicity = {
+ enable = mkEnableOption "backups with duplicity";
+
+ root = mkOption {
+ type = types.path;
+ default = "/";
+ description = ''
+ Root directory to backup.
+ '';
+ };
+
+ include = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "/home" ];
+ description = ''
+ List of paths to include into the backups. See the FILE SELECTION
+ section in <citerefentry><refentrytitle>duplicity</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> for details on the syntax.
+ '';
+ };
+
+ exclude = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ List of paths to exclude from backups. See the FILE SELECTION section in
+ <citerefentry><refentrytitle>duplicity</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> for details on the syntax.
+ '';
+ };
+
+ targetUrl = mkOption {
+ type = types.str;
+ example = "s3://host:port/prefix";
+ description = ''
+ Target url to backup to. See the URL FORMAT section in
+ <citerefentry><refentrytitle>duplicity</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> for supported urls.
+ '';
+ };
+
+ secretFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Path of a file containing secrets (gpg passphrase, access key...) in
+ the format of EnvironmentFile as described by
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry>. For example:
+ <programlisting>
+ PASSPHRASE=<replaceable>...</replaceable>
+ AWS_ACCESS_KEY_ID=<replaceable>...</replaceable>
+ AWS_SECRET_ACCESS_KEY=<replaceable>...</replaceable>
+ </programlisting>
+ '';
+ };
+
+ frequency = mkOption {
+ type = types.nullOr types.str;
+ default = "daily";
+ description = ''
+ Run duplicity with the given frequency (see
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry> for the format).
+ If null, do not run automatically.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "--full-if-older-than" "1M" ];
+ description = ''
+ Extra command-line flags passed to duplicity. See
+ <citerefentry><refentrytitle>duplicity</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd = {
+ services.duplicity = {
+ description = "backup files with duplicity";
+
+ environment.HOME = stateDirectory;
+
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.duplicity}/bin/duplicity ${escapeShellArgs (
+ [
+ cfg.root
+ cfg.targetUrl
+ "--archive-dir" stateDirectory
+ ]
+ ++ concatMap (p: [ "--include" p ]) cfg.include
+ ++ concatMap (p: [ "--exclude" p ]) cfg.exclude
+ ++ cfg.extraFlags)}
+ '';
+ PrivateTmp = true;
+ ProtectSystem = "strict";
+ ProtectHome = "read-only";
+ StateDirectory = baseNameOf stateDirectory;
+ } // optionalAttrs (localTarget != null) {
+ ReadWritePaths = localTarget;
+ } // optionalAttrs (cfg.secretFile != null) {
+ EnvironmentFile = cfg.secretFile;
+ };
+ } // optionalAttrs (cfg.frequency != null) {
+ startAt = cfg.frequency;
+ };
+
+ tmpfiles.rules = optional (localTarget != null) "d ${localTarget} 0700 root root -";
+ };
+
+ assertions = singleton {
+ # Duplicity will fail if the last file selection option is an include. It
+ # is not always possible to detect but this simple case can be caught.
+ assertion = cfg.include != [] -> cfg.exclude != [] || cfg.extraFlags != [];
+ message = ''
+ Duplicity will fail if you only specify included paths ("Because the
+ default is to include all files, the expression is redundant. Exiting
+ because this probably isn't what you meant.")
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/mysql-backup.nix b/nixpkgs/nixos/modules/services/backup/mysql-backup.nix
new file mode 100644
index 00000000000..dbd5605143f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/mysql-backup.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs) mysql gzip;
+
+ cfg = config.services.mysqlBackup;
+ defaultUser = "mysqlbackup";
+
+ backupScript = ''
+ set -o pipefail
+ failed=""
+ ${concatMapStringsSep "\n" backupDatabaseScript cfg.databases}
+ if [ -n "$failed" ]; then
+ echo "Backup of database(s) failed:$failed"
+ exit 1
+ fi
+ '';
+ backupDatabaseScript = db: ''
+ dest="${cfg.location}/${db}.gz"
+ if ${mysql}/bin/mysqldump ${if cfg.singleTransaction then "--single-transaction" else ""} ${db} | ${gzip}/bin/gzip -c > $dest.tmp; then
+ mv $dest.tmp $dest
+ echo "Backed up to $dest"
+ else
+ echo "Failed to back up to $dest"
+ rm -f $dest.tmp
+ failed="$failed ${db}"
+ fi
+ '';
+
+in
+
+{
+ options = {
+
+ services.mysqlBackup = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable MySQL backups.
+ '';
+ };
+
+ calendar = mkOption {
+ type = types.str;
+ default = "01:15:00";
+ description = ''
+ Configured when to run the backup service systemd unit (DayOfWeek Year-Month-Day Hour:Minute:Second).
+ '';
+ };
+
+ user = mkOption {
+ default = defaultUser;
+ description = ''
+ User to be used to perform backup.
+ '';
+ };
+
+ databases = mkOption {
+ default = [];
+ description = ''
+ List of database names to dump.
+ '';
+ };
+
+ location = mkOption {
+ default = "/var/backup/mysql";
+ description = ''
+ Location to put the gzipped MySQL database dumps.
+ '';
+ };
+
+ singleTransaction = mkOption {
+ default = false;
+ description = ''
+ Whether to create database dump in a single transaction
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ users.users = optionalAttrs (cfg.user == defaultUser) (singleton
+ { name = defaultUser;
+ isSystemUser = true;
+ createHome = false;
+ home = cfg.location;
+ group = "nogroup";
+ });
+
+ services.mysql.ensureUsers = [{
+ name = cfg.user;
+ ensurePermissions = with lib;
+ let
+ privs = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES";
+ grant = db: nameValuePair "${db}.*" privs;
+ in
+ listToAttrs (map grant cfg.databases);
+ }];
+
+ systemd = {
+ timers.mysql-backup = {
+ description = "Mysql backup timer";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = cfg.calendar;
+ AccuracySec = "5m";
+ Unit = "mysql-backup.service";
+ };
+ };
+ services.mysql-backup = {
+ description = "Mysql backup service";
+ enable = true;
+ serviceConfig = {
+ User = cfg.user;
+ };
+ script = backupScript;
+ };
+ tmpfiles.rules = [
+ "d ${cfg.location} 0700 ${cfg.user} - - -"
+ ];
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/backup/postgresql-backup.nix b/nixpkgs/nixos/modules/services/backup/postgresql-backup.nix
new file mode 100644
index 00000000000..13a36ae32ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/postgresql-backup.nix
@@ -0,0 +1,124 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.postgresqlBackup;
+
+ postgresqlBackupService = db: dumpCmd:
+ {
+ enable = true;
+
+ description = "Backup of ${db} database(s)";
+
+ requires = [ "postgresql.service" ];
+
+ script = ''
+ umask 0077 # ensure backup is only readable by postgres user
+
+ if [ -e ${cfg.location}/${db}.sql.gz ]; then
+ ${pkgs.coreutils}/bin/mv ${cfg.location}/${db}.sql.gz ${cfg.location}/${db}.prev.sql.gz
+ fi
+
+ ${dumpCmd} | \
+ ${pkgs.gzip}/bin/gzip -c > ${cfg.location}/${db}.sql.gz
+ '';
+
+ serviceConfig = {
+ Type = "oneshot";
+ User = "postgres";
+ };
+
+ startAt = cfg.startAt;
+ };
+
+in {
+
+ options = {
+ services.postgresqlBackup = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable PostgreSQL dumps.
+ '';
+ };
+
+ startAt = mkOption {
+ default = "*-*-* 01:15:00";
+ description = ''
+ This option defines (see <literal>systemd.time</literal> for format) when the
+ databases should be dumped.
+ The default is to update at 01:15 (at night) every day.
+ '';
+ };
+
+ backupAll = mkOption {
+ default = cfg.databases == [];
+ defaultText = "services.postgresqlBackup.databases == []";
+ type = lib.types.bool;
+ description = ''
+ Backup all databases using pg_dumpall.
+ This option is mutual exclusive to
+ <literal>services.postgresqlBackup.databases</literal>.
+ The resulting backup dump will have the name all.sql.gz.
+ This option is the default if no databases are specified.
+ '';
+ };
+
+ databases = mkOption {
+ default = [];
+ description = ''
+ List of database names to dump.
+ '';
+ };
+
+ location = mkOption {
+ default = "/var/backup/postgresql";
+ description = ''
+ Location to put the gzipped PostgreSQL database dumps.
+ '';
+ };
+
+ pgdumpOptions = mkOption {
+ type = types.separatedString " ";
+ default = "-Cbo";
+ description = ''
+ Command line options for pg_dump. This options is not used
+ if <literal>config.services.postgresqlBackup.backupAll</literal> is enabled.
+ Note that config.services.postgresqlBackup.backupAll is also active,
+ when no databases where specified.
+ '';
+ };
+ };
+
+ };
+
+ config = mkMerge [
+ {
+ assertions = [{
+ assertion = cfg.backupAll -> cfg.databases == [];
+ message = "config.services.postgresqlBackup.backupAll cannot be used together with config.services.postgresqlBackup.databases";
+ }];
+ }
+ (mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.location}' 0700 postgres - - -"
+ ];
+ })
+ (mkIf (cfg.enable && cfg.backupAll) {
+ systemd.services.postgresqlBackup =
+ postgresqlBackupService "all" "${config.services.postgresql.package}/bin/pg_dumpall";
+ })
+ (mkIf (cfg.enable && !cfg.backupAll) {
+ systemd.services = listToAttrs (map (db:
+ let
+ cmd = "${config.services.postgresql.package}/bin/pg_dump ${cfg.pgdumpOptions} ${db}";
+ in {
+ name = "postgresqlBackup-${db}";
+ value = postgresqlBackupService db cmd;
+ }) cfg.databases);
+ })
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/backup/postgresql-wal-receiver.nix b/nixpkgs/nixos/modules/services/backup/postgresql-wal-receiver.nix
new file mode 100644
index 00000000000..3d9869d5343
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/postgresql-wal-receiver.nix
@@ -0,0 +1,204 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ receiverSubmodule = {
+ options = {
+ postgresqlPackage = mkOption {
+ type = types.package;
+ example = literalExample "pkgs.postgresql_11";
+ description = ''
+ PostgreSQL package to use.
+ '';
+ };
+
+ directory = mkOption {
+ type = types.path;
+ example = literalExample "/mnt/pg_wal/main/";
+ description = ''
+ Directory to write the output to.
+ '';
+ };
+
+ statusInterval = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ Specifies the number of seconds between status packets sent back to the server.
+ This allows for easier monitoring of the progress from server.
+ A value of zero disables the periodic status updates completely,
+ although an update will still be sent when requested by the server, to avoid timeout disconnect.
+ '';
+ };
+
+ slot = mkOption {
+ type = types.str;
+ default = "";
+ example = "some_slot_name";
+ description = ''
+ Require <command>pg_receivewal</command> to use an existing replication slot (see
+ <link xlink:href="https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS">Section 26.2.6 of the PostgreSQL manual</link>).
+ When this option is used, <command>pg_receivewal</command> will report a flush position to the server,
+ indicating when each segment has been synchronized to disk so that the server can remove that segment if it is not otherwise needed.
+
+ When the replication client of <command>pg_receivewal</command> is configured on the server as a synchronous standby,
+ then using a replication slot will report the flush position to the server, but only when a WAL file is closed.
+ Therefore, that configuration will cause transactions on the primary to wait for a long time and effectively not work satisfactorily.
+ The option <option>synchronous</option> must be specified in addition to make this work correctly.
+ '';
+ };
+
+ synchronous = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Flush the WAL data to disk immediately after it has been received.
+ Also send a status packet back to the server immediately after flushing, regardless of <option>statusInterval</option>.
+
+ This option should be specified if the replication client of <command>pg_receivewal</command> is configured on the server as a synchronous standby,
+ to ensure that timely feedback is sent to the server.
+ '';
+ };
+
+ compress = mkOption {
+ type = types.ints.between 0 9;
+ default = 0;
+ description = ''
+ Enables gzip compression of write-ahead logs, and specifies the compression level
+ (<literal>0</literal> through <literal>9</literal>, <literal>0</literal> being no compression and <literal>9</literal> being best compression).
+ The suffix <literal>.gz</literal> will automatically be added to all filenames.
+
+ This option requires PostgreSQL >= 10.
+ '';
+ };
+
+ connection = mkOption {
+ type = types.str;
+ example = "postgresql://user@somehost";
+ description = ''
+ Specifies parameters used to connect to the server, as a connection string.
+ See <link xlink:href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING">Section 34.1.1 of the PostgreSQL manual</link> for more information.
+
+ Because <command>pg_receivewal</command> doesn't connect to any particular database in the cluster,
+ database name in the connection string will be ignored.
+ '';
+ };
+
+ extraArgs = mkOption {
+ type = with types; listOf str;
+ default = [ ];
+ example = literalExample ''
+ [
+ "--no-sync"
+ ]
+ '';
+ description = ''
+ A list of extra arguments to pass to the <command>pg_receivewal</command> command.
+ '';
+ };
+
+ environment = mkOption {
+ type = with types; attrsOf str;
+ default = { };
+ example = literalExample ''
+ {
+ PGPASSFILE = "/private/passfile";
+ PGSSLMODE = "require";
+ }
+ '';
+ description = ''
+ Environment variables passed to the service.
+ Usable parameters are listed in <link xlink:href="https://www.postgresql.org/docs/current/libpq-envars.html">Section 34.14 of the PostgreSQL manual</link>.
+ '';
+ };
+ };
+ };
+
+in {
+ options = {
+ services.postgresqlWalReceiver = {
+ receivers = mkOption {
+ type = with types; attrsOf (submodule receiverSubmodule);
+ default = { };
+ example = literalExample ''
+ {
+ main = {
+ postgresqlPackage = pkgs.postgresql_11;
+ directory = /mnt/pg_wal/main/;
+ slot = "main_wal_receiver";
+ connection = "postgresql://user@somehost";
+ };
+ }
+ '';
+ description = ''
+ PostgreSQL WAL receivers.
+ Stream write-ahead logs from a PostgreSQL server using <command>pg_receivewal</command> (formerly <command>pg_receivexlog</command>).
+ See <link xlink:href="https://www.postgresql.org/docs/current/app-pgreceivewal.html">the man page</link> for more information.
+ '';
+ };
+ };
+ };
+
+ config = let
+ receivers = config.services.postgresqlWalReceiver.receivers;
+ in mkIf (receivers != { }) {
+ users = {
+ users.postgres = {
+ uid = config.ids.uids.postgres;
+ group = "postgres";
+ description = "PostgreSQL server user";
+ };
+
+ groups.postgres = {
+ gid = config.ids.gids.postgres;
+ };
+ };
+
+ assertions = concatLists (attrsets.mapAttrsToList (name: config: [
+ {
+ assertion = config.compress > 0 -> versionAtLeast config.postgresqlPackage.version "10";
+ message = "Invalid configuration for WAL receiver \"${name}\": compress requires PostgreSQL version >= 10.";
+ }
+ ]) receivers);
+
+ systemd.tmpfiles.rules = mapAttrsToList (name: config: ''
+ d ${escapeShellArg config.directory} 0750 postgres postgres - -
+ '') receivers;
+
+ systemd.services = with attrsets; mapAttrs' (name: config: nameValuePair "postgresql-wal-receiver-${name}" {
+ description = "PostgreSQL WAL receiver (${name})";
+ wantedBy = [ "multi-user.target" ];
+ startLimitIntervalSec = 0; # retry forever, useful in case of network disruption
+
+ serviceConfig = {
+ User = "postgres";
+ Group = "postgres";
+ KillSignal = "SIGINT";
+ Restart = "always";
+ RestartSec = 60;
+ };
+
+ inherit (config) environment;
+
+ script = let
+ receiverCommand = postgresqlPackage:
+ if (versionAtLeast postgresqlPackage.version "10")
+ then "${postgresqlPackage}/bin/pg_receivewal"
+ else "${postgresqlPackage}/bin/pg_receivexlog";
+ in ''
+ ${receiverCommand config.postgresqlPackage} \
+ --no-password \
+ --directory=${escapeShellArg config.directory} \
+ --status-interval=${toString config.statusInterval} \
+ --dbname=${escapeShellArg config.connection} \
+ ${optionalString (config.compress > 0) "--compress=${toString config.compress}"} \
+ ${optionalString (config.slot != "") "--slot=${escapeShellArg config.slot}"} \
+ ${optionalString config.synchronous "--synchronous"} \
+ ${concatStringsSep " " config.extraArgs}
+ '';
+ }) receivers;
+ };
+
+ meta.maintainers = with maintainers; [ pacien ];
+}
diff --git a/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix b/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix
new file mode 100644
index 00000000000..d1b775f150d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.restic.server;
+in
+{
+ meta.maintainers = [ maintainers.bachp ];
+
+ options.services.restic.server = {
+ enable = mkEnableOption "Restic REST Server";
+
+ listenAddress = mkOption {
+ default = ":8000";
+ example = "127.0.0.1:8080";
+ type = types.str;
+ description = "Listen on a specific IP address and port.";
+ };
+
+ dataDir = mkOption {
+ default = "/var/lib/restic";
+ type = types.path;
+ description = "The directory for storing the restic repository.";
+ };
+
+ appendOnly = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable append only mode.
+ This mode allows creation of new backups but prevents deletion and modification of existing backups.
+ This can be useful when backing up systems that have a potential of being hacked.
+ '';
+ };
+
+ privateRepos = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable private repos.
+ Grants access only when a subdirectory with the same name as the user is specified in the repository URL.
+ '';
+ };
+
+ prometheus = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Enable Prometheus metrics at /metrics.";
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Extra commandline options to pass to Restic REST server.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.restic-rest-server;
+ defaultText = "pkgs.restic-rest-server";
+ type = types.package;
+ description = "Restic REST server package to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.restic-rest-server = {
+ description = "Restic REST Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${cfg.package}/bin/rest-server \
+ --listen ${cfg.listenAddress} \
+ --path ${cfg.dataDir} \
+ ${optionalString cfg.appendOnly "--append-only"} \
+ ${optionalString cfg.privateRepos "--private-repos"} \
+ ${optionalString cfg.prometheus "--prometheus"} \
+ ${escapeShellArgs cfg.extraFlags} \
+ '';
+ Type = "simple";
+ User = "restic";
+ Group = "restic";
+
+ # Security hardening
+ ReadWritePaths = [ cfg.dataDir ];
+ PrivateTmp = true;
+ ProtectSystem = "strict";
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+ PrivateDevices = true;
+ };
+ };
+
+ users.users.restic = {
+ group = "restic";
+ home = cfg.dataDir;
+ createHome = true;
+ uid = config.ids.uids.restic;
+ };
+
+ users.groups.restic.gid = config.ids.uids.restic;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/restic.nix b/nixpkgs/nixos/modules/services/backup/restic.nix
new file mode 100644
index 00000000000..7e8e91e4b9c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/restic.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ # Type for a valid systemd unit option. Needed for correctly passing "timerConfig" to "systemd.timers"
+ unitOption = (import ../../system/boot/systemd-unit-options.nix { inherit config lib; }).unitOption;
+in
+{
+ options.services.restic.backups = mkOption {
+ description = ''
+ Periodic backups to create with Restic.
+ '';
+ type = types.attrsOf (types.submodule ({ name, ... }: {
+ options = {
+ passwordFile = mkOption {
+ type = types.str;
+ description = ''
+ Read the repository password from a file.
+ '';
+ example = "/etc/nixos/restic-password";
+ };
+
+ s3CredentialsFile = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ file containing the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
+ for an S3-hosted repository, in the format of an EnvironmentFile
+ as described by systemd.exec(5)
+ '';
+ };
+
+ repository = mkOption {
+ type = types.str;
+ description = ''
+ repository to backup to.
+ '';
+ example = "sftp:backup@192.168.1.100:/backups/${name}";
+ };
+
+ paths = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Which paths to backup.
+ '';
+ example = [
+ "/var/lib/postgresql"
+ "/home/user/backup"
+ ];
+ };
+
+ timerConfig = mkOption {
+ type = types.attrsOf unitOption;
+ default = {
+ OnCalendar = "daily";
+ };
+ description = ''
+ When to run the backup. See man systemd.timer for details.
+ '';
+ example = {
+ OnCalendar = "00:05";
+ RandomizedDelaySec = "5h";
+ };
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "root";
+ description = ''
+ As which user the backup should run.
+ '';
+ example = "postgresql";
+ };
+
+ extraBackupArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Extra arguments passed to restic backup.
+ '';
+ example = [
+ "--exclude-file=/etc/nixos/restic-ignore"
+ ];
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Extra extended options to be passed to the restic --option flag.
+ '';
+ example = [
+ "sftp.command='ssh backup@192.168.1.100 -i /home/user/.ssh/id_rsa -s sftp'"
+ ];
+ };
+
+ initialize = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Create the repository if it doesn't exist.
+ '';
+ };
+ };
+ }));
+ default = {};
+ example = {
+ localbackup = {
+ paths = [ "/home" ];
+ repository = "/mnt/backup-hdd";
+ passwordFile = "/etc/nixos/secrets/restic-password";
+ initialize = true;
+ };
+ remotebackup = {
+ paths = [ "/home" ];
+ repository = "sftp:backup@host:/backups/home";
+ passwordFile = "/etc/nixos/secrets/restic-password";
+ extraOptions = [
+ "sftp.command='ssh backup@host -i /etc/nixos/secrets/backup-private-key -s sftp'"
+ ];
+ timerConfig = {
+ OnCalendar = "00:05";
+ RandomizedDelaySec = "5h";
+ };
+ };
+ };
+ };
+
+ config = {
+ systemd.services =
+ mapAttrs' (name: backup:
+ let
+ extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
+ resticCmd = "${pkgs.restic}/bin/restic${extraOptions}";
+ in nameValuePair "restic-backups-${name}" ({
+ environment = {
+ RESTIC_PASSWORD_FILE = backup.passwordFile;
+ RESTIC_REPOSITORY = backup.repository;
+ };
+ path = with pkgs; [
+ openssh
+ ];
+ restartIfChanged = false;
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${resticCmd} backup ${concatStringsSep " " backup.extraBackupArgs} ${concatStringsSep " " backup.paths}";
+ User = backup.user;
+ } // optionalAttrs (backup.s3CredentialsFile != null) {
+ EnvironmentFile = backup.s3CredentialsFile;
+ };
+ } // optionalAttrs backup.initialize {
+ preStart = ''
+ ${resticCmd} snapshots || ${resticCmd} init
+ '';
+ })
+ ) config.services.restic.backups;
+ systemd.timers =
+ mapAttrs' (name: backup: nameValuePair "restic-backups-${name}" {
+ wantedBy = [ "timers.target" ];
+ timerConfig = backup.timerConfig;
+ }) config.services.restic.backups;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/rsnapshot.nix b/nixpkgs/nixos/modules/services/backup/rsnapshot.nix
new file mode 100644
index 00000000000..6635a51ec2c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/rsnapshot.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.rsnapshot;
+ cfgfile = pkgs.writeText "rsnapshot.conf" ''
+ config_version 1.2
+ cmd_cp ${pkgs.coreutils}/bin/cp
+ cmd_rm ${pkgs.coreutils}/bin/rm
+ cmd_rsync ${pkgs.rsync}/bin/rsync
+ cmd_ssh ${pkgs.openssh}/bin/ssh
+ cmd_logger ${pkgs.inetutils}/bin/logger
+ cmd_du ${pkgs.coreutils}/bin/du
+ cmd_rsnapshot_diff ${pkgs.rsnapshot}/bin/rsnapshot-diff
+ lockfile /run/rsnapshot.pid
+ link_dest 1
+
+ ${cfg.extraConfig}
+ '';
+in
+{
+ options = {
+ services.rsnapshot = {
+ enable = mkEnableOption "rsnapshot backups";
+ enableManualRsnapshot = mkOption {
+ description = "Whether to enable manual usage of the rsnapshot command with this module.";
+ default = true;
+ type = types.bool;
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ example = ''
+ retains hourly 24
+ retain daily 365
+ backup /home/ localhost/
+ '';
+ type = types.lines;
+ description = ''
+ rsnapshot configuration option in addition to the defaults from
+ rsnapshot and this module.
+
+ Note that tabs are required to separate option arguments, and
+ directory names require trailing slashes.
+
+ The "extra" in the option name might be a little misleading right
+ now, as it is required to get a functional configuration.
+ '';
+ };
+
+ cronIntervals = mkOption {
+ default = {};
+ example = { hourly = "0 * * * *"; daily = "50 21 * * *"; };
+ type = types.attrsOf types.str;
+ description = ''
+ Periodicity at which intervals should be run by cron.
+ Note that the intervals also have to exist in configuration
+ as retain options.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ services.cron.systemCronJobs =
+ mapAttrsToList (interval: time: "${time} root ${pkgs.rsnapshot}/bin/rsnapshot -c ${cfgfile} ${interval}") cfg.cronIntervals;
+ }
+ (mkIf cfg.enableManualRsnapshot {
+ environment.systemPackages = [ pkgs.rsnapshot ];
+ environment.etc."rsnapshot.conf".source = cfgfile;
+ })
+ ]);
+}
diff --git a/nixpkgs/nixos/modules/services/backup/tarsnap.nix b/nixpkgs/nixos/modules/services/backup/tarsnap.nix
new file mode 100644
index 00000000000..4fc7c24813a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/tarsnap.nix
@@ -0,0 +1,402 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+ gcfg = config.services.tarsnap;
+
+ configFile = name: cfg: ''
+ keyfile ${cfg.keyfile}
+ ${optionalString (cfg.cachedir != null) "cachedir ${cfg.cachedir}"}
+ ${optionalString cfg.nodump "nodump"}
+ ${optionalString cfg.printStats "print-stats"}
+ ${optionalString cfg.printStats "humanize-numbers"}
+ ${optionalString (cfg.checkpointBytes != null) ("checkpoint-bytes "+cfg.checkpointBytes)}
+ ${optionalString cfg.aggressiveNetworking "aggressive-networking"}
+ ${concatStringsSep "\n" (map (v: "exclude ${v}") cfg.excludes)}
+ ${concatStringsSep "\n" (map (v: "include ${v}") cfg.includes)}
+ ${optionalString cfg.lowmem "lowmem"}
+ ${optionalString cfg.verylowmem "verylowmem"}
+ ${optionalString (cfg.maxbw != null) "maxbw ${toString cfg.maxbw}"}
+ ${optionalString (cfg.maxbwRateUp != null) "maxbw-rate-up ${toString cfg.maxbwRateUp}"}
+ ${optionalString (cfg.maxbwRateDown != null) "maxbw-rate-down ${toString cfg.maxbwRateDown}"}
+ '';
+in
+{
+ options = {
+ services.tarsnap = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable periodic tarsnap backups.
+ '';
+ };
+
+ keyfile = mkOption {
+ type = types.str;
+ default = "/root/tarsnap.key";
+ description = ''
+ The keyfile which associates this machine with your tarsnap
+ account.
+ Create the keyfile with <command>tarsnap-keygen</command>.
+
+ Note that each individual archive (specified below) may also have its
+ own individual keyfile specified. Tarsnap does not allow multiple
+ concurrent backups with the same cache directory and key (starting a
+ new backup will cause another one to fail). If you have multiple
+ archives specified, you should either spread out your backups to be
+ far apart, or specify a separate key for each archive. By default
+ every archive defaults to using
+ <literal>"/root/tarsnap.key"</literal>.
+
+ It's recommended for backups that you generate a key for every archive
+ using <literal>tarsnap-keygen(1)</literal>, and then generate a
+ write-only tarsnap key using <literal>tarsnap-keymgmt(1)</literal>,
+ and keep your master key(s) for a particular machine off-site.
+
+ The keyfile name should be given as a string and not a path, to
+ avoid the key being copied into the Nix store.
+ '';
+ };
+
+ archives = mkOption {
+ type = types.attrsOf (types.submodule ({ config, ... }:
+ {
+ options = {
+ keyfile = mkOption {
+ type = types.str;
+ default = gcfg.keyfile;
+ description = ''
+ Set a specific keyfile for this archive. This defaults to
+ <literal>"/root/tarsnap.key"</literal> if left unspecified.
+
+ Use this option if you want to run multiple backups
+ concurrently - each archive must have a unique key. You can
+ generate a write-only key derived from your master key (which
+ is recommended) using <literal>tarsnap-keymgmt(1)</literal>.
+
+ Note: every archive must have an individual master key. You
+ must generate multiple keys with
+ <literal>tarsnap-keygen(1)</literal>, and then generate write
+ only keys from those.
+
+ The keyfile name should be given as a string and not a path, to
+ avoid the key being copied into the Nix store.
+ '';
+ };
+
+ cachedir = mkOption {
+ type = types.nullOr types.path;
+ default = "/var/cache/tarsnap/${utils.escapeSystemdPath config.keyfile}";
+ description = ''
+ The cache allows tarsnap to identify previously stored data
+ blocks, reducing archival time and bandwidth usage.
+
+ Should the cache become desynchronized or corrupted, tarsnap
+ will refuse to run until you manually rebuild the cache with
+ <command>tarsnap --fsck</command>.
+
+ Set to <literal>null</literal> to disable caching.
+ '';
+ };
+
+ nodump = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Exclude files with the <literal>nodump</literal> flag.
+ '';
+ };
+
+ printStats = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Print global archive statistics upon completion.
+ The output is available via
+ <command>systemctl status tarsnap-archive-name</command>.
+ '';
+ };
+
+ checkpointBytes = mkOption {
+ type = types.nullOr types.str;
+ default = "1GB";
+ description = ''
+ Create a checkpoint every <literal>checkpointBytes</literal>
+ of uploaded data (optionally specified using an SI prefix).
+
+ 1GB is the minimum value. A higher value is recommended,
+ as checkpointing is expensive.
+
+ Set to <literal>null</literal> to disable checkpointing.
+ '';
+ };
+
+ period = mkOption {
+ type = types.str;
+ default = "01:15";
+ example = "hourly";
+ description = ''
+ Create archive at this interval.
+
+ The format is described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ '';
+ };
+
+ aggressiveNetworking = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Upload data over multiple TCP connections, potentially
+ increasing tarsnap's bandwidth utilisation at the cost
+ of slowing down all other network traffic. Not
+ recommended unless TCP congestion is the dominant
+ limiting factor.
+ '';
+ };
+
+ directories = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = "List of filesystem paths to archive.";
+ };
+
+ excludes = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Exclude files and directories matching these patterns.
+ '';
+ };
+
+ includes = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Include only files and directories matching these
+ patterns (the empty list includes everything).
+
+ Exclusions have precedence over inclusions.
+ '';
+ };
+
+ lowmem = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Reduce memory consumption by not caching small files.
+ Possibly beneficial if the average file size is smaller
+ than 1 MB and the number of files is lower than the
+ total amount of RAM in KB.
+ '';
+ };
+
+ verylowmem = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Reduce memory consumption by a factor of 2 beyond what
+ <literal>lowmem</literal> does, at the cost of significantly
+ slowing down the archiving process.
+ '';
+ };
+
+ maxbw = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Abort archival if upstream bandwidth usage in bytes
+ exceeds this threshold.
+ '';
+ };
+
+ maxbwRateUp = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = literalExample "25 * 1000";
+ description = ''
+ Upload bandwidth rate limit in bytes.
+ '';
+ };
+
+ maxbwRateDown = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = literalExample "50 * 1000";
+ description = ''
+ Download bandwidth rate limit in bytes.
+ '';
+ };
+
+ verbose = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to produce verbose logging output.
+ '';
+ };
+ explicitSymlinks = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to follow symlinks specified as archives.
+ '';
+ };
+ followSymlinks = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to follow all symlinks in archive trees.
+ '';
+ };
+ };
+ }
+ ));
+
+ default = {};
+
+ example = literalExample ''
+ {
+ nixos =
+ { directories = [ "/home" "/root/ssl" ];
+ };
+
+ gamedata =
+ { directories = [ "/var/lib/minecraft" ];
+ period = "*:30";
+ };
+ }
+ '';
+
+ description = ''
+ Tarsnap archive configurations. Each attribute names an archive
+ to be created at a given time interval, according to the options
+ associated with it. When uploading to the tarsnap server,
+ archive names are suffixed by a 1 second resolution timestamp.
+
+ For each member of the set is created a timer which triggers the
+ instanced <literal>tarsnap-archive-name</literal> service unit. You may use
+ <command>systemctl start tarsnap-archive-name</command> to
+ manually trigger creation of <literal>archive-name</literal> at
+ any time.
+ '';
+ };
+ };
+ };
+
+ config = mkIf gcfg.enable {
+ assertions =
+ (mapAttrsToList (name: cfg:
+ { assertion = cfg.directories != [];
+ message = "Must specify paths for tarsnap to back up";
+ }) gcfg.archives) ++
+ (mapAttrsToList (name: cfg:
+ { assertion = !(cfg.lowmem && cfg.verylowmem);
+ message = "You cannot set both lowmem and verylowmem";
+ }) gcfg.archives);
+
+ systemd.services =
+ (mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}" {
+ description = "Tarsnap archive '${name}'";
+ requires = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+
+ path = with pkgs; [ iputils tarsnap utillinux ];
+
+ # In order for the persistent tarsnap timer to work reliably, we have to
+ # make sure that the tarsnap server is reachable after systemd starts up
+ # the service - therefore we sleep in a loop until we can ping the
+ # endpoint.
+ preStart = ''
+ while ! ping -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done
+ '';
+
+ script = let
+ tarsnap = ''tarsnap --configfile "/etc/tarsnap/${name}.conf"'';
+ run = ''${tarsnap} -c -f "${name}-$(date +"%Y%m%d%H%M%S")" \
+ ${optionalString cfg.verbose "-v"} \
+ ${optionalString cfg.explicitSymlinks "-H"} \
+ ${optionalString cfg.followSymlinks "-L"} \
+ ${concatStringsSep " " cfg.directories}'';
+ in if (cfg.cachedir != null) then ''
+ mkdir -p ${cfg.cachedir}
+ chmod 0700 ${cfg.cachedir}
+
+ ( flock 9
+ if [ ! -e ${cfg.cachedir}/firstrun ]; then
+ ( flock 10
+ flock -u 9
+ ${tarsnap} --fsck
+ flock 9
+ ) 10>${cfg.cachedir}/firstrun
+ fi
+ ) 9>${cfg.cachedir}/lockf
+
+ exec flock ${cfg.cachedir}/firstrun ${run}
+ '' else "exec ${run}";
+
+ serviceConfig = {
+ Type = "oneshot";
+ IOSchedulingClass = "idle";
+ NoNewPrivileges = "true";
+ CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
+ PermissionsStartOnly = "true";
+ };
+ }) gcfg.archives) //
+
+ (mapAttrs' (name: cfg: nameValuePair "tarsnap-restore-${name}"{
+ description = "Tarsnap restore '${name}'";
+ requires = [ "network-online.target" ];
+
+ path = with pkgs; [ iputils tarsnap utillinux ];
+
+ script = let
+ tarsnap = ''tarsnap --configfile "/etc/tarsnap/${name}.conf"'';
+ lastArchive = ''$(${tarsnap} --list-archives | sort | tail -1)'';
+ run = ''${tarsnap} -x -f "${lastArchive}" ${optionalString cfg.verbose "-v"}'';
+
+ in if (cfg.cachedir != null) then ''
+ mkdir -p ${cfg.cachedir}
+ chmod 0700 ${cfg.cachedir}
+
+ ( flock 9
+ if [ ! -e ${cfg.cachedir}/firstrun ]; then
+ ( flock 10
+ flock -u 9
+ ${tarsnap} --fsck
+ flock 9
+ ) 10>${cfg.cachedir}/firstrun
+ fi
+ ) 9>${cfg.cachedir}/lockf
+
+ exec flock ${cfg.cachedir}/firstrun ${run}
+ '' else "exec ${run}";
+
+ serviceConfig = {
+ Type = "oneshot";
+ IOSchedulingClass = "idle";
+ NoNewPrivileges = "true";
+ CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
+ PermissionsStartOnly = "true";
+ };
+ }) gcfg.archives);
+
+ # Note: the timer must be Persistent=true, so that systemd will start it even
+ # if e.g. your laptop was asleep while the latest interval occurred.
+ systemd.timers = mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}"
+ { timerConfig.OnCalendar = cfg.period;
+ timerConfig.Persistent = "true";
+ wantedBy = [ "timers.target" ];
+ }) gcfg.archives;
+
+ environment.etc =
+ mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.conf"
+ { text = configFile name cfg;
+ }) gcfg.archives;
+
+ environment.systemPackages = [ pkgs.tarsnap ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/tsm.nix b/nixpkgs/nixos/modules/services/backup/tsm.nix
new file mode 100644
index 00000000000..6c238745797
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/tsm.nix
@@ -0,0 +1,106 @@
+{ config, lib, ... }:
+
+let
+
+ inherit (lib.attrsets) hasAttr;
+ inherit (lib.modules) mkDefault mkIf;
+ inherit (lib.options) mkEnableOption mkOption;
+ inherit (lib.types) nullOr strMatching;
+
+ options.services.tsmBackup = {
+ enable = mkEnableOption ''
+ automatic backups with the
+ IBM Spectrum Protect (Tivoli Storage Manager, TSM) client.
+ This also enables
+ <option>programs.tsmClient.enable</option>
+ '';
+ command = mkOption {
+ type = strMatching ".+";
+ default = "backup";
+ example = "incr";
+ description = ''
+ The actual command passed to the
+ <literal>dsmc</literal> executable to start the backup.
+ '';
+ };
+ servername = mkOption {
+ type = strMatching ".+";
+ example = "mainTsmServer";
+ description = ''
+ Create a systemd system service
+ <literal>tsm-backup.service</literal> that starts
+ a backup based on the given servername's stanza.
+ Note that this server's
+ <option>passwdDir</option> will default to
+ <filename>/var/lib/tsm-backup/password</filename>
+ (but may be overridden);
+ also, the service will use
+ <filename>/var/lib/tsm-backup</filename> as
+ <literal>HOME</literal> when calling
+ <literal>dsmc</literal>.
+ '';
+ };
+ autoTime = mkOption {
+ type = nullOr (strMatching ".+");
+ default = null;
+ example = "12:00";
+ description = ''
+ The backup service will be invoked
+ automatically at the given date/time,
+ which must be in the format described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ The default <literal>null</literal>
+ disables automatic backups.
+ '';
+ };
+ };
+
+ cfg = config.services.tsmBackup;
+ cfgPrg = config.programs.tsmClient;
+
+ assertions = [
+ {
+ assertion = hasAttr cfg.servername cfgPrg.servers;
+ message = "TSM service servername not found in list of servers";
+ }
+ {
+ assertion = cfgPrg.servers.${cfg.servername}.genPasswd;
+ message = "TSM service requires automatic password generation";
+ }
+ ];
+
+in
+
+{
+
+ inherit options;
+
+ config = mkIf cfg.enable {
+ inherit assertions;
+ programs.tsmClient.enable = true;
+ programs.tsmClient.servers.${cfg.servername}.passwdDir =
+ mkDefault "/var/lib/tsm-backup/password";
+ systemd.services.tsm-backup = {
+ description = "IBM Spectrum Protect (Tivoli Storage Manager) Backup";
+ # DSM_LOG needs a trailing slash to have it treated as a directory.
+ # `/var/log` would be littered with TSM log files otherwise.
+ environment.DSM_LOG = "/var/log/tsm-backup/";
+ # TSM needs a HOME dir to store certificates.
+ environment.HOME = "/var/lib/tsm-backup";
+ # for exit status description see
+ # https://www.ibm.com/support/knowledgecenter/en/SSEQVQ_8.1.8/client/c_sched_rtncode.html
+ serviceConfig.SuccessExitStatus = "4 8";
+ # The `-se` option must come after the command.
+ # The `-optfile` option suppresses a `dsm.opt`-not-found warning.
+ serviceConfig.ExecStart =
+ "${cfgPrg.wrappedPackage}/bin/dsmc ${cfg.command} -se='${cfg.servername}' -optfile=/dev/null";
+ serviceConfig.LogsDirectory = "tsm-backup";
+ serviceConfig.StateDirectory = "tsm-backup";
+ serviceConfig.StateDirectoryMode = "0750";
+ startAt = mkIf (cfg.autoTime!=null) cfg.autoTime;
+ };
+ };
+
+ meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/backup/zfs-replication.nix b/nixpkgs/nixos/modules/services/backup/zfs-replication.nix
new file mode 100644
index 00000000000..5a64304275d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/zfs-replication.nix
@@ -0,0 +1,90 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+ cfg = config.services.zfs.autoReplication;
+ recursive = optionalString cfg.recursive " --recursive";
+ followDelete = optionalString cfg.followDelete " --follow-delete";
+in {
+ options = {
+ services.zfs.autoReplication = {
+ enable = mkEnableOption "ZFS snapshot replication.";
+
+ followDelete = mkOption {
+ description = "Remove remote snapshots that don't have a local correspondant.";
+ default = true;
+ type = types.bool;
+ };
+
+ host = mkOption {
+ description = "Remote host where snapshots should be sent.";
+ example = "example.com";
+ type = types.str;
+ };
+
+ identityFilePath = mkOption {
+ description = "Path to SSH key used to login to host.";
+ example = "/home/username/.ssh/id_rsa";
+ type = types.path;
+ };
+
+ localFilesystem = mkOption {
+ description = "Local ZFS fileystem from which snapshots should be sent. Defaults to the attribute name.";
+ example = "pool/file/path";
+ type = types.str;
+ };
+
+ remoteFilesystem = mkOption {
+ description = "Remote ZFS filesystem where snapshots should be sent.";
+ example = "pool/file/path";
+ type = types.str;
+ };
+
+ recursive = mkOption {
+ description = "Recursively discover snapshots to send.";
+ default = true;
+ type = types.bool;
+ };
+
+ username = mkOption {
+ description = "Username used by SSH to login to remote host.";
+ example = "username";
+ type = types.str;
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = [
+ pkgs.lz4
+ ];
+
+ systemd.services.zfs-replication = {
+ after = [
+ "zfs-snapshot-daily.service"
+ "zfs-snapshot-frequent.service"
+ "zfs-snapshot-hourly.service"
+ "zfs-snapshot-monthly.service"
+ "zfs-snapshot-weekly.service"
+ ];
+ description = "ZFS Snapshot Replication";
+ documentation = [
+ "https://github.com/alunduil/zfs-replicate"
+ ];
+ restartIfChanged = false;
+ serviceConfig.ExecStart = "${pkgs.zfs-replicate}/bin/zfs-replicate${recursive} -l ${escapeShellArg cfg.username} -i ${escapeShellArg cfg.identityFilePath}${followDelete} ${escapeShellArg cfg.host} ${escapeShellArg cfg.remoteFilesystem} ${escapeShellArg cfg.localFilesystem}";
+ wantedBy = [
+ "zfs-snapshot-daily.service"
+ "zfs-snapshot-frequent.service"
+ "zfs-snapshot-hourly.service"
+ "zfs-snapshot-monthly.service"
+ "zfs-snapshot-weekly.service"
+ ];
+ };
+ };
+
+ meta = {
+ maintainers = with lib.maintainers; [ alunduil ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/znapzend.nix b/nixpkgs/nixos/modules/services/backup/znapzend.nix
new file mode 100644
index 00000000000..f317078ddda
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/znapzend.nix
@@ -0,0 +1,399 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with types;
+
+let
+
+ planDescription = ''
+ The znapzend backup plan to use for the source.
+
+ The plan specifies how often to backup and for how long to keep the
+ backups. It consists of a series of retention periodes to interval
+ associations:
+
+ <literal>
+ retA=>intA,retB=>intB,...
+ </literal>
+
+ Both intervals and retention periods are expressed in standard units
+ of time or multiples of them. You can use both the full name or a
+ shortcut according to the following listing:
+
+ <literal>
+ second|sec|s, minute|min, hour|h, day|d, week|w, month|mon|m, year|y
+ </literal>
+
+ See <citerefentry><refentrytitle>znapzendzetup</refentrytitle><manvolnum>1</manvolnum></citerefentry> for more info.
+ '';
+ planExample = "1h=>10min,1d=>1h,1w=>1d,1m=>1w,1y=>1m";
+
+ # A type for a string of the form number{b|k|M|G}
+ mbufferSizeType = str // {
+ check = x: str.check x && builtins.isList (builtins.match "^[0-9]+[bkMG]$" x);
+ description = "string of the form number{b|k|M|G}";
+ };
+
+ # Type for a string that must contain certain other strings (the list parameter).
+ # Note that these would need regex escaping.
+ stringContainingStrings = list: let
+ matching = s: map (str: builtins.match ".*${str}.*" s) list;
+ in str // {
+ check = x: str.check x && all isList (matching x);
+ description = "string containing all of the characters ${concatStringsSep ", " list}";
+ };
+
+ timestampType = stringContainingStrings [ "%Y" "%m" "%d" "%H" "%M" "%S" ];
+
+ destType = srcConfig: submodule ({ name, ... }: {
+ options = {
+
+ label = mkOption {
+ type = str;
+ description = "Label for this destination. Defaults to the attribute name.";
+ };
+
+ plan = mkOption {
+ type = str;
+ description = planDescription;
+ example = planExample;
+ };
+
+ dataset = mkOption {
+ type = str;
+ description = "Dataset name to send snapshots to.";
+ example = "tank/main";
+ };
+
+ host = mkOption {
+ type = nullOr str;
+ description = ''
+ Host to use for the destination dataset. Can be prefixed with
+ <literal>user@</literal> to specify the ssh user.
+ '';
+ default = null;
+ example = "john@example.com";
+ };
+
+ presend = mkOption {
+ type = nullOr str;
+ description = ''
+ Command to run before sending the snapshot to the destination.
+ Intended to run a remote script via <command>ssh</command> on the
+ destination, e.g. to bring up a backup disk or server or to put a
+ zpool online/offline. See also <option>postsend</option>.
+ '';
+ default = null;
+ example = "ssh root@bserv zpool import -Nf tank";
+ };
+
+ postsend = mkOption {
+ type = nullOr str;
+ description = ''
+ Command to run after sending the snapshot to the destination.
+ Intended to run a remote script via <command>ssh</command> on the
+ destination, e.g. to bring up a backup disk or server or to put a
+ zpool online/offline. See also <option>presend</option>.
+ '';
+ default = null;
+ example = "ssh root@bserv zpool export tank";
+ };
+ };
+
+ config = {
+ label = mkDefault name;
+ plan = mkDefault srcConfig.plan;
+ };
+ });
+
+
+
+ srcType = submodule ({ name, config, ... }: {
+ options = {
+
+ enable = mkOption {
+ type = bool;
+ description = "Whether to enable this source.";
+ default = true;
+ };
+
+ recursive = mkOption {
+ type = bool;
+ description = "Whether to do recursive snapshots.";
+ default = false;
+ };
+
+ mbuffer = {
+ enable = mkOption {
+ type = bool;
+ description = "Whether to use <command>mbuffer</command>.";
+ default = false;
+ };
+
+ port = mkOption {
+ type = nullOr ints.u16;
+ description = ''
+ Port to use for <command>mbuffer</command>.
+
+ If this is null, it will run <command>mbuffer</command> through
+ ssh.
+
+ If this is not null, it will run <command>mbuffer</command>
+ directly through TCP, which is not encrypted but faster. In that
+ case the given port needs to be open on the destination host.
+ '';
+ default = null;
+ };
+
+ size = mkOption {
+ type = mbufferSizeType;
+ description = ''
+ The size for <command>mbuffer</command>.
+ Supports the units b, k, M, G.
+ '';
+ default = "1G";
+ example = "128M";
+ };
+ };
+
+ presnap = mkOption {
+ type = nullOr str;
+ description = ''
+ Command to run before snapshots are taken on the source dataset,
+ e.g. for database locking/flushing. See also
+ <option>postsnap</option>.
+ '';
+ default = null;
+ example = literalExample ''
+ ''${pkgs.mariadb}/bin/mysql -e "set autocommit=0;flush tables with read lock;\\! ''${pkgs.coreutils}/bin/sleep 600" & ''${pkgs.coreutils}/bin/echo $! > /tmp/mariadblock.pid ; sleep 10
+ '';
+ };
+
+ postsnap = mkOption {
+ type = nullOr str;
+ description = ''
+ Command to run after snapshots are taken on the source dataset,
+ e.g. for database unlocking. See also <option>presnap</option>.
+ '';
+ default = null;
+ example = literalExample ''
+ ''${pkgs.coreutils}/bin/kill `''${pkgs.coreutils}/bin/cat /tmp/mariadblock.pid`;''${pkgs.coreutils}/bin/rm /tmp/mariadblock.pid
+ '';
+ };
+
+ timestampFormat = mkOption {
+ type = timestampType;
+ description = ''
+ The timestamp format to use for constructing snapshot names.
+ The syntax is <literal>strftime</literal>-like. The string must
+ consist of the mandatory <literal>%Y %m %d %H %M %S</literal>.
+ Optionally <literal>- _ . :</literal> characters as well as any
+ alphanumeric character are allowed. If suffixed by a
+ <literal>Z</literal>, times will be in UTC.
+ '';
+ default = "%Y-%m-%d-%H%M%S";
+ example = "znapzend-%m.%d.%Y-%H%M%SZ";
+ };
+
+ sendDelay = mkOption {
+ type = int;
+ description = ''
+ Specify delay (in seconds) before sending snaps to the destination.
+ May be useful if you want to control sending time.
+ '';
+ default = 0;
+ example = 60;
+ };
+
+ plan = mkOption {
+ type = str;
+ description = planDescription;
+ example = planExample;
+ };
+
+ dataset = mkOption {
+ type = str;
+ description = "The dataset to use for this source.";
+ example = "tank/home";
+ };
+
+ destinations = mkOption {
+ type = loaOf (destType config);
+ description = "Additional destinations.";
+ default = {};
+ example = literalExample ''
+ {
+ local = {
+ dataset = "btank/backup";
+ presend = "zpool import -N btank";
+ postsend = "zpool export btank";
+ };
+ remote = {
+ host = "john@example.com";
+ dataset = "tank/john";
+ };
+ };
+ '';
+ };
+ };
+
+ config = {
+ dataset = mkDefault name;
+ };
+
+ });
+
+ ### Generating the configuration from here
+
+ cfg = config.services.znapzend;
+
+ onOff = b: if b then "on" else "off";
+ nullOff = b: if b == null then "off" else toString b;
+ stripSlashes = replaceStrings [ "/" ] [ "." ];
+
+ attrsToFile = config: concatStringsSep "\n" (builtins.attrValues (
+ mapAttrs (n: v: "${n}=${v}") config));
+
+ mkDestAttrs = dst: with dst;
+ mapAttrs' (n: v: nameValuePair "dst_${label}${n}" v) ({
+ "" = optionalString (host != null) "${host}:" + dataset;
+ _plan = plan;
+ } // optionalAttrs (presend != null) {
+ _precmd = presend;
+ } // optionalAttrs (postsend != null) {
+ _pstcmd = postsend;
+ });
+
+ mkSrcAttrs = srcCfg: with srcCfg; {
+ enabled = onOff enable;
+ mbuffer = with mbuffer; if enable then "${pkgs.mbuffer}/bin/mbuffer"
+ + optionalString (port != null) ":${toString port}" else "off";
+ mbuffer_size = mbuffer.size;
+ post_znap_cmd = nullOff postsnap;
+ pre_znap_cmd = nullOff presnap;
+ recursive = onOff recursive;
+ src = dataset;
+ src_plan = plan;
+ tsformat = timestampFormat;
+ zend_delay = toString sendDelay;
+ } // fold (a: b: a // b) {} (
+ map mkDestAttrs (builtins.attrValues destinations)
+ );
+
+ files = mapAttrs' (n: srcCfg: let
+ fileText = attrsToFile (mkSrcAttrs srcCfg);
+ in {
+ name = srcCfg.dataset;
+ value = pkgs.writeText (stripSlashes srcCfg.dataset) fileText;
+ }) cfg.zetup;
+
+in
+{
+ options = {
+ services.znapzend = {
+ enable = mkEnableOption "ZnapZend ZFS backup daemon";
+
+ logLevel = mkOption {
+ default = "debug";
+ example = "warning";
+ type = enum ["debug" "info" "warning" "err" "alert"];
+ description = ''
+ The log level when logging to file. Any of debug, info, warning, err,
+ alert. Default in daemonized form is debug.
+ '';
+ };
+
+ logTo = mkOption {
+ type = str;
+ default = "syslog::daemon";
+ example = "/var/log/znapzend.log";
+ description = ''
+ Where to log to (syslog::&lt;facility&gt; or &lt;filepath&gt;).
+ '';
+ };
+
+ noDestroy = mkOption {
+ type = bool;
+ default = false;
+ description = "Does all changes to the filesystem except destroy.";
+ };
+
+ autoCreation = mkOption {
+ type = bool;
+ default = false;
+ description = "Automatically create the destination dataset if it does not exists.";
+ };
+
+ zetup = mkOption {
+ type = loaOf srcType;
+ description = "Znapzend configuration.";
+ default = {};
+ example = literalExample ''
+ {
+ "tank/home" = {
+ # Make snapshots of tank/home every hour, keep those for 1 day,
+ # keep every days snapshot for 1 month, etc.
+ plan = "1d=>1h,1m=>1d,1y=>1m";
+ recursive = true;
+ # Send all those snapshots to john@example.com:rtank/john as well
+ destinations.remote = {
+ host = "john@example.com";
+ dataset = "rtank/john";
+ };
+ };
+ };
+ '';
+ };
+
+ pure = mkOption {
+ type = bool;
+ description = ''
+ Do not persist any stateful znapzend setups. If this option is
+ enabled, your previously set znapzend setups will be cleared and only
+ the ones defined with this module will be applied.
+ '';
+ default = false;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.znapzend ];
+
+ systemd.services = {
+ znapzend = {
+ description = "ZnapZend - ZFS Backup System";
+ wantedBy = [ "zfs.target" ];
+ after = [ "zfs.target" ];
+
+ path = with pkgs; [ zfs mbuffer openssh ];
+
+ preStart = optionalString cfg.pure ''
+ echo Resetting znapzend zetups
+ ${pkgs.znapzend}/bin/znapzendzetup list \
+ | grep -oP '(?<=\*\*\* backup plan: ).*(?= \*\*\*)' \
+ | xargs -I{} ${pkgs.znapzend}/bin/znapzendzetup delete "{}"
+ '' + concatStringsSep "\n" (mapAttrsToList (dataset: config: ''
+ echo Importing znapzend zetup ${config} for dataset ${dataset}
+ ${pkgs.znapzend}/bin/znapzendzetup import --write ${dataset} ${config} &
+ '') files) + ''
+ wait
+ '';
+
+ serviceConfig = {
+ ExecStart = let
+ args = concatStringsSep " " [
+ "--logto=${cfg.logTo}"
+ "--loglevel=${cfg.logLevel}"
+ (optionalString cfg.noDestroy "--nodestroy")
+ (optionalString cfg.autoCreation "--autoCreation")
+ ]; in "${pkgs.znapzend}/bin/znapzend ${args}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ Restart = "on-failure";
+ };
+ };
+ };
+ };
+
+ meta.maintainers = with maintainers; [ infinisil ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix
new file mode 100644
index 00000000000..38db10406b9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix
@@ -0,0 +1,31 @@
+{ hadoop, pkgs }:
+let
+ propertyXml = name: value: ''
+ <property>
+ <name>${name}</name>
+ <value>${builtins.toString value}</value>
+ </property>
+ '';
+ siteXml = fileName: properties: pkgs.writeTextDir fileName ''
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+ <!-- generated by NixOS -->
+ <configuration>
+ ${builtins.concatStringsSep "\n" (pkgs.lib.mapAttrsToList propertyXml properties)}
+ </configuration>
+ '';
+ userFunctions = ''
+ hadoop_verify_logdir() {
+ echo Skipping verification of log directory
+ }
+ '';
+in
+pkgs.buildEnv {
+ name = "hadoop-conf";
+ paths = [
+ (siteXml "core-site.xml" hadoop.coreSite)
+ (siteXml "hdfs-site.xml" hadoop.hdfsSite)
+ (siteXml "mapred-site.xml" hadoop.mapredSite)
+ (siteXml "yarn-site.xml" hadoop.yarnSite)
+ (pkgs.writeTextDir "hadoop-user-functions.sh" userFunctions)
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix
new file mode 100644
index 00000000000..f0f5a6ecbfc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+{
+ imports = [ ./yarn.nix ./hdfs.nix ];
+
+ options.services.hadoop = {
+ coreSite = mkOption {
+ default = {};
+ example = {
+ "fs.defaultFS" = "hdfs://localhost";
+ };
+ description = "Hadoop core-site.xml definition";
+ };
+
+ hdfsSite = mkOption {
+ default = {};
+ example = {
+ "dfs.nameservices" = "namenode1";
+ };
+ description = "Hadoop hdfs-site.xml definition";
+ };
+
+ mapredSite = mkOption {
+ default = {};
+ example = {
+ "mapreduce.map.cpu.vcores" = "1";
+ };
+ description = "Hadoop mapred-site.xml definition";
+ };
+
+ yarnSite = mkOption {
+ default = {};
+ example = {
+ "yarn.resourcemanager.ha.id" = "resourcemanager1";
+ };
+ description = "Hadoop yarn-site.xml definition";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.hadoop;
+ defaultText = "pkgs.hadoop";
+ example = literalExample "pkgs.hadoop";
+ description = ''
+ '';
+ };
+ };
+
+
+ config = mkMerge [
+ (mkIf (builtins.hasAttr "yarn" config.users.users ||
+ builtins.hasAttr "hdfs" config.users.users) {
+ users.groups.hadoop = {
+ gid = config.ids.gids.hadoop;
+ };
+ })
+
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix
new file mode 100644
index 00000000000..4f4b0a92108
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ...}:
+let
+ cfg = config.services.hadoop;
+ hadoopConf = import ./conf.nix { hadoop = cfg; pkgs = pkgs; };
+in
+with lib;
+{
+ options.services.hadoop.hdfs = {
+ namenode.enabled = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run the Hadoop YARN NameNode
+ '';
+ };
+ datanode.enabled = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run the Hadoop YARN DataNode
+ '';
+ };
+ };
+
+ config = mkMerge [
+ (mkIf cfg.hdfs.namenode.enabled {
+ systemd.services.hdfs-namenode = {
+ description = "Hadoop HDFS NameNode";
+ wantedBy = [ "multi-user.target" ];
+
+ environment = {
+ HADOOP_HOME = "${cfg.package}";
+ };
+
+ preStart = ''
+ ${cfg.package}/bin/hdfs --config ${hadoopConf} namenode -format -nonInteractive || true
+ '';
+
+ serviceConfig = {
+ User = "hdfs";
+ SyslogIdentifier = "hdfs-namenode";
+ ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} namenode";
+ };
+ };
+ })
+ (mkIf cfg.hdfs.datanode.enabled {
+ systemd.services.hdfs-datanode = {
+ description = "Hadoop HDFS DataNode";
+ wantedBy = [ "multi-user.target" ];
+
+ environment = {
+ HADOOP_HOME = "${cfg.package}";
+ };
+
+ serviceConfig = {
+ User = "hdfs";
+ SyslogIdentifier = "hdfs-datanode";
+ ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} datanode";
+ };
+ };
+ })
+ (mkIf (
+ cfg.hdfs.namenode.enabled || cfg.hdfs.datanode.enabled
+ ) {
+ users.users.hdfs = {
+ description = "Hadoop HDFS user";
+ group = "hadoop";
+ uid = config.ids.uids.hdfs;
+ };
+ })
+
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix
new file mode 100644
index 00000000000..c92020637e4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ...}:
+let
+ cfg = config.services.hadoop;
+ hadoopConf = import ./conf.nix { hadoop = cfg; pkgs = pkgs; };
+in
+with lib;
+{
+ options.services.hadoop.yarn = {
+ resourcemanager.enabled = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run the Hadoop YARN ResourceManager
+ '';
+ };
+ nodemanager.enabled = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run the Hadoop YARN NodeManager
+ '';
+ };
+ };
+
+ config = mkMerge [
+ (mkIf (
+ cfg.yarn.resourcemanager.enabled || cfg.yarn.nodemanager.enabled
+ ) {
+
+ users.users.yarn = {
+ description = "Hadoop YARN user";
+ group = "hadoop";
+ uid = config.ids.uids.yarn;
+ };
+ })
+
+ (mkIf cfg.yarn.resourcemanager.enabled {
+ systemd.services.yarn-resourcemanager = {
+ description = "Hadoop YARN ResourceManager";
+ wantedBy = [ "multi-user.target" ];
+
+ environment = {
+ HADOOP_HOME = "${cfg.package}";
+ };
+
+ serviceConfig = {
+ User = "yarn";
+ SyslogIdentifier = "yarn-resourcemanager";
+ ExecStart = "${cfg.package}/bin/yarn --config ${hadoopConf} " +
+ " resourcemanager";
+ };
+ };
+ })
+
+ (mkIf cfg.yarn.nodemanager.enabled {
+ systemd.services.yarn-nodemanager = {
+ description = "Hadoop YARN NodeManager";
+ wantedBy = [ "multi-user.target" ];
+
+ environment = {
+ HADOOP_HOME = "${cfg.package}";
+ };
+
+ serviceConfig = {
+ User = "yarn";
+ SyslogIdentifier = "yarn-nodemanager";
+ ExecStart = "${cfg.package}/bin/yarn --config ${hadoopConf} " +
+ " nodemanager";
+ };
+ };
+ })
+
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix
new file mode 100644
index 00000000000..17f2dde31a7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix
@@ -0,0 +1,167 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ top = config.services.kubernetes;
+ cfg = top.addonManager;
+
+ isRBACEnabled = elem "RBAC" top.apiserver.authorizationMode;
+
+ addons = pkgs.runCommand "kubernetes-addons" { } ''
+ mkdir -p $out
+ # since we are mounting the addons to the addon manager, they need to be copied
+ ${concatMapStringsSep ";" (a: "cp -v ${a}/* $out/") (mapAttrsToList (name: addon:
+ pkgs.writeTextDir "${name}.json" (builtins.toJSON addon)
+ ) (cfg.addons))}
+ '';
+in
+{
+ ###### interface
+ options.services.kubernetes.addonManager = with lib.types; {
+
+ bootstrapAddons = mkOption {
+ description = ''
+ Bootstrap addons are like regular addons, but they are applied with cluster-admin rigths.
+ They are applied at addon-manager startup only.
+ '';
+ default = { };
+ type = attrsOf attrs;
+ example = literalExample ''
+ {
+ "my-service" = {
+ "apiVersion" = "v1";
+ "kind" = "Service";
+ "metadata" = {
+ "name" = "my-service";
+ "namespace" = "default";
+ };
+ "spec" = { ... };
+ };
+ }
+ '';
+ };
+
+ addons = mkOption {
+ description = "Kubernetes addons (any kind of Kubernetes resource can be an addon).";
+ default = { };
+ type = attrsOf (either attrs (listOf attrs));
+ example = literalExample ''
+ {
+ "my-service" = {
+ "apiVersion" = "v1";
+ "kind" = "Service";
+ "metadata" = {
+ "name" = "my-service";
+ "namespace" = "default";
+ };
+ "spec" = { ... };
+ };
+ }
+ // import <nixpkgs/nixos/modules/services/cluster/kubernetes/dashboard.nix> { cfg = config.services.kubernetes; };
+ '';
+ };
+
+ enable = mkEnableOption "Whether to enable Kubernetes addon manager.";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ environment.etc."kubernetes/addons".source = "${addons}/";
+
+ systemd.services.kube-addon-manager = {
+ description = "Kubernetes addon manager";
+ wantedBy = [ "kubernetes.target" ];
+ after = [ "kube-apiserver.service" ];
+ environment.ADDON_PATH = "/etc/kubernetes/addons/";
+ path = [ pkgs.gawk ];
+ serviceConfig = {
+ Slice = "kubernetes.slice";
+ ExecStart = "${top.package}/bin/kube-addons";
+ WorkingDirectory = top.dataDir;
+ User = "kubernetes";
+ Group = "kubernetes";
+ Restart = "on-failure";
+ RestartSec = 10;
+ };
+ };
+
+ services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled
+ (let
+ name = system:kube-addon-manager;
+ namespace = "kube-system";
+ in
+ {
+
+ kube-addon-manager-r = {
+ apiVersion = "rbac.authorization.k8s.io/v1";
+ kind = "Role";
+ metadata = {
+ inherit name namespace;
+ };
+ rules = [{
+ apiGroups = ["*"];
+ resources = ["*"];
+ verbs = ["*"];
+ }];
+ };
+
+ kube-addon-manager-rb = {
+ apiVersion = "rbac.authorization.k8s.io/v1";
+ kind = "RoleBinding";
+ metadata = {
+ inherit name namespace;
+ };
+ roleRef = {
+ apiGroup = "rbac.authorization.k8s.io";
+ kind = "Role";
+ inherit name;
+ };
+ subjects = [{
+ apiGroup = "rbac.authorization.k8s.io";
+ kind = "User";
+ inherit name;
+ }];
+ };
+
+ kube-addon-manager-cluster-lister-cr = {
+ apiVersion = "rbac.authorization.k8s.io/v1";
+ kind = "ClusterRole";
+ metadata = {
+ name = "${name}:cluster-lister";
+ };
+ rules = [{
+ apiGroups = ["*"];
+ resources = ["*"];
+ verbs = ["list"];
+ }];
+ };
+
+ kube-addon-manager-cluster-lister-crb = {
+ apiVersion = "rbac.authorization.k8s.io/v1";
+ kind = "ClusterRoleBinding";
+ metadata = {
+ name = "${name}:cluster-lister";
+ };
+ roleRef = {
+ apiGroup = "rbac.authorization.k8s.io";
+ kind = "ClusterRole";
+ name = "${name}:cluster-lister";
+ };
+ subjects = [{
+ kind = "User";
+ inherit name;
+ }];
+ };
+ });
+
+ services.kubernetes.pki.certs = {
+ addonManager = top.lib.mkCert {
+ name = "kube-addon-manager";
+ CN = "system:kube-addon-manager";
+ action = "systemctl restart kube-addon-manager.service";
+ };
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
new file mode 100644
index 00000000000..70f96d75a46
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
@@ -0,0 +1,328 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.kubernetes.addons.dashboard;
+in {
+ options.services.kubernetes.addons.dashboard = {
+ enable = mkEnableOption "kubernetes dashboard addon";
+
+ extraArgs = mkOption {
+ description = "Extra arguments to append to the dashboard cmdline";
+ type = types.listOf types.str;
+ default = [];
+ example = ["--enable-skip-login"];
+ };
+
+ rbac = mkOption {
+ description = "Role-based access control (RBAC) options";
+ default = {};
+ type = types.submodule {
+ options = {
+ enable = mkOption {
+ description = "Whether to enable role based access control is enabled for kubernetes dashboard";
+ type = types.bool;
+ default = elem "RBAC" config.services.kubernetes.apiserver.authorizationMode;
+ };
+
+ clusterAdmin = mkOption {
+ description = "Whether to assign cluster admin rights to the kubernetes dashboard";
+ type = types.bool;
+ default = false;
+ };
+ };
+ };
+ };
+
+ version = mkOption {
+ description = "Which version of the kubernetes dashboard to deploy";
+ type = types.str;
+ default = "v1.10.1";
+ };
+
+ image = mkOption {
+ description = "Docker image to seed for the kubernetes dashboard container.";
+ type = types.attrs;
+ default = {
+ imageName = "k8s.gcr.io/kubernetes-dashboard-amd64";
+ imageDigest = "sha256:0ae6b69432e78069c5ce2bcde0fe409c5c4d6f0f4d9cd50a17974fea38898747";
+ finalImageTag = cfg.version;
+ sha256 = "01xrr4pwgr2hcjrjsi3d14ifpzdfbxzqpzxbk2fkbjb9zkv38zxy";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.kubernetes.kubelet.seedDockerImages = [(pkgs.dockerTools.pullImage cfg.image)];
+
+ services.kubernetes.addonManager.addons = {
+ kubernetes-dashboard-deployment = {
+ kind = "Deployment";
+ apiVersion = "apps/v1";
+ metadata = {
+ labels = {
+ k8s-addon = "kubernetes-dashboard.addons.k8s.io";
+ k8s-app = "kubernetes-dashboard";
+ version = cfg.version;
+ "kubernetes.io/cluster-service" = "true";
+ "addonmanager.kubernetes.io/mode" = "Reconcile";
+ };
+ name = "kubernetes-dashboard";
+ namespace = "kube-system";
+ };
+ spec = {
+ replicas = 1;
+ revisionHistoryLimit = 10;
+ selector.matchLabels.k8s-app = "kubernetes-dashboard";
+ template = {
+ metadata = {
+ labels = {
+ k8s-addon = "kubernetes-dashboard.addons.k8s.io";
+ k8s-app = "kubernetes-dashboard";
+ version = cfg.version;
+ "kubernetes.io/cluster-service" = "true";
+ };
+ annotations = {
+ "scheduler.alpha.kubernetes.io/critical-pod" = "";
+ };
+ };
+ spec = {
+ priorityClassName = "system-cluster-critical";
+ containers = [{
+ name = "kubernetes-dashboard";
+ image = with cfg.image; "${imageName}:${finalImageTag}";
+ ports = [{
+ containerPort = 8443;
+ protocol = "TCP";
+ }];
+ resources = {
+ limits = {
+ cpu = "100m";
+ memory = "300Mi";
+ };
+ requests = {
+ cpu = "100m";
+ memory = "100Mi";
+ };
+ };
+ args = ["--auto-generate-certificates"] ++ cfg.extraArgs;
+ volumeMounts = [{
+ name = "tmp-volume";
+ mountPath = "/tmp";
+ } {
+ name = "kubernetes-dashboard-certs";
+ mountPath = "/certs";
+ }];
+ livenessProbe = {
+ httpGet = {
+ scheme = "HTTPS";
+ path = "/";
+ port = 8443;
+ };
+ initialDelaySeconds = 30;
+ timeoutSeconds = 30;
+ };
+ }];
+ volumes = [{
+ name = "kubernetes-dashboard-certs";
+ secret = {
+ secretName = "kubernetes-dashboard-certs";
+ };
+ } {
+ name = "tmp-volume";
+ emptyDir = {};
+ }];
+ serviceAccountName = "kubernetes-dashboard";
+ tolerations = [{
+ key = "node-role.kubernetes.io/master";
+ effect = "NoSchedule";
+ } {
+ key = "CriticalAddonsOnly";
+ operator = "Exists";
+ }];
+ };
+ };
+ };
+ };
+
+ kubernetes-dashboard-svc = {
+ apiVersion = "v1";
+ kind = "Service";
+ metadata = {
+ labels = {
+ k8s-addon = "kubernetes-dashboard.addons.k8s.io";
+ k8s-app = "kubernetes-dashboard";
+ "kubernetes.io/cluster-service" = "true";
+ "kubernetes.io/name" = "KubeDashboard";
+ "addonmanager.kubernetes.io/mode" = "Reconcile";
+ };
+ name = "kubernetes-dashboard";
+ namespace = "kube-system";
+ };
+ spec = {
+ ports = [{
+ port = 443;
+ targetPort = 8443;
+ }];
+ selector.k8s-app = "kubernetes-dashboard";
+ };
+ };
+
+ kubernetes-dashboard-sa = {
+ apiVersion = "v1";
+ kind = "ServiceAccount";
+ metadata = {
+ labels = {
+ k8s-app = "kubernetes-dashboard";
+ k8s-addon = "kubernetes-dashboard.addons.k8s.io";
+ "addonmanager.kubernetes.io/mode" = "Reconcile";
+ };
+ name = "kubernetes-dashboard";
+ namespace = "kube-system";
+ };
+ };
+ kubernetes-dashboard-sec-certs = {
+ apiVersion = "v1";
+ kind = "Secret";
+ metadata = {
+ labels = {
+ k8s-app = "kubernetes-dashboard";
+ # Allows editing resource and makes sure it is created first.
+ "addonmanager.kubernetes.io/mode" = "EnsureExists";
+ };
+ name = "kubernetes-dashboard-certs";
+ namespace = "kube-system";
+ };
+ type = "Opaque";
+ };
+ kubernetes-dashboard-sec-kholder = {
+ apiVersion = "v1";
+ kind = "Secret";
+ metadata = {
+ labels = {
+ k8s-app = "kubernetes-dashboard";
+ # Allows editing resource and makes sure it is created first.
+ "addonmanager.kubernetes.io/mode" = "EnsureExists";
+ };
+ name = "kubernetes-dashboard-key-holder";
+ namespace = "kube-system";
+ };
+ type = "Opaque";
+ };
+ kubernetes-dashboard-cm = {
+ apiVersion = "v1";
+ kind = "ConfigMap";
+ metadata = {
+ labels = {
+ k8s-app = "kubernetes-dashboard";
+ # Allows editing resource and makes sure it is created first.
+ "addonmanager.kubernetes.io/mode" = "EnsureExists";
+ };
+ name = "kubernetes-dashboard-settings";
+ namespace = "kube-system";
+ };
+ };
+ } // (optionalAttrs cfg.rbac.enable
+ (let
+ subjects = [{
+ kind = "ServiceAccount";
+ name = "kubernetes-dashboard";
+ namespace = "kube-system";
+ }];
+ labels = {
+ k8s-app = "kubernetes-dashboard";
+ k8s-addon = "kubernetes-dashboard.addons.k8s.io";
+ "addonmanager.kubernetes.io/mode" = "Reconcile";
+ };
+ in
+ (if cfg.rbac.clusterAdmin then {
+ kubernetes-dashboard-crb = {
+ apiVersion = "rbac.authorization.k8s.io/v1";
+ kind = "ClusterRoleBinding";
+ metadata = {
+ name = "kubernetes-dashboard";
+ inherit labels;
+ };
+ roleRef = {
+ apiGroup = "rbac.authorization.k8s.io";
+ kind = "ClusterRole";
+ name = "cluster-admin";
+ };
+ inherit subjects;
+ };
+ }
+ else
+ {
+ # Upstream role- and rolebinding as per:
+ # https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/alternative/kubernetes-dashboard.yaml
+ kubernetes-dashboard-role = {
+ apiVersion = "rbac.authorization.k8s.io/v1";
+ kind = "Role";
+ metadata = {
+ name = "kubernetes-dashboard-minimal";
+ namespace = "kube-system";
+ inherit labels;
+ };
+ rules = [
+ # Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret.
+ {
+ apiGroups = [""];
+ resources = ["secrets"];
+ verbs = ["create"];
+ }
+ # Allow Dashboard to create 'kubernetes-dashboard-settings' config map.
+ {
+ apiGroups = [""];
+ resources = ["configmaps"];
+ verbs = ["create"];
+ }
+ # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
+ {
+ apiGroups = [""];
+ resources = ["secrets"];
+ resourceNames = ["kubernetes-dashboard-key-holder"];
+ verbs = ["get" "update" "delete"];
+ }
+ # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
+ {
+ apiGroups = [""];
+ resources = ["configmaps"];
+ resourceNames = ["kubernetes-dashboard-settings"];
+ verbs = ["get" "update"];
+ }
+ # Allow Dashboard to get metrics from heapster.
+ {
+ apiGroups = [""];
+ resources = ["services"];
+ resourceNames = ["heapster"];
+ verbs = ["proxy"];
+ }
+ {
+ apiGroups = [""];
+ resources = ["services/proxy"];
+ resourceNames = ["heapster" "http:heapster:" "https:heapster:"];
+ verbs = ["get"];
+ }
+ ];
+ };
+
+ kubernetes-dashboard-rb = {
+ apiVersion = "rbac.authorization.k8s.io/v1";
+ kind = "RoleBinding";
+ metadata = {
+ name = "kubernetes-dashboard-minimal";
+ namespace = "kube-system";
+ inherit labels;
+ };
+ roleRef = {
+ apiGroup = "rbac.authorization.k8s.io";
+ kind = "Role";
+ name = "kubernetes-dashboard-minimal";
+ };
+ inherit subjects;
+ };
+ })
+ ));
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix
new file mode 100644
index 00000000000..47e588de3c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -0,0 +1,334 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ version = "1.5.0";
+ cfg = config.services.kubernetes.addons.dns;
+ ports = {
+ dns = 10053;
+ health = 10054;
+ metrics = 10055;
+ };
+in {
+ options.services.kubernetes.addons.dns = {
+ enable = mkEnableOption "kubernetes dns addon";
+
+ clusterIp = mkOption {
+ description = "Dns addon clusterIP";
+
+ # this default is also what kubernetes users
+ default = (
+ concatStringsSep "." (
+ take 3 (splitString "." config.services.kubernetes.apiserver.serviceClusterIpRange
+ ))
+ ) + ".254";
+ type = types.str;
+ };
+
+ clusterDomain = mkOption {
+ description = "Dns cluster domain";
+ default = "cluster.local";
+ type = types.str;
+ };
+
+ replicas = mkOption {
+ description = "Number of DNS pod replicas to deploy in the cluster.";
+ default = 2;
+ type = types.int;
+ };
+
+ reconcileMode = mkOption {
+ description = ''
+ Controls the addon manager reconciliation mode for the DNS addon.
+
+ Setting reconcile mode to EnsureExists makes it possible to tailor DNS behavior by editing the coredns ConfigMap.
+
+ See: <link xlink:href="https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/addon-manager/README.md"/>.
+ '';
+ default = "Reconcile";
+ type = types.enum [ "Reconcile" "EnsureExists" ];
+ };
+
+ coredns = mkOption {
+ description = "Docker image to seed for the CoreDNS container.";
+ type = types.attrs;
+ default = {
+ imageName = "coredns/coredns";
+ imageDigest = "sha256:e83beb5e43f8513fa735e77ffc5859640baea30a882a11cc75c4c3244a737d3c";
+ finalImageTag = version;
+ sha256 = "15sbmhrxjxidj0j0cccn1qxpg6al175w43m6ngspl0mc132zqc9q";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.kubernetes.kubelet.seedDockerImages =
+ singleton (pkgs.dockerTools.pullImage cfg.coredns);
+
+ services.kubernetes.addonManager.bootstrapAddons = {
+ coredns-cr = {
+ apiVersion = "rbac.authorization.k8s.io/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";
+ }
+ ];
+ };
+ };
+
+ services.kubernetes.addonManager.addons = {
+ coredns-sa = {
+ apiVersion = "v1";
+ kind = "ServiceAccount";
+ metadata = {
+ labels = {
+ "addonmanager.kubernetes.io/mode" = "Reconcile";
+ k8s-app = "kube-dns";
+ "kubernetes.io/cluster-service" = "true";
+ };
+ name = "coredns";
+ namespace = "kube-system";
+ };
+ };
+
+ coredns-cm = {
+ apiVersion = "v1";
+ kind = "ConfigMap";
+ metadata = {
+ labels = {
+ "addonmanager.kubernetes.io/mode" = cfg.reconcileMode;
+ k8s-app = "kube-dns";
+ "kubernetes.io/cluster-service" = "true";
+ };
+ name = "coredns";
+ namespace = "kube-system";
+ };
+ data = {
+ Corefile = ".:${toString ports.dns} {
+ errors
+ health :${toString ports.health}
+ kubernetes ${cfg.clusterDomain} in-addr.arpa ip6.arpa {
+ pods insecure
+ upstream
+ fallthrough in-addr.arpa ip6.arpa
+ }
+ prometheus :${toString ports.metrics}
+ forward . /etc/resolv.conf
+ cache 30
+ loop
+ reload
+ loadbalance
+ }";
+ };
+ };
+
+ coredns-deploy = {
+ apiVersion = "extensions/v1beta1";
+ kind = "Deployment";
+ metadata = {
+ labels = {
+ "addonmanager.kubernetes.io/mode" = cfg.reconcileMode;
+ k8s-app = "kube-dns";
+ "kubernetes.io/cluster-service" = "true";
+ "kubernetes.io/name" = "CoreDNS";
+ };
+ name = "coredns";
+ namespace = "kube-system";
+ };
+ spec = {
+ replicas = cfg.replicas;
+ selector = {
+ matchLabels = { k8s-app = "kube-dns"; };
+ };
+ strategy = {
+ rollingUpdate = { maxUnavailable = 1; };
+ type = "RollingUpdate";
+ };
+ template = {
+ metadata = {
+ labels = {
+ k8s-app = "kube-dns";
+ };
+ };
+ spec = {
+ containers = [
+ {
+ args = [ "-conf" "/etc/coredns/Corefile" ];
+ image = with cfg.coredns; "${imageName}:${finalImageTag}";
+ imagePullPolicy = "Never";
+ livenessProbe = {
+ failureThreshold = 5;
+ httpGet = {
+ path = "/health";
+ port = ports.health;
+ scheme = "HTTP";
+ };
+ initialDelaySeconds = 60;
+ successThreshold = 1;
+ timeoutSeconds = 5;
+ };
+ name = "coredns";
+ ports = [
+ {
+ containerPort = ports.dns;
+ name = "dns";
+ protocol = "UDP";
+ }
+ {
+ containerPort = ports.dns;
+ name = "dns-tcp";
+ protocol = "TCP";
+ }
+ {
+ containerPort = ports.metrics;
+ name = "metrics";
+ protocol = "TCP";
+ }
+ ];
+ resources = {
+ limits = {
+ memory = "170Mi";
+ };
+ requests = {
+ cpu = "100m";
+ memory = "70Mi";
+ };
+ };
+ securityContext = {
+ allowPrivilegeEscalation = false;
+ capabilities = {
+ drop = [ "all" ];
+ };
+ readOnlyRootFilesystem = true;
+ };
+ volumeMounts = [
+ {
+ mountPath = "/etc/coredns";
+ name = "config-volume";
+ readOnly = true;
+ }
+ ];
+ }
+ ];
+ dnsPolicy = "Default";
+ nodeSelector = {
+ "beta.kubernetes.io/os" = "linux";
+ };
+ serviceAccountName = "coredns";
+ tolerations = [
+ {
+ effect = "NoSchedule";
+ key = "node-role.kubernetes.io/master";
+ }
+ {
+ key = "CriticalAddonsOnly";
+ operator = "Exists";
+ }
+ ];
+ volumes = [
+ {
+ configMap = {
+ items = [
+ {
+ key = "Corefile";
+ path = "Corefile";
+ }
+ ];
+ name = "coredns";
+ };
+ name = "config-volume";
+ }
+ ];
+ };
+ };
+ };
+ };
+
+ coredns-svc = {
+ apiVersion = "v1";
+ kind = "Service";
+ metadata = {
+ annotations = {
+ "prometheus.io/port" = toString ports.metrics;
+ "prometheus.io/scrape" = "true";
+ };
+ labels = {
+ "addonmanager.kubernetes.io/mode" = "Reconcile";
+ k8s-app = "kube-dns";
+ "kubernetes.io/cluster-service" = "true";
+ "kubernetes.io/name" = "CoreDNS";
+ };
+ name = "kube-dns";
+ namespace = "kube-system";
+ };
+ spec = {
+ clusterIP = cfg.clusterIp;
+ ports = [
+ {
+ name = "dns";
+ port = 53;
+ targetPort = ports.dns;
+ protocol = "UDP";
+ }
+ {
+ name = "dns-tcp";
+ port = 53;
+ targetPort = ports.dns;
+ protocol = "TCP";
+ }
+ ];
+ selector = { k8s-app = "kube-dns"; };
+ };
+ };
+ };
+
+ services.kubernetes.kubelet.clusterDns = mkDefault cfg.clusterIp;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix
new file mode 100644
index 00000000000..33796bf2e08
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix
@@ -0,0 +1,457 @@
+ { config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ top = config.services.kubernetes;
+ cfg = top.apiserver;
+
+ isRBACEnabled = elem "RBAC" cfg.authorizationMode;
+
+ apiserverServiceIP = (concatStringsSep "." (
+ take 3 (splitString "." cfg.serviceClusterIpRange
+ )) + ".1");
+in
+{
+ ###### interface
+ options.services.kubernetes.apiserver = with lib.types; {
+
+ advertiseAddress = mkOption {
+ description = ''
+ Kubernetes apiserver IP address on which to advertise the apiserver
+ to members of the cluster. This address must be reachable by the rest
+ of the cluster.
+ '';
+ default = null;
+ type = nullOr str;
+ };
+
+ allowPrivileged = mkOption {
+ description = "Whether to allow privileged containers on Kubernetes.";
+ default = false;
+ type = bool;
+ };
+
+ authorizationMode = mkOption {
+ description = ''
+ Kubernetes apiserver authorization mode (AlwaysAllow/AlwaysDeny/ABAC/Webhook/RBAC/Node). See
+ <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authorization/"/>
+ '';
+ default = ["RBAC" "Node"]; # Enabling RBAC by default, although kubernetes default is AllowAllow
+ type = listOf (enum ["AlwaysAllow" "AlwaysDeny" "ABAC" "Webhook" "RBAC" "Node"]);
+ };
+
+ authorizationPolicy = mkOption {
+ description = ''
+ Kubernetes apiserver authorization policy file. See
+ <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authorization/"/>
+ '';
+ default = [];
+ type = listOf attrs;
+ };
+
+ basicAuthFile = mkOption {
+ description = ''
+ Kubernetes apiserver basic authentication file. See
+ <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authentication"/>
+ '';
+ default = null;
+ type = nullOr path;
+ };
+
+ bindAddress = mkOption {
+ description = ''
+ The IP address on which to listen for the --secure-port port.
+ The associated interface(s) must be reachable by the rest
+ of the cluster, and by CLI/web clients.
+ '';
+ default = "0.0.0.0";
+ type = str;
+ };
+
+ clientCaFile = mkOption {
+ description = "Kubernetes apiserver CA file for client auth.";
+ default = top.caFile;
+ type = nullOr path;
+ };
+
+ disableAdmissionPlugins = mkOption {
+ description = ''
+ Kubernetes admission control plugins to disable. See
+ <link xlink:href="https://kubernetes.io/docs/admin/admission-controllers/"/>
+ '';
+ default = [];
+ type = listOf str;
+ };
+
+ enable = mkEnableOption "Kubernetes apiserver";
+
+ enableAdmissionPlugins = mkOption {
+ description = ''
+ Kubernetes admission control plugins to enable. See
+ <link xlink:href="https://kubernetes.io/docs/admin/admission-controllers/"/>
+ '';
+ default = [
+ "NamespaceLifecycle" "LimitRanger" "ServiceAccount"
+ "ResourceQuota" "DefaultStorageClass" "DefaultTolerationSeconds"
+ "NodeRestriction"
+ ];
+ example = [
+ "NamespaceLifecycle" "NamespaceExists" "LimitRanger"
+ "SecurityContextDeny" "ServiceAccount" "ResourceQuota"
+ "PodSecurityPolicy" "NodeRestriction" "DefaultStorageClass"
+ ];
+ type = listOf str;
+ };
+
+ etcd = {
+ servers = mkOption {
+ description = "List of etcd servers.";
+ default = ["http://127.0.0.1:2379"];
+ type = types.listOf types.str;
+ };
+
+ keyFile = mkOption {
+ description = "Etcd key file.";
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ certFile = mkOption {
+ description = "Etcd cert file.";
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ caFile = mkOption {
+ description = "Etcd ca file.";
+ default = top.caFile;
+ type = types.nullOr types.path;
+ };
+ };
+
+ extraOpts = mkOption {
+ description = "Kubernetes apiserver extra command line options.";
+ default = "";
+ type = str;
+ };
+
+ extraSANs = mkOption {
+ description = "Extra x509 Subject Alternative Names to be added to the kubernetes apiserver tls cert.";
+ default = [];
+ type = listOf str;
+ };
+
+ featureGates = mkOption {
+ description = "List set of feature gates";
+ default = top.featureGates;
+ type = listOf str;
+ };
+
+ insecureBindAddress = mkOption {
+ description = "The IP address on which to serve the --insecure-port.";
+ default = "127.0.0.1";
+ type = str;
+ };
+
+ insecurePort = mkOption {
+ description = "Kubernetes apiserver insecure listening port. (0 = disabled)";
+ default = 0;
+ type = int;
+ };
+
+ kubeletClientCaFile = mkOption {
+ description = "Path to a cert file for connecting to kubelet.";
+ default = top.caFile;
+ type = nullOr path;
+ };
+
+ kubeletClientCertFile = mkOption {
+ description = "Client certificate to use for connections to kubelet.";
+ default = null;
+ type = nullOr path;
+ };
+
+ kubeletClientKeyFile = mkOption {
+ description = "Key to use for connections to kubelet.";
+ default = null;
+ type = nullOr path;
+ };
+
+ kubeletHttps = mkOption {
+ description = "Whether to use https for connections to kubelet.";
+ default = true;
+ type = bool;
+ };
+
+ preferredAddressTypes = mkOption {
+ description = "List of the preferred NodeAddressTypes to use for kubelet connections.";
+ type = nullOr str;
+ default = null;
+ };
+
+ proxyClientCertFile = mkOption {
+ description = "Client certificate to use for connections to proxy.";
+ default = null;
+ type = nullOr path;
+ };
+
+ proxyClientKeyFile = mkOption {
+ description = "Key to use for connections to proxy.";
+ default = null;
+ type = nullOr path;
+ };
+
+ runtimeConfig = mkOption {
+ description = ''
+ Api runtime configuration. See
+ <link xlink:href="https://kubernetes.io/docs/tasks/administer-cluster/cluster-management/"/>
+ '';
+ default = "authentication.k8s.io/v1beta1=true";
+ example = "api/all=false,api/v1=true";
+ type = str;
+ };
+
+ storageBackend = mkOption {
+ description = ''
+ Kubernetes apiserver storage backend.
+ '';
+ default = "etcd3";
+ type = enum ["etcd2" "etcd3"];
+ };
+
+ securePort = mkOption {
+ description = "Kubernetes apiserver secure port.";
+ default = 6443;
+ type = int;
+ };
+
+ serviceAccountKeyFile = mkOption {
+ description = ''
+ Kubernetes apiserver PEM-encoded x509 RSA private or public key file,
+ used to verify ServiceAccount tokens. By default tls private key file
+ is used.
+ '';
+ default = null;
+ type = nullOr path;
+ };
+
+ serviceClusterIpRange = mkOption {
+ description = ''
+ A CIDR notation IP range from which to assign service cluster IPs.
+ This must not overlap with any IP ranges assigned to nodes for pods.
+ '';
+ default = "10.0.0.0/24";
+ type = str;
+ };
+
+ tlsCertFile = mkOption {
+ description = "Kubernetes apiserver certificate file.";
+ default = null;
+ type = nullOr path;
+ };
+
+ tlsKeyFile = mkOption {
+ description = "Kubernetes apiserver private key file.";
+ default = null;
+ type = nullOr path;
+ };
+
+ tokenAuthFile = mkOption {
+ description = ''
+ Kubernetes apiserver token authentication file. See
+ <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authentication"/>
+ '';
+ default = null;
+ type = nullOr path;
+ };
+
+ verbosity = mkOption {
+ description = ''
+ Optional glog verbosity level for logging statements. See
+ <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+ '';
+ default = null;
+ type = nullOr int;
+ };
+
+ webhookConfig = mkOption {
+ description = ''
+ Kubernetes apiserver Webhook config file. It uses the kubeconfig file format.
+ See <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/webhook/"/>
+ '';
+ default = null;
+ type = nullOr path;
+ };
+
+ };
+
+
+ ###### implementation
+ config = mkMerge [
+
+ (mkIf cfg.enable {
+ systemd.services.kube-apiserver = {
+ description = "Kubernetes APIServer Service";
+ wantedBy = [ "kubernetes.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ Slice = "kubernetes.slice";
+ ExecStart = ''${top.package}/bin/kube-apiserver \
+ --allow-privileged=${boolToString cfg.allowPrivileged} \
+ --authorization-mode=${concatStringsSep "," cfg.authorizationMode} \
+ ${optionalString (elem "ABAC" cfg.authorizationMode)
+ "--authorization-policy-file=${
+ pkgs.writeText "kube-auth-policy.jsonl"
+ (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.authorizationPolicy)
+ }"
+ } \
+ ${optionalString (elem "Webhook" cfg.authorizationMode)
+ "--authorization-webhook-config-file=${cfg.webhookConfig}"
+ } \
+ --bind-address=${cfg.bindAddress} \
+ ${optionalString (cfg.advertiseAddress != null)
+ "--advertise-address=${cfg.advertiseAddress}"} \
+ ${optionalString (cfg.clientCaFile != null)
+ "--client-ca-file=${cfg.clientCaFile}"} \
+ --disable-admission-plugins=${concatStringsSep "," cfg.disableAdmissionPlugins} \
+ --enable-admission-plugins=${concatStringsSep "," cfg.enableAdmissionPlugins} \
+ --etcd-servers=${concatStringsSep "," cfg.etcd.servers} \
+ ${optionalString (cfg.etcd.caFile != null)
+ "--etcd-cafile=${cfg.etcd.caFile}"} \
+ ${optionalString (cfg.etcd.certFile != null)
+ "--etcd-certfile=${cfg.etcd.certFile}"} \
+ ${optionalString (cfg.etcd.keyFile != null)
+ "--etcd-keyfile=${cfg.etcd.keyFile}"} \
+ ${optionalString (cfg.featureGates != [])
+ "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+ ${optionalString (cfg.basicAuthFile != null)
+ "--basic-auth-file=${cfg.basicAuthFile}"} \
+ --kubelet-https=${boolToString cfg.kubeletHttps} \
+ ${optionalString (cfg.kubeletClientCaFile != null)
+ "--kubelet-certificate-authority=${cfg.kubeletClientCaFile}"} \
+ ${optionalString (cfg.kubeletClientCertFile != null)
+ "--kubelet-client-certificate=${cfg.kubeletClientCertFile}"} \
+ ${optionalString (cfg.kubeletClientKeyFile != null)
+ "--kubelet-client-key=${cfg.kubeletClientKeyFile}"} \
+ ${optionalString (cfg.preferredAddressTypes != null)
+ "--kubelet-preferred-address-types=${cfg.preferredAddressTypes}"} \
+ ${optionalString (cfg.proxyClientCertFile != null)
+ "--proxy-client-cert-file=${cfg.proxyClientCertFile}"} \
+ ${optionalString (cfg.proxyClientKeyFile != null)
+ "--proxy-client-key-file=${cfg.proxyClientKeyFile}"} \
+ --insecure-bind-address=${cfg.insecureBindAddress} \
+ --insecure-port=${toString cfg.insecurePort} \
+ ${optionalString (cfg.runtimeConfig != "")
+ "--runtime-config=${cfg.runtimeConfig}"} \
+ --secure-port=${toString cfg.securePort} \
+ ${optionalString (cfg.serviceAccountKeyFile!=null)
+ "--service-account-key-file=${cfg.serviceAccountKeyFile}"} \
+ --service-cluster-ip-range=${cfg.serviceClusterIpRange} \
+ --storage-backend=${cfg.storageBackend} \
+ ${optionalString (cfg.tlsCertFile != null)
+ "--tls-cert-file=${cfg.tlsCertFile}"} \
+ ${optionalString (cfg.tlsKeyFile != null)
+ "--tls-private-key-file=${cfg.tlsKeyFile}"} \
+ ${optionalString (cfg.tokenAuthFile != null)
+ "--token-auth-file=${cfg.tokenAuthFile}"} \
+ ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+ ${cfg.extraOpts}
+ '';
+ WorkingDirectory = top.dataDir;
+ User = "kubernetes";
+ Group = "kubernetes";
+ AmbientCapabilities = "cap_net_bind_service";
+ Restart = "on-failure";
+ RestartSec = 5;
+ };
+ };
+
+ services.etcd = {
+ clientCertAuth = mkDefault true;
+ peerClientCertAuth = mkDefault true;
+ listenClientUrls = mkDefault ["https://0.0.0.0:2379"];
+ listenPeerUrls = mkDefault ["https://0.0.0.0:2380"];
+ advertiseClientUrls = mkDefault ["https://${top.masterAddress}:2379"];
+ initialCluster = mkDefault ["${top.masterAddress}=https://${top.masterAddress}:2380"];
+ name = mkDefault top.masterAddress;
+ initialAdvertisePeerUrls = mkDefault ["https://${top.masterAddress}:2380"];
+ };
+
+ services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled {
+
+ apiserver-kubelet-api-admin-crb = {
+ apiVersion = "rbac.authorization.k8s.io/v1";
+ kind = "ClusterRoleBinding";
+ metadata = {
+ name = "system:kube-apiserver:kubelet-api-admin";
+ };
+ roleRef = {
+ apiGroup = "rbac.authorization.k8s.io";
+ kind = "ClusterRole";
+ name = "system:kubelet-api-admin";
+ };
+ subjects = [{
+ kind = "User";
+ name = "system:kube-apiserver";
+ }];
+ };
+
+ };
+
+ services.kubernetes.pki.certs = with top.lib; {
+ apiServer = mkCert {
+ name = "kube-apiserver";
+ CN = "kubernetes";
+ hosts = [
+ "kubernetes.default.svc"
+ "kubernetes.default.svc.${top.addons.dns.clusterDomain}"
+ cfg.advertiseAddress
+ top.masterAddress
+ apiserverServiceIP
+ "127.0.0.1"
+ ] ++ cfg.extraSANs;
+ action = "systemctl restart kube-apiserver.service";
+ };
+ apiserverProxyClient = mkCert {
+ name = "kube-apiserver-proxy-client";
+ CN = "front-proxy-client";
+ action = "systemctl restart kube-apiserver.service";
+ };
+ apiserverKubeletClient = mkCert {
+ name = "kube-apiserver-kubelet-client";
+ CN = "system:kube-apiserver";
+ action = "systemctl restart kube-apiserver.service";
+ };
+ apiserverEtcdClient = mkCert {
+ name = "kube-apiserver-etcd-client";
+ CN = "etcd-client";
+ action = "systemctl restart kube-apiserver.service";
+ };
+ clusterAdmin = mkCert {
+ name = "cluster-admin";
+ CN = "cluster-admin";
+ fields = {
+ O = "system:masters";
+ };
+ privateKeyOwner = "root";
+ };
+ etcd = mkCert {
+ name = "etcd";
+ CN = top.masterAddress;
+ hosts = [
+ "etcd.local"
+ "etcd.${top.addons.dns.clusterDomain}"
+ top.masterAddress
+ cfg.advertiseAddress
+ ];
+ privateKeyOwner = "etcd";
+ action = "systemctl restart etcd.service";
+ };
+ };
+
+ })
+
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix
new file mode 100644
index 00000000000..0b73d090f24
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix
@@ -0,0 +1,162 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ top = config.services.kubernetes;
+ cfg = top.controllerManager;
+in
+{
+ ###### interface
+ options.services.kubernetes.controllerManager = with lib.types; {
+
+ allocateNodeCIDRs = mkOption {
+ description = "Whether to automatically allocate CIDR ranges for cluster nodes.";
+ default = true;
+ type = bool;
+ };
+
+ bindAddress = mkOption {
+ description = "Kubernetes controller manager listening address.";
+ default = "127.0.0.1";
+ type = str;
+ };
+
+ clusterCidr = mkOption {
+ description = "Kubernetes CIDR Range for Pods in cluster.";
+ default = top.clusterCidr;
+ type = str;
+ };
+
+ enable = mkEnableOption "Kubernetes controller manager";
+
+ extraOpts = mkOption {
+ description = "Kubernetes controller manager extra command line options.";
+ default = "";
+ type = str;
+ };
+
+ featureGates = mkOption {
+ description = "List set of feature gates";
+ default = top.featureGates;
+ type = listOf str;
+ };
+
+ insecurePort = mkOption {
+ description = "Kubernetes controller manager insecure listening port.";
+ default = 0;
+ type = int;
+ };
+
+ kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes controller manager";
+
+ leaderElect = mkOption {
+ description = "Whether to start leader election before executing main loop.";
+ type = bool;
+ default = true;
+ };
+
+ rootCaFile = mkOption {
+ description = ''
+ Kubernetes controller manager certificate authority file included in
+ service account's token secret.
+ '';
+ default = top.caFile;
+ type = nullOr path;
+ };
+
+ securePort = mkOption {
+ description = "Kubernetes controller manager secure listening port.";
+ default = 10252;
+ type = int;
+ };
+
+ serviceAccountKeyFile = mkOption {
+ description = ''
+ Kubernetes controller manager PEM-encoded private RSA key file used to
+ sign service account tokens
+ '';
+ default = null;
+ type = nullOr path;
+ };
+
+ tlsCertFile = mkOption {
+ description = "Kubernetes controller-manager certificate file.";
+ default = null;
+ type = nullOr path;
+ };
+
+ tlsKeyFile = mkOption {
+ description = "Kubernetes controller-manager private key file.";
+ default = null;
+ type = nullOr path;
+ };
+
+ verbosity = mkOption {
+ description = ''
+ Optional glog verbosity level for logging statements. See
+ <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+ '';
+ default = null;
+ type = nullOr int;
+ };
+
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ systemd.services.kube-controller-manager = {
+ description = "Kubernetes Controller Manager Service";
+ wantedBy = [ "kubernetes.target" ];
+ after = [ "kube-apiserver.service" ];
+ serviceConfig = {
+ RestartSec = "30s";
+ Restart = "on-failure";
+ Slice = "kubernetes.slice";
+ ExecStart = ''${top.package}/bin/kube-controller-manager \
+ --allocate-node-cidrs=${boolToString cfg.allocateNodeCIDRs} \
+ --bind-address=${cfg.bindAddress} \
+ ${optionalString (cfg.clusterCidr!=null)
+ "--cluster-cidr=${cfg.clusterCidr}"} \
+ ${optionalString (cfg.featureGates != [])
+ "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+ --kubeconfig=${top.lib.mkKubeConfig "kube-controller-manager" cfg.kubeconfig} \
+ --leader-elect=${boolToString cfg.leaderElect} \
+ ${optionalString (cfg.rootCaFile!=null)
+ "--root-ca-file=${cfg.rootCaFile}"} \
+ --port=${toString cfg.insecurePort} \
+ --secure-port=${toString cfg.securePort} \
+ ${optionalString (cfg.serviceAccountKeyFile!=null)
+ "--service-account-private-key-file=${cfg.serviceAccountKeyFile}"} \
+ ${optionalString (cfg.tlsCertFile!=null)
+ "--tls-cert-file=${cfg.tlsCertFile}"} \
+ ${optionalString (cfg.tlsKeyFile!=null)
+ "--tls-private-key-file=${cfg.tlsKeyFile}"} \
+ ${optionalString (elem "RBAC" top.apiserver.authorizationMode)
+ "--use-service-account-credentials"} \
+ ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+ ${cfg.extraOpts}
+ '';
+ WorkingDirectory = top.dataDir;
+ User = "kubernetes";
+ Group = "kubernetes";
+ };
+ path = top.path;
+ };
+
+ services.kubernetes.pki.certs = with top.lib; {
+ controllerManager = mkCert {
+ name = "kube-controller-manager";
+ CN = "kube-controller-manager";
+ action = "systemctl restart kube-controller-manager.service";
+ };
+ controllerManagerClient = mkCert {
+ name = "kube-controller-manager-client";
+ CN = "system:kube-controller-manager";
+ action = "systemctl restart kube-controller-manager.service";
+ };
+ };
+
+ services.kubernetes.controllerManager.kubeconfig.server = mkDefault top.apiserverAddress;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix
new file mode 100644
index 00000000000..3790ac9b691
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix
@@ -0,0 +1,284 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.kubernetes;
+
+ mkKubeConfig = name: conf: pkgs.writeText "${name}-kubeconfig" (builtins.toJSON {
+ apiVersion = "v1";
+ kind = "Config";
+ clusters = [{
+ name = "local";
+ cluster.certificate-authority = conf.caFile or cfg.caFile;
+ cluster.server = conf.server;
+ }];
+ users = [{
+ inherit name;
+ user = {
+ client-certificate = conf.certFile;
+ client-key = conf.keyFile;
+ };
+ }];
+ contexts = [{
+ context = {
+ cluster = "local";
+ user = name;
+ };
+ current-context = "local";
+ }];
+ });
+
+ caCert = secret "ca";
+
+ etcdEndpoints = ["https://${cfg.masterAddress}:2379"];
+
+ mkCert = { name, CN, hosts ? [], fields ? {}, action ? "",
+ privateKeyOwner ? "kubernetes" }: rec {
+ inherit name caCert CN hosts fields action;
+ cert = secret name;
+ key = secret "${name}-key";
+ privateKeyOptions = {
+ owner = privateKeyOwner;
+ group = "nogroup";
+ mode = "0600";
+ path = key;
+ };
+ };
+
+ secret = name: "${cfg.secretsPath}/${name}.pem";
+
+ mkKubeConfigOptions = prefix: {
+ server = mkOption {
+ description = "${prefix} kube-apiserver server address.";
+ type = types.str;
+ };
+
+ caFile = mkOption {
+ description = "${prefix} certificate authority file used to connect to kube-apiserver.";
+ type = types.nullOr types.path;
+ default = cfg.caFile;
+ };
+
+ certFile = mkOption {
+ description = "${prefix} client certificate file used to connect to kube-apiserver.";
+ type = types.nullOr types.path;
+ default = null;
+ };
+
+ keyFile = mkOption {
+ description = "${prefix} client key file used to connect to kube-apiserver.";
+ type = types.nullOr types.path;
+ default = null;
+ };
+ };
+in {
+
+ ###### interface
+
+ options.services.kubernetes = {
+ roles = mkOption {
+ description = ''
+ Kubernetes role that this machine should take.
+
+ Master role will enable etcd, apiserver, scheduler, controller manager
+ addon manager, flannel and proxy services.
+ Node role will enable flannel, docker, kubelet and proxy services.
+ '';
+ default = [];
+ type = types.listOf (types.enum ["master" "node"]);
+ };
+
+ package = mkOption {
+ description = "Kubernetes package to use.";
+ type = types.package;
+ default = pkgs.kubernetes;
+ defaultText = "pkgs.kubernetes";
+ };
+
+ kubeconfig = mkKubeConfigOptions "Default kubeconfig";
+
+ apiserverAddress = mkOption {
+ description = ''
+ Clusterwide accessible address for the kubernetes apiserver,
+ including protocol and optional port.
+ '';
+ example = "https://kubernetes-apiserver.example.com:6443";
+ type = types.str;
+ };
+
+ caFile = mkOption {
+ description = "Default kubernetes certificate authority";
+ type = types.nullOr types.path;
+ default = null;
+ };
+
+ dataDir = mkOption {
+ description = "Kubernetes root directory for managing kubelet files.";
+ default = "/var/lib/kubernetes";
+ type = types.path;
+ };
+
+ easyCerts = mkOption {
+ description = "Automatically setup x509 certificates and keys for the entire cluster.";
+ default = false;
+ type = types.bool;
+ };
+
+ featureGates = mkOption {
+ description = "List set of feature gates.";
+ default = [];
+ type = types.listOf types.str;
+ };
+
+ masterAddress = mkOption {
+ description = "Clusterwide available network address or hostname for the kubernetes master server.";
+ example = "master.example.com";
+ type = types.str;
+ };
+
+ path = mkOption {
+ description = "Packages added to the services' PATH environment variable. Both the bin and sbin subdirectories of each package are added.";
+ type = types.listOf types.package;
+ default = [];
+ };
+
+ clusterCidr = mkOption {
+ description = "Kubernetes controller manager and proxy CIDR Range for Pods in cluster.";
+ default = "10.1.0.0/16";
+ type = types.nullOr types.str;
+ };
+
+ lib = mkOption {
+ description = "Common functions for the kubernetes modules.";
+ default = {
+ inherit mkCert;
+ inherit mkKubeConfig;
+ inherit mkKubeConfigOptions;
+ };
+ type = types.attrs;
+ };
+
+ secretsPath = mkOption {
+ description = "Default location for kubernetes secrets. Not a store location.";
+ type = types.path;
+ default = cfg.dataDir + "/secrets";
+ };
+ };
+
+ ###### implementation
+
+ config = mkMerge [
+
+ (mkIf cfg.easyCerts {
+ services.kubernetes.pki.enable = mkDefault true;
+ services.kubernetes.caFile = caCert;
+ })
+
+ (mkIf (elem "master" cfg.roles) {
+ services.kubernetes.apiserver.enable = mkDefault true;
+ services.kubernetes.scheduler.enable = mkDefault true;
+ services.kubernetes.controllerManager.enable = mkDefault true;
+ services.kubernetes.addonManager.enable = mkDefault true;
+ services.kubernetes.proxy.enable = mkDefault true;
+ services.etcd.enable = true; # Cannot mkDefault because of flannel default options
+ services.kubernetes.kubelet = {
+ enable = mkDefault true;
+ taints = mkIf (!(elem "node" cfg.roles)) {
+ master = {
+ key = "node-role.kubernetes.io/master";
+ value = "true";
+ effect = "NoSchedule";
+ };
+ };
+ };
+ })
+
+
+ (mkIf (all (el: el == "master") cfg.roles) {
+ # if this node is only a master make it unschedulable by default
+ services.kubernetes.kubelet.unschedulable = mkDefault true;
+ })
+
+ (mkIf (elem "node" cfg.roles) {
+ services.kubernetes.kubelet.enable = mkDefault true;
+ services.kubernetes.proxy.enable = mkDefault true;
+ })
+
+ # Using "services.kubernetes.roles" will automatically enable easyCerts and flannel
+ (mkIf (cfg.roles != []) {
+ services.kubernetes.flannel.enable = mkDefault true;
+ services.flannel.etcd.endpoints = mkDefault etcdEndpoints;
+ services.kubernetes.easyCerts = mkDefault true;
+ })
+
+ (mkIf cfg.apiserver.enable {
+ services.kubernetes.pki.etcClusterAdminKubeconfig = mkDefault "kubernetes/cluster-admin.kubeconfig";
+ services.kubernetes.apiserver.etcd.servers = mkDefault etcdEndpoints;
+ })
+
+ (mkIf cfg.kubelet.enable {
+ virtualisation.docker = {
+ enable = mkDefault true;
+
+ # kubernetes needs access to logs
+ logDriver = mkDefault "json-file";
+
+ # iptables must be disabled for kubernetes
+ extraOptions = "--iptables=false --ip-masq=false";
+ };
+ })
+
+ (mkIf (cfg.apiserver.enable || cfg.controllerManager.enable) {
+ services.kubernetes.pki.certs = {
+ serviceAccount = mkCert {
+ name = "service-account";
+ CN = "system:service-account-signer";
+ action = ''
+ systemctl reload \
+ kube-apiserver.service \
+ kube-controller-manager.service
+ '';
+ };
+ };
+ })
+
+ (mkIf (
+ cfg.apiserver.enable ||
+ cfg.scheduler.enable ||
+ cfg.controllerManager.enable ||
+ cfg.kubelet.enable ||
+ cfg.proxy.enable ||
+ cfg.addonManager.enable
+ ) {
+ systemd.targets.kubernetes = {
+ description = "Kubernetes";
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ systemd.tmpfiles.rules = [
+ "d /opt/cni/bin 0755 root root -"
+ "d /run/kubernetes 0755 kubernetes kubernetes -"
+ "d /var/lib/kubernetes 0755 kubernetes kubernetes -"
+ ];
+
+ users.users = singleton {
+ name = "kubernetes";
+ uid = config.ids.uids.kubernetes;
+ description = "Kubernetes user";
+ extraGroups = [ "docker" ];
+ group = "kubernetes";
+ home = cfg.dataDir;
+ createHome = true;
+ };
+ users.groups.kubernetes.gid = config.ids.gids.kubernetes;
+
+ # dns addon is enabled by default
+ services.kubernetes.addons.dns.enable = mkDefault true;
+
+ services.kubernetes.apiserverAddress = mkDefault ("https://${if cfg.apiserver.advertiseAddress != null
+ then cfg.apiserver.advertiseAddress
+ else "${cfg.masterAddress}:${toString cfg.apiserver.securePort}"}");
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix
new file mode 100644
index 00000000000..d799e638fc9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ top = config.services.kubernetes;
+ cfg = top.flannel;
+
+ # we want flannel to use kubernetes itself as configuration backend, not direct etcd
+ storageBackend = "kubernetes";
+
+ # needed for flannel to pass options to docker
+ mkDockerOpts = pkgs.runCommand "mk-docker-opts" {
+ buildInputs = [ pkgs.makeWrapper ];
+ } ''
+ mkdir -p $out
+
+ # bashInteractive needed for `compgen`
+ makeWrapper ${pkgs.bashInteractive}/bin/bash $out/mk-docker-opts --add-flags "${pkgs.kubernetes}/bin/mk-docker-opts.sh"
+ '';
+in
+{
+ ###### interface
+ options.services.kubernetes.flannel = {
+ enable = mkEnableOption "enable flannel networking";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.flannel = {
+
+ enable = mkDefault true;
+ network = mkDefault top.clusterCidr;
+ inherit storageBackend;
+ nodeName = config.services.kubernetes.kubelet.hostname;
+ };
+
+ services.kubernetes.kubelet = {
+ networkPlugin = mkDefault "cni";
+ cni.config = mkDefault [{
+ name = "mynet";
+ type = "flannel";
+ delegate = {
+ isDefaultGateway = true;
+ bridge = "docker0";
+ };
+ }];
+ };
+
+ systemd.services.mk-docker-opts = {
+ description = "Pre-Docker Actions";
+ path = with pkgs; [ gawk gnugrep ];
+ script = ''
+ ${mkDockerOpts}/mk-docker-opts -d /run/flannel/docker
+ systemctl restart docker
+ '';
+ serviceConfig.Type = "oneshot";
+ };
+
+ systemd.paths.flannel-subnet-env = {
+ wantedBy = [ "flannel.service" ];
+ pathConfig = {
+ PathModified = "/run/flannel/subnet.env";
+ Unit = "mk-docker-opts.service";
+ };
+ };
+
+ systemd.services.docker = {
+ environment.DOCKER_OPTS = "-b none";
+ serviceConfig.EnvironmentFile = "-/run/flannel/docker";
+ };
+
+ # read environment variables generated by mk-docker-opts
+ virtualisation.docker.extraOptions = "$DOCKER_OPTS";
+
+ networking = {
+ firewall.allowedUDPPorts = [
+ 8285 # flannel udp
+ 8472 # flannel vxlan
+ ];
+ dhcpcd.denyInterfaces = [ "docker*" "flannel*" ];
+ };
+
+ services.kubernetes.pki.certs = {
+ flannelClient = top.lib.mkCert {
+ name = "flannel-client";
+ CN = "flannel-client";
+ action = "systemctl restart flannel.service";
+ };
+ };
+
+ # give flannel som kubernetes rbac permissions if applicable
+ services.kubernetes.addonManager.bootstrapAddons = mkIf ((storageBackend == "kubernetes") && (elem "RBAC" top.apiserver.authorizationMode)) {
+
+ flannel-cr = {
+ apiVersion = "rbac.authorization.k8s.io/v1beta1";
+ kind = "ClusterRole";
+ metadata = { name = "flannel"; };
+ rules = [{
+ apiGroups = [ "" ];
+ resources = [ "pods" ];
+ verbs = [ "get" ];
+ }
+ {
+ apiGroups = [ "" ];
+ resources = [ "nodes" ];
+ verbs = [ "list" "watch" ];
+ }
+ {
+ apiGroups = [ "" ];
+ resources = [ "nodes/status" ];
+ verbs = [ "patch" ];
+ }];
+ };
+
+ flannel-crb = {
+ apiVersion = "rbac.authorization.k8s.io/v1beta1";
+ kind = "ClusterRoleBinding";
+ metadata = { name = "flannel"; };
+ roleRef = {
+ apiGroup = "rbac.authorization.k8s.io";
+ kind = "ClusterRole";
+ name = "flannel";
+ };
+ subjects = [{
+ kind = "User";
+ name = "flannel-client";
+ }];
+ };
+
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix
new file mode 100644
index 00000000000..250da4c807e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -0,0 +1,344 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ top = config.services.kubernetes;
+ cfg = top.kubelet;
+
+ cniConfig =
+ if cfg.cni.config != [] && cfg.cni.configDir != null then
+ throw "Verbatim CNI-config and CNI configDir cannot both be set."
+ else if cfg.cni.configDir != null then
+ cfg.cni.configDir
+ else
+ (pkgs.buildEnv {
+ name = "kubernetes-cni-config";
+ paths = imap (i: entry:
+ pkgs.writeTextDir "${toString (10+i)}-${entry.type}.conf" (builtins.toJSON entry)
+ ) cfg.cni.config;
+ });
+
+ infraContainer = pkgs.dockerTools.buildImage {
+ name = "pause";
+ tag = "latest";
+ contents = top.package.pause;
+ config.Cmd = "/bin/pause";
+ };
+
+ kubeconfig = top.lib.mkKubeConfig "kubelet" cfg.kubeconfig;
+
+ manifestPath = "kubernetes/manifests";
+
+ taintOptions = with lib.types; { name, ... }: {
+ options = {
+ key = mkOption {
+ description = "Key of taint.";
+ default = name;
+ type = str;
+ };
+ value = mkOption {
+ description = "Value of taint.";
+ type = str;
+ };
+ effect = mkOption {
+ description = "Effect of taint.";
+ example = "NoSchedule";
+ type = enum ["NoSchedule" "PreferNoSchedule" "NoExecute"];
+ };
+ };
+ };
+
+ taints = concatMapStringsSep "," (v: "${v.key}=${v.value}:${v.effect}") (mapAttrsToList (n: v: v) cfg.taints);
+in
+{
+ ###### interface
+ options.services.kubernetes.kubelet = with lib.types; {
+
+ address = mkOption {
+ description = "Kubernetes kubelet info server listening address.";
+ default = "0.0.0.0";
+ type = str;
+ };
+
+ clusterDns = mkOption {
+ description = "Use alternative DNS.";
+ default = "10.1.0.1";
+ type = str;
+ };
+
+ clusterDomain = mkOption {
+ description = "Use alternative domain.";
+ default = config.services.kubernetes.addons.dns.clusterDomain;
+ type = str;
+ };
+
+ clientCaFile = mkOption {
+ description = "Kubernetes apiserver CA file for client authentication.";
+ default = top.caFile;
+ type = nullOr path;
+ };
+
+ cni = {
+ packages = mkOption {
+ description = "List of network plugin packages to install.";
+ type = listOf package;
+ default = [];
+ };
+
+ config = mkOption {
+ description = "Kubernetes CNI configuration.";
+ type = listOf attrs;
+ default = [];
+ example = literalExample ''
+ [{
+ "cniVersion": "0.2.0",
+ "name": "mynet",
+ "type": "bridge",
+ "bridge": "cni0",
+ "isGateway": true,
+ "ipMasq": true,
+ "ipam": {
+ "type": "host-local",
+ "subnet": "10.22.0.0/16",
+ "routes": [
+ { "dst": "0.0.0.0/0" }
+ ]
+ }
+ } {
+ "cniVersion": "0.2.0",
+ "type": "loopback"
+ }]
+ '';
+ };
+
+ configDir = mkOption {
+ description = "Path to Kubernetes CNI configuration directory.";
+ type = nullOr path;
+ default = null;
+ };
+ };
+
+ enable = mkEnableOption "Kubernetes kubelet.";
+
+ extraOpts = mkOption {
+ description = "Kubernetes kubelet extra command line options.";
+ default = "";
+ type = str;
+ };
+
+ featureGates = mkOption {
+ description = "List set of feature gates";
+ default = top.featureGates;
+ type = listOf str;
+ };
+
+ healthz = {
+ bind = mkOption {
+ description = "Kubernetes kubelet healthz listening address.";
+ default = "127.0.0.1";
+ type = str;
+ };
+
+ port = mkOption {
+ description = "Kubernetes kubelet healthz port.";
+ default = 10248;
+ type = int;
+ };
+ };
+
+ hostname = mkOption {
+ description = "Kubernetes kubelet hostname override.";
+ default = config.networking.hostName;
+ type = str;
+ };
+
+ kubeconfig = top.lib.mkKubeConfigOptions "Kubelet";
+
+ manifests = mkOption {
+ description = "List of manifests to bootstrap with kubelet (only pods can be created as manifest entry)";
+ type = attrsOf attrs;
+ default = {};
+ };
+
+ networkPlugin = mkOption {
+ description = "Network plugin to use by Kubernetes.";
+ type = nullOr (enum ["cni" "kubenet"]);
+ default = "kubenet";
+ };
+
+ nodeIp = mkOption {
+ description = "IP address of the node. If set, kubelet will use this IP address for the node.";
+ default = null;
+ type = nullOr str;
+ };
+
+ registerNode = mkOption {
+ description = "Whether to auto register kubelet with API server.";
+ default = true;
+ type = bool;
+ };
+
+ port = mkOption {
+ description = "Kubernetes kubelet info server listening port.";
+ default = 10250;
+ type = int;
+ };
+
+ seedDockerImages = mkOption {
+ description = "List of docker images to preload on system";
+ default = [];
+ type = listOf package;
+ };
+
+ taints = mkOption {
+ description = "Node taints (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/).";
+ default = {};
+ type = attrsOf (submodule [ taintOptions ]);
+ };
+
+ tlsCertFile = mkOption {
+ description = "File containing x509 Certificate for HTTPS.";
+ default = null;
+ type = nullOr path;
+ };
+
+ tlsKeyFile = mkOption {
+ description = "File containing x509 private key matching tlsCertFile.";
+ default = null;
+ type = nullOr path;
+ };
+
+ unschedulable = mkOption {
+ description = "Whether to set node taint to unschedulable=true as it is the case of node that has only master role.";
+ default = false;
+ type = bool;
+ };
+
+ verbosity = mkOption {
+ description = ''
+ Optional glog verbosity level for logging statements. See
+ <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+ '';
+ default = null;
+ type = nullOr int;
+ };
+
+ };
+
+ ###### implementation
+ config = mkMerge [
+ (mkIf cfg.enable {
+ services.kubernetes.kubelet.seedDockerImages = [infraContainer];
+
+ systemd.services.kubelet = {
+ description = "Kubernetes Kubelet Service";
+ wantedBy = [ "kubernetes.target" ];
+ after = [ "network.target" "docker.service" "kube-apiserver.service" ];
+ path = with pkgs; [ gitMinimal openssh docker utillinux iproute ethtool thin-provisioning-tools iptables socat ] ++ top.path;
+ preStart = ''
+ ${concatMapStrings (img: ''
+ echo "Seeding docker image: ${img}"
+ docker load <${img}
+ '') cfg.seedDockerImages}
+
+ rm /opt/cni/bin/* || true
+ ${concatMapStrings (package: ''
+ echo "Linking cni package: ${package}"
+ ln -fs ${package}/bin/* /opt/cni/bin
+ '') cfg.cni.packages}
+ '';
+ serviceConfig = {
+ Slice = "kubernetes.slice";
+ CPUAccounting = true;
+ MemoryAccounting = true;
+ Restart = "on-failure";
+ RestartSec = "1000ms";
+ ExecStart = ''${top.package}/bin/kubelet \
+ --address=${cfg.address} \
+ --authentication-token-webhook \
+ --authentication-token-webhook-cache-ttl="10s" \
+ --authorization-mode=Webhook \
+ ${optionalString (cfg.clientCaFile != null)
+ "--client-ca-file=${cfg.clientCaFile}"} \
+ ${optionalString (cfg.clusterDns != "")
+ "--cluster-dns=${cfg.clusterDns}"} \
+ ${optionalString (cfg.clusterDomain != "")
+ "--cluster-domain=${cfg.clusterDomain}"} \
+ --cni-conf-dir=${cniConfig} \
+ ${optionalString (cfg.featureGates != [])
+ "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+ --hairpin-mode=hairpin-veth \
+ --healthz-bind-address=${cfg.healthz.bind} \
+ --healthz-port=${toString cfg.healthz.port} \
+ --hostname-override=${cfg.hostname} \
+ --kubeconfig=${kubeconfig} \
+ ${optionalString (cfg.networkPlugin != null)
+ "--network-plugin=${cfg.networkPlugin}"} \
+ ${optionalString (cfg.nodeIp != null)
+ "--node-ip=${cfg.nodeIp}"} \
+ --pod-infra-container-image=pause \
+ ${optionalString (cfg.manifests != {})
+ "--pod-manifest-path=/etc/${manifestPath}"} \
+ --port=${toString cfg.port} \
+ --register-node=${boolToString cfg.registerNode} \
+ ${optionalString (taints != "")
+ "--register-with-taints=${taints}"} \
+ --root-dir=${top.dataDir} \
+ ${optionalString (cfg.tlsCertFile != null)
+ "--tls-cert-file=${cfg.tlsCertFile}"} \
+ ${optionalString (cfg.tlsKeyFile != null)
+ "--tls-private-key-file=${cfg.tlsKeyFile}"} \
+ ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+ ${cfg.extraOpts}
+ '';
+ WorkingDirectory = top.dataDir;
+ };
+ };
+
+ # Allways include cni plugins
+ services.kubernetes.kubelet.cni.packages = [pkgs.cni-plugins];
+
+ boot.kernelModules = ["br_netfilter"];
+
+ services.kubernetes.kubelet.hostname = with config.networking;
+ mkDefault (hostName + optionalString (domain != null) ".${domain}");
+
+ services.kubernetes.pki.certs = with top.lib; {
+ kubelet = mkCert {
+ name = "kubelet";
+ CN = top.kubelet.hostname;
+ action = "systemctl restart kubelet.service";
+
+ };
+ kubeletClient = mkCert {
+ name = "kubelet-client";
+ CN = "system:node:${top.kubelet.hostname}";
+ fields = {
+ O = "system:nodes";
+ };
+ action = "systemctl restart kubelet.service";
+ };
+ };
+
+ services.kubernetes.kubelet.kubeconfig.server = mkDefault top.apiserverAddress;
+ })
+
+ (mkIf (cfg.enable && cfg.manifests != {}) {
+ environment.etc = mapAttrs' (name: manifest:
+ nameValuePair "${manifestPath}/${name}.json" {
+ text = builtins.toJSON manifest;
+ mode = "0755";
+ }
+ ) cfg.manifests;
+ })
+
+ (mkIf (cfg.unschedulable && cfg.enable) {
+ services.kubernetes.kubelet.taints.unschedulable = {
+ value = "true";
+ effect = "NoSchedule";
+ };
+ })
+
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix
new file mode 100644
index 00000000000..733479e24c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -0,0 +1,390 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ top = config.services.kubernetes;
+ cfg = top.pki;
+
+ csrCA = pkgs.writeText "kube-pki-cacert-csr.json" (builtins.toJSON {
+ key = {
+ algo = "rsa";
+ size = 2048;
+ };
+ names = singleton cfg.caSpec;
+ });
+
+ csrCfssl = pkgs.writeText "kube-pki-cfssl-csr.json" (builtins.toJSON {
+ key = {
+ algo = "rsa";
+ size = 2048;
+ };
+ CN = top.masterAddress;
+ });
+
+ cfsslAPITokenBaseName = "apitoken.secret";
+ cfsslAPITokenPath = "${config.services.cfssl.dataDir}/${cfsslAPITokenBaseName}";
+ certmgrAPITokenPath = "${top.secretsPath}/${cfsslAPITokenBaseName}";
+ cfsslAPITokenLength = 32;
+
+ clusterAdminKubeconfig = with cfg.certs.clusterAdmin;
+ top.lib.mkKubeConfig "cluster-admin" {
+ server = top.apiserverAddress;
+ certFile = cert;
+ keyFile = key;
+ };
+
+ remote = with config.services; "https://${kubernetes.masterAddress}:${toString cfssl.port}";
+in
+{
+ ###### interface
+ options.services.kubernetes.pki = with lib.types; {
+
+ enable = mkEnableOption "easyCert issuer service";
+
+ certs = mkOption {
+ description = "List of certificate specs to feed to cert generator.";
+ default = {};
+ type = attrs;
+ };
+
+ genCfsslCACert = mkOption {
+ description = ''
+ Whether to automatically generate cfssl CA certificate and key,
+ if they don't exist.
+ '';
+ default = true;
+ type = bool;
+ };
+
+ genCfsslAPICerts = mkOption {
+ description = ''
+ Whether to automatically generate cfssl API webserver TLS cert and key,
+ if they don't exist.
+ '';
+ default = true;
+ type = bool;
+ };
+
+ genCfsslAPIToken = mkOption {
+ description = ''
+ Whether to automatically generate cfssl API-token secret,
+ if they doesn't exist.
+ '';
+ default = true;
+ type = bool;
+ };
+
+ pkiTrustOnBootstrap = mkOption {
+ description = "Whether to always trust remote cfssl server upon initial PKI bootstrap.";
+ default = true;
+ type = bool;
+ };
+
+ caCertPathPrefix = mkOption {
+ description = ''
+ Path-prefrix for the CA-certificate to be used for cfssl signing.
+ Suffixes ".pem" and "-key.pem" will be automatically appended for
+ the public and private keys respectively.
+ '';
+ default = "${config.services.cfssl.dataDir}/ca";
+ type = str;
+ };
+
+ caSpec = mkOption {
+ description = "Certificate specification for the auto-generated CAcert.";
+ default = {
+ CN = "kubernetes-cluster-ca";
+ O = "NixOS";
+ OU = "services.kubernetes.pki.caSpec";
+ L = "auto-generated";
+ };
+ type = attrs;
+ };
+
+ etcClusterAdminKubeconfig = mkOption {
+ description = ''
+ Symlink a kubeconfig with cluster-admin privileges to environment path
+ (/etc/&lt;path&gt;).
+ '';
+ default = null;
+ type = nullOr str;
+ };
+
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable
+ (let
+ cfsslCertPathPrefix = "${config.services.cfssl.dataDir}/cfssl";
+ cfsslCert = "${cfsslCertPathPrefix}.pem";
+ cfsslKey = "${cfsslCertPathPrefix}-key.pem";
+ in
+ {
+
+ services.cfssl = mkIf (top.apiserver.enable) {
+ enable = true;
+ address = "0.0.0.0";
+ tlsCert = cfsslCert;
+ tlsKey = cfsslKey;
+ configFile = toString (pkgs.writeText "cfssl-config.json" (builtins.toJSON {
+ signing = {
+ profiles = {
+ default = {
+ usages = ["digital signature"];
+ auth_key = "default";
+ expiry = "720h";
+ };
+ };
+ };
+ auth_keys = {
+ default = {
+ type = "standard";
+ key = "file:${cfsslAPITokenPath}";
+ };
+ };
+ }));
+ };
+
+ systemd.services.cfssl.preStart = with pkgs; with config.services.cfssl; mkIf (top.apiserver.enable)
+ (concatStringsSep "\n" [
+ "set -e"
+ (optionalString cfg.genCfsslCACert ''
+ if [ ! -f "${cfg.caCertPathPrefix}.pem" ]; then
+ ${cfssl}/bin/cfssl genkey -initca ${csrCA} | \
+ ${cfssl}/bin/cfssljson -bare ${cfg.caCertPathPrefix}
+ fi
+ '')
+ (optionalString cfg.genCfsslAPICerts ''
+ if [ ! -f "${dataDir}/cfssl.pem" ]; then
+ ${cfssl}/bin/cfssl gencert -ca "${cfg.caCertPathPrefix}.pem" -ca-key "${cfg.caCertPathPrefix}-key.pem" ${csrCfssl} | \
+ ${cfssl}/bin/cfssljson -bare ${cfsslCertPathPrefix}
+ fi
+ '')
+ (optionalString cfg.genCfsslAPIToken ''
+ if [ ! -f "${cfsslAPITokenPath}" ]; then
+ head -c ${toString (cfsslAPITokenLength / 2)} /dev/urandom | od -An -t x | tr -d ' ' >"${cfsslAPITokenPath}"
+ fi
+ chown cfssl "${cfsslAPITokenPath}" && chmod 400 "${cfsslAPITokenPath}"
+ '')]);
+
+ systemd.services.kube-certmgr-bootstrap = {
+ description = "Kubernetes certmgr bootstrapper";
+ wantedBy = [ "certmgr.service" ];
+ after = [ "cfssl.target" ];
+ script = concatStringsSep "\n" [''
+ set -e
+
+ # If there's a cfssl (cert issuer) running locally, then don't rely on user to
+ # manually paste it in place. Just symlink.
+ # otherwise, create the target file, ready for users to insert the token
+
+ if [ -f "${cfsslAPITokenPath}" ]; then
+ ln -fs "${cfsslAPITokenPath}" "${certmgrAPITokenPath}"
+ else
+ touch "${certmgrAPITokenPath}" && chmod 600 "${certmgrAPITokenPath}"
+ fi
+ ''
+ (optionalString (cfg.pkiTrustOnBootstrap) ''
+ if [ ! -f "${top.caFile}" ] || [ $(cat "${top.caFile}" | wc -c) -lt 1 ]; then
+ ${pkgs.curl}/bin/curl --fail-early -f -kd '{}' ${remote}/api/v1/cfssl/info | \
+ ${pkgs.cfssl}/bin/cfssljson -stdout >${top.caFile}
+ fi
+ '')
+ ];
+ serviceConfig = {
+ RestartSec = "10s";
+ Restart = "on-failure";
+ };
+ };
+
+ services.certmgr = {
+ enable = true;
+ package = pkgs.certmgr-selfsigned;
+ svcManager = "command";
+ specs =
+ let
+ mkSpec = _: cert: {
+ inherit (cert) action;
+ authority = {
+ inherit remote;
+ file.path = cert.caCert;
+ root_ca = cert.caCert;
+ profile = "default";
+ auth_key_file = certmgrAPITokenPath;
+ };
+ certificate = {
+ path = cert.cert;
+ };
+ private_key = cert.privateKeyOptions;
+ request = {
+ inherit (cert) CN hosts;
+ key = {
+ algo = "rsa";
+ size = 2048;
+ };
+ names = [ cert.fields ];
+ };
+ };
+ in
+ mapAttrs mkSpec cfg.certs;
+ };
+
+ #TODO: Get rid of kube-addon-manager in the future for the following reasons
+ # - it is basically just a shell script wrapped around kubectl
+ # - it assumes that it is clusterAdmin or can gain clusterAdmin rights through serviceAccount
+ # - it is designed to be used with k8s system components only
+ # - it would be better with a more Nix-oriented way of managing addons
+ systemd.services.kube-addon-manager = mkIf top.addonManager.enable (mkMerge [{
+ environment.KUBECONFIG = with cfg.certs.addonManager;
+ top.lib.mkKubeConfig "addon-manager" {
+ server = top.apiserverAddress;
+ certFile = cert;
+ keyFile = key;
+ };
+ }
+
+ (optionalAttrs (top.addonManager.bootstrapAddons != {}) {
+ serviceConfig.PermissionsStartOnly = true;
+ preStart = with pkgs;
+ let
+ files = mapAttrsToList (n: v: writeText "${n}.json" (builtins.toJSON v))
+ top.addonManager.bootstrapAddons;
+ in
+ ''
+ export KUBECONFIG=${clusterAdminKubeconfig}
+ ${kubectl}/bin/kubectl apply -f ${concatStringsSep " \\\n -f " files}
+ '';
+ })]);
+
+ environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (!isNull cfg.etcClusterAdminKubeconfig)
+ clusterAdminKubeconfig;
+
+ environment.systemPackages = mkIf (top.kubelet.enable || top.proxy.enable) [
+ (pkgs.writeScriptBin "nixos-kubernetes-node-join" ''
+ set -e
+ exec 1>&2
+
+ if [ $# -gt 0 ]; then
+ echo "Usage: $(basename $0)"
+ echo ""
+ echo "No args. Apitoken must be provided on stdin."
+ echo "To get the apitoken, execute: 'sudo cat ${certmgrAPITokenPath}' on the master node."
+ exit 1
+ fi
+
+ if [ $(id -u) != 0 ]; then
+ echo "Run as root please."
+ exit 1
+ fi
+
+ read -r token
+ if [ ''${#token} != ${toString cfsslAPITokenLength} ]; then
+ echo "Token must be of length ${toString cfsslAPITokenLength}."
+ exit 1
+ fi
+
+ echo $token > ${certmgrAPITokenPath}
+ chmod 600 ${certmgrAPITokenPath}
+
+ echo "Restarting certmgr..." >&1
+ systemctl restart certmgr
+
+ echo "Waiting for certs to appear..." >&1
+
+ ${optionalString top.kubelet.enable ''
+ while [ ! -f ${cfg.certs.kubelet.cert} ]; do sleep 1; done
+ echo "Restarting kubelet..." >&1
+ systemctl restart kubelet
+ ''}
+
+ ${optionalString top.proxy.enable ''
+ while [ ! -f ${cfg.certs.kubeProxyClient.cert} ]; do sleep 1; done
+ echo "Restarting kube-proxy..." >&1
+ systemctl restart kube-proxy
+ ''}
+
+ ${optionalString top.flannel.enable ''
+ while [ ! -f ${cfg.certs.flannelClient.cert} ]; do sleep 1; done
+ echo "Restarting flannel..." >&1
+ systemctl restart flannel
+ ''}
+
+ echo "Node joined succesfully"
+ '')];
+
+ # isolate etcd on loopback at the master node
+ # easyCerts doesn't support multimaster clusters anyway atm.
+ services.etcd = with cfg.certs.etcd; {
+ listenClientUrls = ["https://127.0.0.1:2379"];
+ listenPeerUrls = ["https://127.0.0.1:2380"];
+ advertiseClientUrls = ["https://etcd.local:2379"];
+ initialCluster = ["${top.masterAddress}=https://etcd.local:2380"];
+ initialAdvertisePeerUrls = ["https://etcd.local:2380"];
+ certFile = mkDefault cert;
+ keyFile = mkDefault key;
+ trustedCaFile = mkDefault caCert;
+ };
+ networking.extraHosts = mkIf (config.services.etcd.enable) ''
+ 127.0.0.1 etcd.${top.addons.dns.clusterDomain} etcd.local
+ '';
+
+ services.flannel = with cfg.certs.flannelClient; {
+ kubeconfig = top.lib.mkKubeConfig "flannel" {
+ server = top.apiserverAddress;
+ certFile = cert;
+ keyFile = key;
+ };
+ };
+
+ services.kubernetes = {
+
+ apiserver = mkIf top.apiserver.enable (with cfg.certs.apiServer; {
+ etcd = with cfg.certs.apiserverEtcdClient; {
+ servers = ["https://etcd.local:2379"];
+ certFile = mkDefault cert;
+ keyFile = mkDefault key;
+ caFile = mkDefault caCert;
+ };
+ clientCaFile = mkDefault caCert;
+ tlsCertFile = mkDefault cert;
+ tlsKeyFile = mkDefault key;
+ serviceAccountKeyFile = mkDefault cfg.certs.serviceAccount.cert;
+ kubeletClientCaFile = mkDefault caCert;
+ kubeletClientCertFile = mkDefault cfg.certs.apiserverKubeletClient.cert;
+ kubeletClientKeyFile = mkDefault cfg.certs.apiserverKubeletClient.key;
+ proxyClientCertFile = mkDefault cfg.certs.apiserverProxyClient.cert;
+ proxyClientKeyFile = mkDefault cfg.certs.apiserverProxyClient.key;
+ });
+ controllerManager = mkIf top.controllerManager.enable {
+ serviceAccountKeyFile = mkDefault cfg.certs.serviceAccount.key;
+ rootCaFile = cfg.certs.controllerManagerClient.caCert;
+ kubeconfig = with cfg.certs.controllerManagerClient; {
+ certFile = mkDefault cert;
+ keyFile = mkDefault key;
+ };
+ };
+ scheduler = mkIf top.scheduler.enable {
+ kubeconfig = with cfg.certs.schedulerClient; {
+ certFile = mkDefault cert;
+ keyFile = mkDefault key;
+ };
+ };
+ kubelet = mkIf top.kubelet.enable {
+ clientCaFile = mkDefault cfg.certs.kubelet.caCert;
+ tlsCertFile = mkDefault cfg.certs.kubelet.cert;
+ tlsKeyFile = mkDefault cfg.certs.kubelet.key;
+ kubeconfig = with cfg.certs.kubeletClient; {
+ certFile = mkDefault cert;
+ keyFile = mkDefault key;
+ };
+ };
+ proxy = mkIf top.proxy.enable {
+ kubeconfig = with cfg.certs.kubeProxyClient; {
+ certFile = mkDefault cert;
+ keyFile = mkDefault key;
+ };
+ };
+ };
+ });
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix
new file mode 100644
index 00000000000..bd4bf04ea83
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ top = config.services.kubernetes;
+ cfg = top.proxy;
+in
+{
+
+ ###### interface
+ options.services.kubernetes.proxy = with lib.types; {
+
+ bindAddress = mkOption {
+ description = "Kubernetes proxy listening address.";
+ default = "0.0.0.0";
+ type = str;
+ };
+
+ enable = mkEnableOption "Kubernetes proxy";
+
+ extraOpts = mkOption {
+ description = "Kubernetes proxy extra command line options.";
+ default = "";
+ type = str;
+ };
+
+ featureGates = mkOption {
+ description = "List set of feature gates";
+ default = top.featureGates;
+ type = listOf str;
+ };
+
+ kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes proxy";
+
+ verbosity = mkOption {
+ description = ''
+ Optional glog verbosity level for logging statements. See
+ <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+ '';
+ default = null;
+ type = nullOr int;
+ };
+
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ systemd.services.kube-proxy = {
+ description = "Kubernetes Proxy Service";
+ wantedBy = [ "kubernetes.target" ];
+ after = [ "kube-apiserver.service" ];
+ path = with pkgs; [ iptables conntrack_tools ];
+ serviceConfig = {
+ Slice = "kubernetes.slice";
+ ExecStart = ''${top.package}/bin/kube-proxy \
+ --bind-address=${cfg.bindAddress} \
+ ${optionalString (top.clusterCidr!=null)
+ "--cluster-cidr=${top.clusterCidr}"} \
+ ${optionalString (cfg.featureGates != [])
+ "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+ --kubeconfig=${top.lib.mkKubeConfig "kube-proxy" cfg.kubeconfig} \
+ ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+ ${cfg.extraOpts}
+ '';
+ WorkingDirectory = top.dataDir;
+ Restart = "on-failure";
+ RestartSec = 5;
+ };
+ };
+
+ services.kubernetes.pki.certs = {
+ kubeProxyClient = top.lib.mkCert {
+ name = "kube-proxy-client";
+ CN = "system:kube-proxy";
+ action = "systemctl restart kube-proxy.service";
+ };
+ };
+
+ services.kubernetes.proxy.kubeconfig.server = mkDefault top.apiserverAddress;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix
new file mode 100644
index 00000000000..5f6113227d9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ top = config.services.kubernetes;
+ cfg = top.scheduler;
+in
+{
+ ###### interface
+ options.services.kubernetes.scheduler = with lib.types; {
+
+ address = mkOption {
+ description = "Kubernetes scheduler listening address.";
+ default = "127.0.0.1";
+ type = str;
+ };
+
+ enable = mkEnableOption "Kubernetes scheduler";
+
+ extraOpts = mkOption {
+ description = "Kubernetes scheduler extra command line options.";
+ default = "";
+ type = str;
+ };
+
+ featureGates = mkOption {
+ description = "List set of feature gates";
+ default = top.featureGates;
+ type = listOf str;
+ };
+
+ kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes scheduler";
+
+ leaderElect = mkOption {
+ description = "Whether to start leader election before executing main loop.";
+ type = bool;
+ default = true;
+ };
+
+ port = mkOption {
+ description = "Kubernetes scheduler listening port.";
+ default = 10251;
+ type = int;
+ };
+
+ verbosity = mkOption {
+ description = ''
+ Optional glog verbosity level for logging statements. See
+ <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+ '';
+ default = null;
+ type = nullOr int;
+ };
+
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ systemd.services.kube-scheduler = {
+ description = "Kubernetes Scheduler Service";
+ wantedBy = [ "kubernetes.target" ];
+ after = [ "kube-apiserver.service" ];
+ serviceConfig = {
+ Slice = "kubernetes.slice";
+ ExecStart = ''${top.package}/bin/kube-scheduler \
+ --address=${cfg.address} \
+ ${optionalString (cfg.featureGates != [])
+ "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+ --kubeconfig=${top.lib.mkKubeConfig "kube-scheduler" cfg.kubeconfig} \
+ --leader-elect=${boolToString cfg.leaderElect} \
+ --port=${toString cfg.port} \
+ ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+ ${cfg.extraOpts}
+ '';
+ WorkingDirectory = top.dataDir;
+ User = "kubernetes";
+ Group = "kubernetes";
+ Restart = "on-failure";
+ RestartSec = 5;
+ };
+ };
+
+ services.kubernetes.pki.certs = {
+ schedulerClient = top.lib.mkCert {
+ name = "kube-scheduler-client";
+ CN = "system:kube-scheduler";
+ action = "systemctl restart kube-scheduler.service";
+ };
+ };
+
+ services.kubernetes.scheduler.kubeconfig.server = mkDefault top.apiserverAddress;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/computing/boinc/client.nix b/nixpkgs/nixos/modules/services/computing/boinc/client.nix
new file mode 100644
index 00000000000..a7edac02538
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/computing/boinc/client.nix
@@ -0,0 +1,129 @@
+{config, lib, pkgs, ...}:
+
+with lib;
+
+let
+ cfg = config.services.boinc;
+ allowRemoteGuiRpcFlag = optionalString cfg.allowRemoteGuiRpc "--allow_remote_gui_rpc";
+
+ fhsEnv = pkgs.buildFHSUserEnv {
+ name = "boinc-fhs-env";
+ targetPkgs = pkgs': [ cfg.package ] ++ cfg.extraEnvPackages;
+ runScript = "/bin/boinc_client";
+ };
+ fhsEnvExecutable = "${fhsEnv}/bin/${fhsEnv.name}";
+
+in
+ {
+ options.services.boinc = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the BOINC distributed computing client. If this
+ option is set to true, the boinc_client daemon will be run as a
+ background service. The boinccmd command can be used to control the
+ daemon.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.boinc;
+ defaultText = "pkgs.boinc";
+ description = ''
+ Which BOINC package to use.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/boinc";
+ description = ''
+ The directory in which to store BOINC's configuration and data files.
+ '';
+ };
+
+ allowRemoteGuiRpc = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If set to true, any remote host can connect to and control this BOINC
+ client (subject to password authentication). If instead set to false,
+ only the hosts listed in <varname>dataDir</varname>/remote_hosts.cfg will be allowed to
+ connect.
+
+ See also: <link xlink:href="http://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access"/>
+ '';
+ };
+
+ extraEnvPackages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = "[ pkgs.virtualbox ]";
+ description = ''
+ Additional packages to make available in the environment in which
+ BOINC will run. Common choices are:
+ <variablelist>
+ <varlistentry>
+ <term><varname>pkgs.virtualbox</varname></term>
+ <listitem><para>
+ The VirtualBox virtual machine framework. Required by some BOINC
+ projects, such as ATLAS@home.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>pkgs.ocl-icd</varname></term>
+ <listitem><para>
+ OpenCL infrastructure library. Required by BOINC projects that
+ use OpenCL, in addition to a device-specific OpenCL driver.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>pkgs.linuxPackages.nvidia_x11</varname></term>
+ <listitem><para>
+ Provides CUDA libraries. Required by BOINC projects that use
+ CUDA. Note that this requires an NVIDIA graphics device to be
+ present on the system.
+ </para><para>
+ Also provides OpenCL drivers for NVIDIA GPUs;
+ <varname>pkgs.ocl-icd</varname> is also needed in this case.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [cfg.package];
+
+ users.users.boinc = {
+ createHome = false;
+ description = "BOINC Client";
+ home = cfg.dataDir;
+ isSystemUser = true;
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - boinc - - -"
+ ];
+
+ systemd.services.boinc = {
+ description = "BOINC Client";
+ after = ["network.target"];
+ wantedBy = ["multi-user.target"];
+ script = ''
+ ${fhsEnvExecutable} --dir ${cfg.dataDir} --redirectio ${allowRemoteGuiRpcFlag}
+ '';
+ serviceConfig = {
+ User = "boinc";
+ Nice = 10;
+ };
+ };
+ };
+
+ meta = {
+ maintainers = with lib.maintainers; [kierdavis];
+ };
+ }
diff --git a/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix b/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix
new file mode 100644
index 00000000000..d1a1383e45b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix
@@ -0,0 +1,371 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.slurm;
+ # configuration file can be generated by http://slurm.schedmd.com/configurator.html
+
+ defaultUser = "slurm";
+
+ configFile = pkgs.writeTextDir "slurm.conf"
+ ''
+ ClusterName=${cfg.clusterName}
+ StateSaveLocation=${cfg.stateSaveLocation}
+ SlurmUser=${cfg.user}
+ ${optionalString (cfg.controlMachine != null) ''controlMachine=${cfg.controlMachine}''}
+ ${optionalString (cfg.controlAddr != null) ''controlAddr=${cfg.controlAddr}''}
+ ${toString (map (x: "NodeName=${x}\n") cfg.nodeName)}
+ ${toString (map (x: "PartitionName=${x}\n") cfg.partitionName)}
+ PlugStackConfig=${plugStackConfig}
+ ProctrackType=${cfg.procTrackType}
+ ${cfg.extraConfig}
+ '';
+
+ plugStackConfig = pkgs.writeTextDir "plugstack.conf"
+ ''
+ ${optionalString cfg.enableSrunX11 ''optional ${pkgs.slurm-spank-x11}/lib/x11.so''}
+ ${cfg.extraPlugstackConfig}
+ '';
+
+ cgroupConfig = pkgs.writeTextDir "cgroup.conf"
+ ''
+ ${cfg.extraCgroupConfig}
+ '';
+
+ slurmdbdConf = pkgs.writeTextDir "slurmdbd.conf"
+ ''
+ DbdHost=${cfg.dbdserver.dbdHost}
+ SlurmUser=${cfg.user}
+ StorageType=accounting_storage/mysql
+ ${cfg.dbdserver.extraConfig}
+ '';
+
+ # slurm expects some additional config files to be
+ # in the same directory as slurm.conf
+ etcSlurm = pkgs.symlinkJoin {
+ name = "etc-slurm";
+ paths = [ configFile cgroupConfig plugStackConfig ] ++ cfg.extraConfigPaths;
+ };
+
+in
+
+{
+
+ ###### interface
+
+ meta.maintainers = [ maintainers.markuskowa ];
+
+ options = {
+
+ services.slurm = {
+
+ server = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Wether to enable the slurm control daemon.
+ Note that the standard authentication method is "munge".
+ The "munge" service needs to be provided with a password file in order for
+ slurm to work properly (see <literal>services.munge.password</literal>).
+ '';
+ };
+ };
+
+ dbdserver = {
+ enable = mkEnableOption "SlurmDBD service";
+
+ dbdHost = mkOption {
+ type = types.str;
+ default = config.networking.hostName;
+ description = ''
+ Hostname of the machine where <literal>slurmdbd</literal>
+ is running (i.e. name returned by <literal>hostname -s</literal>).
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration for <literal>slurmdbd.conf</literal>
+ '';
+ };
+ };
+
+ client = {
+ enable = mkEnableOption "slurm client daemon";
+ };
+
+ enableStools = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Wether to provide a slurm.conf file.
+ Enable this option if you do not run a slurm daemon on this host
+ (i.e. <literal>server.enable</literal> and <literal>client.enable</literal> are <literal>false</literal>)
+ but you still want to run slurm commands from this host.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.slurm;
+ defaultText = "pkgs.slurm";
+ example = literalExample "pkgs.slurm-full";
+ description = ''
+ The package to use for slurm binaries.
+ '';
+ };
+
+ controlMachine = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = null;
+ description = ''
+ The short hostname of the machine where SLURM control functions are
+ executed (i.e. the name returned by the command "hostname -s", use "tux001"
+ rather than "tux001.my.com").
+ '';
+ };
+
+ controlAddr = mkOption {
+ type = types.nullOr types.str;
+ default = cfg.controlMachine;
+ example = null;
+ description = ''
+ Name that ControlMachine should be referred to in establishing a
+ communications path.
+ '';
+ };
+
+ clusterName = mkOption {
+ type = types.str;
+ default = "default";
+ example = "myCluster";
+ description = ''
+ Necessary to distinguish accounting records in a multi-cluster environment.
+ '';
+ };
+
+ nodeName = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = literalExample ''[ "linux[1-32] CPUs=1 State=UNKNOWN" ];'';
+ description = ''
+ Name that SLURM uses to refer to a node (or base partition for BlueGene
+ systems). Typically this would be the string that "/bin/hostname -s"
+ returns. Note that now you have to write node's parameters after the name.
+ '';
+ };
+
+ partitionName = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = literalExample ''[ "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP" ];'';
+ description = ''
+ Name by which the partition may be referenced. Note that now you have
+ to write the partition's parameters after the name.
+ '';
+ };
+
+ enableSrunX11 = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If enabled srun will accept the option "--x11" to allow for X11 forwarding
+ from within an interactive session or a batch job. This activates the
+ slurm-spank-x11 module. Note that this option also enables
+ 'services.openssh.forwardX11' on the client.
+
+ This option requires slurm to be compiled without native X11 support.
+ '';
+ };
+
+ procTrackType = mkOption {
+ type = types.str;
+ default = "proctrack/linuxproc";
+ description = ''
+ Plugin to be used for process tracking on a job step basis.
+ The slurmd daemon uses this mechanism to identify all processes
+ which are children of processes it spawns for a user job step.
+ '';
+ };
+
+ stateSaveLocation = mkOption {
+ type = types.str;
+ default = "/var/spool/slurmctld";
+ description = ''
+ Directory into which the Slurm controller, slurmctld, saves its state.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = defaultUser;
+ description = ''
+ Set this option when you want to run the slurmctld daemon
+ as something else than the default slurm user "slurm".
+ Note that the UID of this user needs to be the same
+ on all nodes.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra configuration options that will be added verbatim at
+ the end of the slurm configuration file.
+ '';
+ };
+
+ extraPlugstackConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra configuration that will be added to the end of <literal>plugstack.conf</literal>.
+ '';
+ };
+
+ extraCgroupConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra configuration for <literal>cgroup.conf</literal>. This file is
+ used when <literal>procTrackType=proctrack/cgroup</literal>.
+ '';
+ };
+
+ extraConfigPaths = mkOption {
+ type = with types; listOf path;
+ default = [];
+ description = ''
+ Slurm expects config files for plugins in the same path
+ as <literal>slurm.conf</literal>. Add extra nix store
+ paths that should be merged into same directory as
+ <literal>slurm.conf</literal>.
+ '';
+ };
+
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config =
+ let
+ wrappedSlurm = pkgs.stdenv.mkDerivation {
+ name = "wrappedSlurm";
+
+ builder = pkgs.writeText "builder.sh" ''
+ source $stdenv/setup
+ mkdir -p $out/bin
+ find ${getBin cfg.package}/bin -type f -executable | while read EXE
+ do
+ exename="$(basename $EXE)"
+ wrappername="$out/bin/$exename"
+ cat > "$wrappername" <<EOT
+ #!/bin/sh
+ if [ -z "$SLURM_CONF" ]
+ then
+ SLURM_CONF="${etcSlurm}/slurm.conf" "$EXE" "\$@"
+ else
+ "$EXE" "\$0"
+ fi
+ EOT
+ chmod +x "$wrappername"
+ done
+
+ mkdir -p $out/share
+ ln -s ${getBin cfg.package}/share/man $out/share/man
+ '';
+ };
+
+ in mkIf ( cfg.enableStools ||
+ cfg.client.enable ||
+ cfg.server.enable ||
+ cfg.dbdserver.enable ) {
+
+ environment.systemPackages = [ wrappedSlurm ];
+
+ services.munge.enable = mkDefault true;
+
+ # use a static uid as default to ensure it is the same on all nodes
+ users.users.slurm = mkIf (cfg.user == defaultUser) {
+ name = defaultUser;
+ group = "slurm";
+ uid = config.ids.uids.slurm;
+ };
+
+ users.groups.slurm.gid = config.ids.uids.slurm;
+
+ systemd.services.slurmd = mkIf (cfg.client.enable) {
+ path = with pkgs; [ wrappedSlurm coreutils ]
+ ++ lib.optional cfg.enableSrunX11 slurm-spank-x11;
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "systemd-tmpfiles-clean.service" ];
+
+ serviceConfig = {
+ Type = "forking";
+ KillMode = "process";
+ ExecStart = "${wrappedSlurm}/bin/slurmd";
+ PIDFile = "/run/slurmd.pid";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+
+ preStart = ''
+ mkdir -p /var/spool
+ '';
+ };
+
+ services.openssh.forwardX11 = mkIf cfg.client.enable (mkDefault true);
+
+ systemd.services.slurmctld = mkIf (cfg.server.enable) {
+ path = with pkgs; [ wrappedSlurm munge coreutils ]
+ ++ lib.optional cfg.enableSrunX11 slurm-spank-x11;
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "munged.service" ];
+ requires = [ "munged.service" ];
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${wrappedSlurm}/bin/slurmctld";
+ PIDFile = "/run/slurmctld.pid";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+
+ preStart = ''
+ mkdir -p ${cfg.stateSaveLocation}
+ chown -R ${cfg.user}:slurm ${cfg.stateSaveLocation}
+ '';
+ };
+
+ systemd.services.slurmdbd = mkIf (cfg.dbdserver.enable) {
+ path = with pkgs; [ wrappedSlurm munge coreutils ];
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "munged.service" "mysql.service" ];
+ requires = [ "munged.service" "mysql.service" ];
+
+ # slurm strips the last component off the path
+ environment.SLURM_CONF = "${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/nixpkgs/nixos/modules/services/computing/torque/mom.nix b/nixpkgs/nixos/modules/services/computing/torque/mom.nix
new file mode 100644
index 00000000000..83772539a7a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/computing/torque/mom.nix
@@ -0,0 +1,63 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.torque.mom;
+ torque = pkgs.torque;
+
+ momConfig = pkgs.writeText "torque-mom-config" ''
+ $pbsserver ${cfg.serverNode}
+ $logevent 225
+ '';
+
+in
+{
+ options = {
+
+ services.torque.mom = {
+ enable = mkEnableOption "torque computing node";
+
+ serverNode = mkOption {
+ type = types.str;
+ description = "Hostname running pbs server.";
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.torque ];
+
+ systemd.services.torque-mom-init = {
+ path = with pkgs; [ torque utillinux procps inetutils ];
+
+ script = ''
+ pbs_mkdirs -v aux
+ pbs_mkdirs -v mom
+ hostname > /var/spool/torque/server_name
+ cp -v ${momConfig} /var/spool/torque/mom_priv/config
+ '';
+
+ serviceConfig.Type = "oneshot";
+ unitConfig.ConditionPathExists = "!/var/spool/torque";
+ };
+
+ systemd.services.torque-mom = {
+ path = [ torque ];
+
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "torque-mom-init.service" ];
+ after = [ "torque-mom-init.service" "network.target" ];
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${torque}/bin/pbs_mom";
+ PIDFile = "/var/spool/torque/mom_priv/mom.lock";
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/computing/torque/server.nix b/nixpkgs/nixos/modules/services/computing/torque/server.nix
new file mode 100644
index 00000000000..655d1500497
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/computing/torque/server.nix
@@ -0,0 +1,96 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.torque.server;
+ torque = pkgs.torque;
+in
+{
+ options = {
+
+ services.torque.server = {
+
+ enable = mkEnableOption "torque server";
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.torque ];
+
+ systemd.services.torque-server-init = {
+ path = with pkgs; [ torque utillinux procps inetutils ];
+
+ script = ''
+ tmpsetup=$(mktemp -t torque-XXXX)
+ cp -p ${torque}/bin/torque.setup $tmpsetup
+ sed -i $tmpsetup -e 's/pbs_server -t create/pbs_server -f -t create/'
+
+ pbs_mkdirs -v aux
+ pbs_mkdirs -v server
+ hostname > /var/spool/torque/server_name
+ cp -prv ${torque}/var/spool/torque/* /var/spool/torque/
+ $tmpsetup root
+
+ sleep 1
+ rm -f $tmpsetup
+ kill $(pgrep pbs_server) 2>/dev/null
+ kill $(pgrep trqauthd) 2>/dev/null
+ '';
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+
+ unitConfig = {
+ ConditionPathExists = "!/var/spool/torque";
+ };
+ };
+
+ systemd.services.trqauthd = {
+ path = [ torque ];
+
+ requires = [ "torque-server-init.service" ];
+ after = [ "torque-server-init.service" ];
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${torque}/bin/trqauthd";
+ };
+ };
+
+ systemd.services.torque-server = {
+ path = [ torque ];
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "torque-scheduler.service" "trqauthd.service" ];
+ before = [ "trqauthd.service" ];
+ requires = [ "torque-server-init.service" ];
+ after = [ "torque-server-init.service" "network.target" ];
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${torque}/bin/pbs_server";
+ ExecStop = "${torque}/bin/qterm";
+ PIDFile = "/var/spool/torque/server_priv/server.lock";
+ };
+ };
+
+ systemd.services.torque-scheduler = {
+ path = [ torque ];
+
+ requires = [ "torque-server-init.service" ];
+ after = [ "torque-server-init.service" ];
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${torque}/bin/pbs_sched";
+ PIDFile = "/var/spool/torque/sched_priv/sched.lock";
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix
new file mode 100644
index 00000000000..9c615fbe885
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -0,0 +1,267 @@
+# NixOS module for Buildbot continous integration server.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.buildbot-master;
+
+ python = cfg.package.pythonModule;
+
+ escapeStr = s: escape ["'"] s;
+
+ defaultMasterCfg = pkgs.writeText "master.cfg" ''
+ from buildbot.plugins import *
+ factory = util.BuildFactory()
+ c = BuildmasterConfig = dict(
+ workers = [${concatStringsSep "," cfg.workers}],
+ protocols = { 'pb': {'port': ${toString cfg.bpPort} } },
+ title = '${escapeStr cfg.title}',
+ titleURL = '${escapeStr cfg.titleUrl}',
+ buildbotURL = '${escapeStr cfg.buildbotUrl}',
+ db = dict(db_url='${escapeStr cfg.dbUrl}'),
+ www = dict(port=${toString cfg.port}),
+ change_source = [ ${concatStringsSep "," cfg.changeSource} ],
+ schedulers = [ ${concatStringsSep "," cfg.schedulers} ],
+ builders = [ ${concatStringsSep "," cfg.builders} ],
+ status = [ ${concatStringsSep "," cfg.status} ],
+ )
+ for step in [ ${concatStringsSep "," cfg.factorySteps} ]:
+ factory.addStep(step)
+
+ ${cfg.extraConfig}
+ '';
+
+ tacFile = pkgs.writeText "buildbot-master.tac" ''
+ import os
+
+ from twisted.application import service
+ from buildbot.master import BuildMaster
+
+ basedir = '${cfg.buildbotDir}'
+
+ configfile = '${cfg.masterCfg}'
+
+ # Default umask for server
+ umask = None
+
+ # note: this line is matched against to check that this is a buildmaster
+ # directory; do not edit it.
+ application = service.Application('buildmaster')
+
+ m = BuildMaster(basedir, configfile, umask)
+ m.setServiceParent(application)
+ '';
+
+in {
+ options = {
+ services.buildbot-master = {
+
+ factorySteps = mkOption {
+ type = types.listOf types.str;
+ description = "Factory Steps";
+ default = [];
+ example = [
+ "steps.Git(repourl='git://github.com/buildbot/pyflakes.git', mode='incremental')"
+ "steps.ShellCommand(command=['trial', 'pyflakes'])"
+ ];
+ };
+
+ changeSource = mkOption {
+ type = types.listOf types.str;
+ description = "List of Change Sources.";
+ default = [];
+ example = [
+ "changes.GitPoller('git://github.com/buildbot/pyflakes.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
+ ];
+ };
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Buildbot continuous integration server.";
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ description = "Extra configuration to append to master.cfg";
+ default = "c['buildbotNetUsageData'] = None";
+ };
+
+ masterCfg = mkOption {
+ type = types.path;
+ description = "Optionally pass master.cfg path. Other options in this configuration will be ignored.";
+ default = defaultMasterCfg;
+ example = "/etc/nixos/buildbot/master.cfg";
+ };
+
+ schedulers = mkOption {
+ type = types.listOf types.str;
+ description = "List of Schedulers.";
+ default = [
+ "schedulers.SingleBranchScheduler(name='all', change_filter=util.ChangeFilter(branch='master'), treeStableTimer=None, builderNames=['runtests'])"
+ "schedulers.ForceScheduler(name='force',builderNames=['runtests'])"
+ ];
+ };
+
+ builders = mkOption {
+ type = types.listOf types.str;
+ description = "List of Builders.";
+ default = [
+ "util.BuilderConfig(name='runtests',workernames=['example-worker'],factory=factory)"
+ ];
+ };
+
+ workers = mkOption {
+ type = types.listOf types.str;
+ description = "List of Workers.";
+ default = [ "worker.Worker('example-worker', 'pass')" ];
+ };
+
+ status = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = "List of status notification endpoints.";
+ };
+
+ user = mkOption {
+ default = "buildbot";
+ type = types.str;
+ description = "User the buildbot server should execute under.";
+ };
+
+ group = mkOption {
+ default = "buildbot";
+ type = types.str;
+ description = "Primary group of buildbot user.";
+ };
+
+ extraGroups = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "List of extra groups that the buildbot user should be a part of.";
+ };
+
+ home = mkOption {
+ default = "/home/buildbot";
+ type = types.path;
+ description = "Buildbot home directory.";
+ };
+
+ buildbotDir = mkOption {
+ default = "${cfg.home}/master";
+ type = types.path;
+ description = "Specifies the Buildbot directory.";
+ };
+
+ bpPort = mkOption {
+ default = 9989;
+ type = types.int;
+ description = "Port where the master will listen to Buildbot Worker.";
+ };
+
+ listenAddress = mkOption {
+ default = "0.0.0.0";
+ type = types.str;
+ description = "Specifies the bind address on which the buildbot HTTP interface listens.";
+ };
+
+ buildbotUrl = mkOption {
+ default = "http://localhost:8010/";
+ type = types.str;
+ description = "Specifies the Buildbot URL.";
+ };
+
+ title = mkOption {
+ default = "Buildbot";
+ type = types.str;
+ description = "Specifies the Buildbot Title.";
+ };
+
+ titleUrl = mkOption {
+ default = "Buildbot";
+ type = types.str;
+ description = "Specifies the Buildbot TitleURL.";
+ };
+
+ dbUrl = mkOption {
+ default = "sqlite:///state.sqlite";
+ type = types.str;
+ description = "Specifies the database connection string.";
+ };
+
+ port = mkOption {
+ default = 8010;
+ type = types.int;
+ description = "Specifies port number on which the buildbot HTTP interface listens.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.python3Packages.buildbot-full;
+ defaultText = "pkgs.python3Packages.buildbot-full";
+ description = "Package to use for buildbot.";
+ example = literalExample "pkgs.python3Packages.buildbot";
+ };
+
+ packages = mkOption {
+ default = [ pkgs.git ];
+ example = literalExample "[ pkgs.git ]";
+ type = types.listOf types.package;
+ description = "Packages to add to PATH for the buildbot process.";
+ };
+
+ pythonPackages = mkOption {
+ default = pythonPackages: with pythonPackages; [ ];
+ defaultText = "pythonPackages: with pythonPackages; [ ]";
+ description = "Packages to add the to the PYTHONPATH of the buildbot process.";
+ example = literalExample "pythonPackages: with pythonPackages; [ requests ]";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.groups = optional (cfg.group == "buildbot") {
+ name = "buildbot";
+ };
+
+ users.users = optional (cfg.user == "buildbot") {
+ name = "buildbot";
+ description = "Buildbot User.";
+ isNormalUser = true;
+ createHome = true;
+ home = cfg.home;
+ group = cfg.group;
+ extraGroups = cfg.extraGroups;
+ useDefaultShell = true;
+ };
+
+ systemd.services.buildbot-master = {
+ description = "Buildbot Continuous Integration Server.";
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = cfg.packages ++ cfg.pythonPackages python.pkgs;
+ environment.PYTHONPATH = "${python.withPackages (self: cfg.pythonPackages self ++ [ cfg.package ])}/${python.sitePackages}";
+
+ preStart = ''
+ mkdir -vp "${cfg.buildbotDir}"
+ # Link the tac file so buildbot command line tools recognize the directory
+ ln -sf "${tacFile}" "${cfg.buildbotDir}/buildbot.tac"
+ ${cfg.package}/bin/buildbot create-master --db "${cfg.dbUrl}" "${cfg.buildbotDir}"
+ rm -f buildbot.tac.new master.cfg.sample
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ WorkingDirectory = cfg.home;
+ # NOTE: call twistd directly with stdout logging for systemd
+ ExecStart = "${python.pkgs.twisted}/bin/twistd -o --nodaemon --pidfile= --logfile - --python ${tacFile}";
+ };
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ nand0p mic92 ];
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix
new file mode 100644
index 00000000000..49e04ca3622
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -0,0 +1,186 @@
+# NixOS module for Buildbot Worker.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.buildbot-worker;
+
+ python = cfg.package.pythonModule;
+
+ tacFile = pkgs.writeText "aur-buildbot-worker.tac" ''
+ import os
+ from io import open
+
+ from buildbot_worker.bot import Worker
+ from twisted.application import service
+
+ basedir = '${cfg.buildbotDir}'
+
+ # note: this line is matched against to check that this is a worker
+ # directory; do not edit it.
+ application = service.Application('buildbot-worker')
+
+ master_url_split = '${cfg.masterUrl}'.split(':')
+ buildmaster_host = master_url_split[0]
+ port = int(master_url_split[1])
+ workername = '${cfg.workerUser}'
+
+ with open('${cfg.workerPassFile}', 'r', encoding='utf-8') as passwd_file:
+ passwd = passwd_file.read().strip('\r\n')
+ keepalive = 600
+ umask = None
+ maxdelay = 300
+ numcpus = None
+ allow_shutdown = None
+
+ s = Worker(buildmaster_host, port, workername, passwd, basedir,
+ keepalive, umask=umask, maxdelay=maxdelay,
+ numcpus=numcpus, allow_shutdown=allow_shutdown)
+ s.setServiceParent(application)
+ '';
+
+in {
+ options = {
+ services.buildbot-worker = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Buildbot Worker.";
+ };
+
+ user = mkOption {
+ default = "bbworker";
+ type = types.str;
+ description = "User the buildbot Worker should execute under.";
+ };
+
+ group = mkOption {
+ default = "bbworker";
+ type = types.str;
+ description = "Primary group of buildbot Worker user.";
+ };
+
+ extraGroups = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "List of extra groups that the Buildbot Worker user should be a part of.";
+ };
+
+ home = mkOption {
+ default = "/home/bbworker";
+ type = types.path;
+ description = "Buildbot home directory.";
+ };
+
+ buildbotDir = mkOption {
+ default = "${cfg.home}/worker";
+ type = types.path;
+ description = "Specifies the Buildbot directory.";
+ };
+
+ workerUser = mkOption {
+ default = "example-worker";
+ type = types.str;
+ description = "Specifies the Buildbot Worker user.";
+ };
+
+ workerPass = mkOption {
+ default = "pass";
+ type = types.str;
+ description = "Specifies the Buildbot Worker password.";
+ };
+
+ workerPassFile = mkOption {
+ type = types.path;
+ description = "File used to store the Buildbot Worker password";
+ };
+
+ hostMessage = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Description of this worker";
+ };
+
+ adminMessage = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Name of the administrator of this worker";
+ };
+
+ masterUrl = mkOption {
+ default = "localhost:9989";
+ type = types.str;
+ description = "Specifies the Buildbot Worker connection string.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.python3Packages.buildbot-worker;
+ defaultText = "pkgs.python3Packages.buildbot-worker";
+ description = "Package to use for buildbot worker.";
+ example = literalExample "pkgs.python2Packages.buildbot-worker";
+ };
+
+ packages = mkOption {
+ default = with pkgs; [ git ];
+ example = literalExample "[ pkgs.git ]";
+ type = types.listOf types.package;
+ description = "Packages to add to PATH for the buildbot process.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.buildbot-worker.workerPassFile = mkDefault (pkgs.writeText "buildbot-worker-password" cfg.workerPass);
+
+ users.groups = optional (cfg.group == "bbworker") {
+ name = "bbworker";
+ };
+
+ users.users = optional (cfg.user == "bbworker") {
+ name = "bbworker";
+ description = "Buildbot Worker User.";
+ isNormalUser = true;
+ createHome = true;
+ home = cfg.home;
+ group = cfg.group;
+ extraGroups = cfg.extraGroups;
+ useDefaultShell = true;
+ };
+
+ systemd.services.buildbot-worker = {
+ description = "Buildbot Worker.";
+ after = [ "network.target" "buildbot-master.service" ];
+ wantedBy = [ "multi-user.target" ];
+ path = cfg.packages;
+ environment.PYTHONPATH = "${python.withPackages (p: [ cfg.package ])}/${python.sitePackages}";
+
+ preStart = ''
+ mkdir -vp "${cfg.buildbotDir}/info"
+ ${optionalString (cfg.hostMessage != null) ''
+ ln -sf "${pkgs.writeText "buildbot-worker-host" cfg.hostMessage}" "${cfg.buildbotDir}/info/host"
+ ''}
+ ${optionalString (cfg.adminMessage != null) ''
+ ln -sf "${pkgs.writeText "buildbot-worker-admin" cfg.adminMessage}" "${cfg.buildbotDir}/info/admin"
+ ''}
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ WorkingDirectory = cfg.home;
+
+ # NOTE: call twistd directly with stdout logging for systemd
+ ExecStart = "${python.pkgs.twisted}/bin/twistd --nodaemon --pidfile= --logfile - --python ${tacFile}";
+ };
+
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ nand0p ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildkite-agent.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildkite-agent.nix
new file mode 100644
index 00000000000..12cc3d2b1cc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildkite-agent.nix
@@ -0,0 +1,252 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.buildkite-agent;
+
+ mkHookOption = { name, description, example ? null }: {
+ inherit name;
+ value = mkOption {
+ default = null;
+ inherit description;
+ type = types.nullOr types.lines;
+ } // (if example == null then {} else { inherit example; });
+ };
+ mkHookOptions = hooks: listToAttrs (map mkHookOption hooks);
+
+ hooksDir = let
+ mkHookEntry = name: value: ''
+ cat > $out/${name} <<'EOF'
+ #! ${pkgs.runtimeShell}
+ set -e
+ ${value}
+ EOF
+ chmod 755 $out/${name}
+ '';
+ in pkgs.runCommand "buildkite-agent-hooks" { preferLocalBuild = true; } ''
+ mkdir $out
+ ${concatStringsSep "\n" (mapAttrsToList mkHookEntry (filterAttrs (n: v: v != null) cfg.hooks))}
+ '';
+
+in
+
+{
+ options = {
+ services.buildkite-agent = {
+ enable = mkEnableOption "buildkite-agent";
+
+ package = mkOption {
+ default = pkgs.buildkite-agent;
+ defaultText = "pkgs.buildkite-agent";
+ description = "Which buildkite-agent derivation to use";
+ type = types.package;
+ };
+
+ dataDir = mkOption {
+ default = "/var/lib/buildkite-agent";
+ description = "The workdir for the agent";
+ type = types.str;
+ };
+
+ runtimePackages = mkOption {
+ default = [ pkgs.bash pkgs.nix ];
+ defaultText = "[ pkgs.bash pkgs.nix ]";
+ description = "Add programs to the buildkite-agent environment";
+ type = types.listOf types.package;
+ };
+
+ tokenPath = mkOption {
+ type = types.path;
+ description = ''
+ The token from your Buildkite "Agents" page.
+
+ A run-time path to the token file, which is supposed to be provisioned
+ outside of Nix store.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "%hostname-%n";
+ description = ''
+ The name of the agent.
+ '';
+ };
+
+ meta-data = mkOption {
+ type = types.str;
+ default = "";
+ example = "queue=default,docker=true,ruby2=true";
+ description = ''
+ Meta data for the agent. This is a comma-separated list of
+ <code>key=value</code> pairs.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "debug=true";
+ description = ''
+ Extra lines to be added verbatim to the configuration file.
+ '';
+ };
+
+ openssh =
+ { privateKeyPath = mkOption {
+ type = types.path;
+ description = ''
+ Private agent key.
+
+ A run-time path to the key file, which is supposed to be provisioned
+ outside of Nix store.
+ '';
+ };
+ publicKeyPath = mkOption {
+ type = types.path;
+ description = ''
+ Public agent key.
+
+ A run-time path to the key file, which is supposed to be provisioned
+ outside of Nix store.
+ '';
+ };
+ };
+
+ hooks = mkHookOptions [
+ { name = "checkout";
+ description = ''
+ The `checkout` hook script will replace the default checkout routine of the
+ bootstrap.sh script. You can use this hook to do your own SCM checkout
+ behaviour
+ ''; }
+ { name = "command";
+ description = ''
+ The `command` hook script will replace the default implementation of running
+ the build command.
+ ''; }
+ { name = "environment";
+ description = ''
+ The `environment` hook will run before all other commands, and can be used
+ to set up secrets, data, etc. Anything exported in hooks will be available
+ to the build script.
+
+ Note: the contents of this file will be copied to the world-readable
+ Nix store.
+ '';
+ example = ''
+ export SECRET_VAR=`head -1 /run/keys/secret`
+ ''; }
+ { name = "post-artifact";
+ description = ''
+ The `post-artifact` hook will run just after artifacts are uploaded
+ ''; }
+ { name = "post-checkout";
+ description = ''
+ The `post-checkout` hook will run after the bootstrap script has checked out
+ your projects source code.
+ ''; }
+ { name = "post-command";
+ description = ''
+ The `post-command` hook will run after the bootstrap script has run your
+ build commands
+ ''; }
+ { name = "pre-artifact";
+ description = ''
+ The `pre-artifact` hook will run just before artifacts are uploaded
+ ''; }
+ { name = "pre-checkout";
+ description = ''
+ The `pre-checkout` hook will run just before your projects source code is
+ checked out from your SCM provider
+ ''; }
+ { name = "pre-command";
+ description = ''
+ The `pre-command` hook will run just before your build command runs
+ ''; }
+ { name = "pre-exit";
+ description = ''
+ The `pre-exit` hook will run just before your build job finishes
+ ''; }
+ ];
+
+ hooksPath = mkOption {
+ type = types.path;
+ default = hooksDir;
+ defaultText = "generated from services.buildkite-agent.hooks";
+ description = ''
+ Path to the directory storing the hooks.
+ Consider using <option>services.buildkite-agent.hooks.&lt;name&gt;</option>
+ instead.
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.buildkite-agent.enable {
+ users.users.buildkite-agent =
+ { name = "buildkite-agent";
+ home = cfg.dataDir;
+ createHome = true;
+ description = "Buildkite agent user";
+ extraGroups = [ "keys" ];
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services.buildkite-agent =
+ { description = "Buildkite Agent";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = cfg.runtimePackages ++ [ pkgs.coreutils ];
+ environment = config.networking.proxy.envVars // {
+ HOME = cfg.dataDir;
+ NIX_REMOTE = "daemon";
+ };
+
+ ## NB: maximum care is taken so that secrets (ssh keys and the CI token)
+ ## don't end up in the Nix store.
+ preStart = let
+ sshDir = "${cfg.dataDir}/.ssh";
+ in
+ ''
+ mkdir -m 0700 -p "${sshDir}"
+ cp -f "${toString cfg.openssh.privateKeyPath}" "${sshDir}/id_rsa"
+ cp -f "${toString cfg.openssh.publicKeyPath}" "${sshDir}/id_rsa.pub"
+ chmod 600 "${sshDir}"/id_rsa*
+
+ cat > "${cfg.dataDir}/buildkite-agent.cfg" <<EOF
+ token="$(cat ${toString cfg.tokenPath})"
+ name="${cfg.name}"
+ meta-data="${cfg.meta-data}"
+ build-path="${cfg.dataDir}/builds"
+ hooks-path="${cfg.hooksPath}"
+ ${cfg.extraConfig}
+ EOF
+ '';
+
+ serviceConfig =
+ { ExecStart = "${pkgs.buildkite-agent}/bin/buildkite-agent start --config /var/lib/buildkite-agent/buildkite-agent.cfg";
+ User = "buildkite-agent";
+ RestartSec = 5;
+ Restart = "on-failure";
+ TimeoutSec = 10;
+ };
+ };
+
+ assertions = [
+ { assertion = cfg.hooksPath == hooksDir || all (v: v == null) (attrValues cfg.hooks);
+ message = ''
+ Options `services.buildkite-agent.hooksPath' and
+ `services.buildkite-agent.hooks.<name>' are mutually exclusive.
+ '';
+ }
+ ];
+ };
+ imports = [
+ (mkRenamedOptionModule [ "services" "buildkite-agent" "token" ] [ "services" "buildkite-agent" "tokenPath" ])
+ (mkRenamedOptionModule [ "services" "buildkite-agent" "openssh" "privateKey" ] [ "services" "buildkite-agent" "openssh" "privateKeyPath" ])
+ (mkRenamedOptionModule [ "services" "buildkite-agent" "openssh" "publicKey" ] [ "services" "buildkite-agent" "openssh" "publicKeyPath" ])
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix
new file mode 100644
index 00000000000..3d307b1abcf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -0,0 +1,151 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gitlab-runner;
+ configFile =
+ if (cfg.configFile == null) then
+ (pkgs.runCommand "config.toml" {
+ buildInputs = [ pkgs.remarshal ];
+ preferLocalBuild = true;
+ } ''
+ remarshal -if json -of toml \
+ < ${pkgs.writeText "config.json" (builtins.toJSON cfg.configOptions)} \
+ > $out
+ '')
+ else
+ cfg.configFile;
+ hasDocker = config.virtualisation.docker.enable;
+in
+{
+ options.services.gitlab-runner = {
+ enable = mkEnableOption "Gitlab Runner";
+
+ configFile = mkOption {
+ default = null;
+ description = ''
+ Configuration file for gitlab-runner.
+ Use this option in favor of configOptions to avoid placing CI tokens in the nix store.
+
+ <option>configFile</option> takes precedence over <option>configOptions</option>.
+
+ Warning: Not using <option>configFile</option> will potentially result in secrets
+ leaking into the WORLD-READABLE nix store.
+ '';
+ type = types.nullOr types.path;
+ };
+
+ configOptions = mkOption {
+ description = ''
+ Configuration for gitlab-runner
+ <option>configFile</option> will take precedence over this option.
+
+ Warning: all Configuration, especially CI token, will be stored in a
+ WORLD-READABLE file in the Nix Store.
+
+ If you want to protect your CI token use <option>configFile</option> instead.
+ '';
+ type = types.attrs;
+ example = {
+ concurrent = 2;
+ runners = [{
+ name = "docker-nix-1.11";
+ url = "https://CI/";
+ token = "TOKEN";
+ executor = "docker";
+ builds_dir = "";
+ docker = {
+ host = "";
+ image = "nixos/nix:1.11";
+ privileged = true;
+ disable_cache = true;
+ cache_dir = "";
+ };
+ }];
+ };
+ };
+
+ gracefulTermination = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Finish all remaining jobs before stopping, restarting or reconfiguring.
+ If not set gitlab-runner will stop immediatly without waiting for jobs to finish,
+ which will lead to failed builds.
+ '';
+ };
+
+ gracefulTimeout = mkOption {
+ default = "infinity";
+ type = types.str;
+ example = "5min 20s";
+ description = ''Time to wait until a graceful shutdown is turned into a forceful one.'';
+ };
+
+ workDir = mkOption {
+ default = "/var/lib/gitlab-runner";
+ type = types.path;
+ description = "The working directory used";
+ };
+
+ package = mkOption {
+ description = "Gitlab Runner package to use";
+ default = pkgs.gitlab-runner;
+ defaultText = "pkgs.gitlab-runner";
+ type = types.package;
+ example = literalExample "pkgs.gitlab-runner_1_11";
+ };
+
+ packages = mkOption {
+ default = [ pkgs.bash pkgs.docker-machine ];
+ defaultText = "[ pkgs.bash pkgs.docker-machine ]";
+ type = types.listOf types.package;
+ description = ''
+ Packages to add to PATH for the gitlab-runner process.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.gitlab-runner = {
+ path = cfg.packages;
+ environment = config.networking.proxy.envVars // {
+ # Gitlab runner will not start if the HOME variable is not set
+ HOME = cfg.workDir;
+ };
+ description = "Gitlab Runner";
+ after = [ "network.target" ]
+ ++ optional hasDocker "docker.service";
+ requires = optional hasDocker "docker.service";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = ''${cfg.package.bin}/bin/gitlab-runner run \
+ --working-directory ${cfg.workDir} \
+ --config ${configFile} \
+ --service gitlab-runner \
+ --user gitlab-runner \
+ '';
+
+ } // optionalAttrs (cfg.gracefulTermination) {
+ TimeoutStopSec = "${cfg.gracefulTimeout}";
+ KillSignal = "SIGQUIT";
+ KillMode = "process";
+ };
+ };
+
+ # Make the gitlab-runner command availabe so users can query the runner
+ environment.systemPackages = [ cfg.package ];
+
+ users.users.gitlab-runner = {
+ group = "gitlab-runner";
+ extraGroups = optional hasDocker "docker";
+ uid = config.ids.uids.gitlab-runner;
+ home = cfg.workDir;
+ createHome = true;
+ };
+
+ users.groups.gitlab-runner.gid = config.ids.gids.gitlab-runner;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix
new file mode 100644
index 00000000000..8126f27c2b0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix
@@ -0,0 +1,206 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gocd-agent;
+in {
+ options = {
+ services.gocd-agent = {
+ enable = mkEnableOption "gocd-agent";
+
+ user = mkOption {
+ default = "gocd-agent";
+ type = types.str;
+ description = ''
+ User the Go.CD agent should execute under.
+ '';
+ };
+
+ group = mkOption {
+ default = "gocd-agent";
+ type = types.str;
+ description = ''
+ If the default user "gocd-agent" is configured then this is the primary
+ group of that user.
+ '';
+ };
+
+ extraGroups = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "wheel" "docker" ];
+ description = ''
+ List of extra groups that the "gocd-agent" user should be a part of.
+ '';
+ };
+
+ packages = mkOption {
+ default = [ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ];
+ defaultText = "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
+ type = types.listOf types.package;
+ description = ''
+ Packages to add to PATH for the Go.CD agent process.
+ '';
+ };
+
+ agentConfig = mkOption {
+ default = "";
+ type = types.str;
+ example = ''
+ agent.auto.register.resources=ant,java
+ agent.auto.register.environments=QA,Performance
+ agent.auto.register.hostname=Agent01
+ '';
+ description = ''
+ Agent registration configuration.
+ '';
+ };
+
+ goServer = mkOption {
+ default = "https://127.0.0.1:8154/go";
+ type = types.str;
+ description = ''
+ URL of the GoCD Server to attach the Go.CD Agent to.
+ '';
+ };
+
+ workDir = mkOption {
+ default = "/var/lib/go-agent";
+ type = types.str;
+ description = ''
+ Specifies the working directory in which the Go.CD agent java archive resides.
+ '';
+ };
+
+ initialJavaHeapSize = mkOption {
+ default = "128m";
+ type = types.str;
+ description = ''
+ Specifies the initial java heap memory size for the Go.CD agent java process.
+ '';
+ };
+
+ maxJavaHeapMemory = mkOption {
+ default = "256m";
+ type = types.str;
+ description = ''
+ Specifies the java maximum heap memory size for the Go.CD agent java process.
+ '';
+ };
+
+ startupOptions = mkOption {
+ default = [
+ "-Xms${cfg.initialJavaHeapSize}"
+ "-Xmx${cfg.maxJavaHeapMemory}"
+ "-Djava.io.tmpdir=/tmp"
+ "-Dcruise.console.publish.interval=10"
+ "-Djava.security.egd=file:/dev/./urandom"
+ ];
+ description = ''
+ Specifies startup command line arguments to pass to Go.CD agent
+ java process.
+ '';
+ };
+
+ extraOptions = mkOption {
+ default = [ ];
+ example = [
+ "-X debug"
+ "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5006"
+ "-verbose:gc"
+ "-Xloggc:go-agent-gc.log"
+ "-XX:+PrintGCTimeStamps"
+ "-XX:+PrintTenuringDistribution"
+ "-XX:+PrintGCDetails"
+ "-XX:+PrintGC"
+ ];
+ description = ''
+ Specifies additional command line arguments to pass to Go.CD agent
+ java process. Example contains debug and gcLog arguments.
+ '';
+ };
+
+ environment = mkOption {
+ default = { };
+ type = with types; attrsOf str;
+ description = ''
+ Additional environment variables to be passed to the Go.CD agent process.
+ As a base environment, Go.CD agent receives NIX_PATH from
+ <option>environment.sessionVariables</option>, NIX_REMOTE is set to
+ "daemon".
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.groups = optional (cfg.group == "gocd-agent") {
+ name = "gocd-agent";
+ gid = config.ids.gids.gocd-agent;
+ };
+
+ users.users = optional (cfg.user == "gocd-agent") {
+ name = "gocd-agent";
+ description = "gocd-agent user";
+ createHome = true;
+ home = cfg.workDir;
+ group = cfg.group;
+ extraGroups = cfg.extraGroups;
+ useDefaultShell = true;
+ uid = config.ids.uids.gocd-agent;
+ };
+
+ systemd.services.gocd-agent = {
+ description = "GoCD Agent";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ environment =
+ let
+ selectedSessionVars =
+ lib.filterAttrs (n: v: builtins.elem n [ "NIX_PATH" ])
+ config.environment.sessionVariables;
+ in
+ selectedSessionVars //
+ {
+ NIX_REMOTE = "daemon";
+ AGENT_WORK_DIR = cfg.workDir;
+ AGENT_STARTUP_ARGS = ''${concatStringsSep " " cfg.startupOptions}'';
+ LOG_DIR = cfg.workDir;
+ LOG_FILE = "${cfg.workDir}/go-agent-start.log";
+ } //
+ cfg.environment;
+
+ path = cfg.packages;
+
+ script = ''
+ MPATH="''${PATH}";
+ source /etc/profile
+ export PATH="''${MPATH}:''${PATH}";
+
+ if ! test -f ~/.nixpkgs/config.nix; then
+ mkdir -p ~/.nixpkgs/
+ echo "{ allowUnfree = true; }" > ~/.nixpkgs/config.nix
+ fi
+
+ mkdir -p config
+ rm -f config/autoregister.properties
+ ln -s "${pkgs.writeText "autoregister.properties" cfg.agentConfig}" config/autoregister.properties
+
+ ${pkgs.git}/bin/git config --global --add http.sslCAinfo /etc/ssl/certs/ca-certificates.crt
+ ${pkgs.jre}/bin/java ${concatStringsSep " " cfg.startupOptions} \
+ ${concatStringsSep " " cfg.extraOptions} \
+ -jar ${pkgs.gocd-agent}/go-agent/agent-bootstrapper.jar \
+ -serverUrl ${cfg.goServer}
+ '';
+
+ serviceConfig = {
+ User = cfg.user;
+ WorkingDirectory = cfg.workDir;
+ RestartSec = 30;
+ Restart = "on-failure";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix
new file mode 100644
index 00000000000..8f177da129e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gocd-server;
+in {
+ options = {
+ services.gocd-server = {
+ enable = mkEnableOption "gocd-server";
+
+ user = mkOption {
+ default = "gocd-server";
+ type = types.str;
+ description = ''
+ User the Go.CD server should execute under.
+ '';
+ };
+
+ group = mkOption {
+ default = "gocd-server";
+ type = types.str;
+ description = ''
+ If the default user "gocd-server" is configured then this is the primary group of that user.
+ '';
+ };
+
+ extraGroups = mkOption {
+ default = [ ];
+ example = [ "wheel" "docker" ];
+ description = ''
+ List of extra groups that the "gocd-server" user should be a part of.
+ '';
+ };
+
+ listenAddress = mkOption {
+ default = "0.0.0.0";
+ example = "localhost";
+ type = types.str;
+ description = ''
+ Specifies the bind address on which the Go.CD server HTTP interface listens.
+ '';
+ };
+
+ port = mkOption {
+ default = 8153;
+ type = types.int;
+ description = ''
+ Specifies port number on which the Go.CD server HTTP interface listens.
+ '';
+ };
+
+ sslPort = mkOption {
+ default = 8154;
+ type = types.int;
+ description = ''
+ Specifies port number on which the Go.CD server HTTPS interface listens.
+ '';
+ };
+
+ workDir = mkOption {
+ default = "/var/lib/go-server";
+ type = types.str;
+ description = ''
+ Specifies the working directory in which the Go.CD server java archive resides.
+ '';
+ };
+
+ packages = mkOption {
+ default = [ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ];
+ defaultText = "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
+ type = types.listOf types.package;
+ description = ''
+ Packages to add to PATH for the Go.CD server's process.
+ '';
+ };
+
+ initialJavaHeapSize = mkOption {
+ default = "512m";
+ type = types.str;
+ description = ''
+ Specifies the initial java heap memory size for the Go.CD server's java process.
+ '';
+ };
+
+ maxJavaHeapMemory = mkOption {
+ default = "1024m";
+ type = types.str;
+ description = ''
+ Specifies the java maximum heap memory size for the Go.CD server's java process.
+ '';
+ };
+
+ startupOptions = mkOption {
+ default = [
+ "-Xms${cfg.initialJavaHeapSize}"
+ "-Xmx${cfg.maxJavaHeapMemory}"
+ "-Dcruise.listen.host=${cfg.listenAddress}"
+ "-Duser.language=en"
+ "-Djruby.rack.request.size.threshold.bytes=30000000"
+ "-Duser.country=US"
+ "-Dcruise.config.dir=${cfg.workDir}/conf"
+ "-Dcruise.config.file=${cfg.workDir}/conf/cruise-config.xml"
+ "-Dcruise.server.port=${toString cfg.port}"
+ "-Dcruise.server.ssl.port=${toString cfg.sslPort}"
+ ];
+
+ description = ''
+ Specifies startup command line arguments to pass to Go.CD server
+ java process.
+ '';
+ };
+
+ extraOptions = mkOption {
+ default = [ ];
+ example = [
+ "-X debug"
+ "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"
+ "-verbose:gc"
+ "-Xloggc:go-server-gc.log"
+ "-XX:+PrintGCTimeStamps"
+ "-XX:+PrintTenuringDistribution"
+ "-XX:+PrintGCDetails"
+ "-XX:+PrintGC"
+ ];
+ description = ''
+ Specifies additional command line arguments to pass to Go.CD server's
+ java process. Example contains debug and gcLog arguments.
+ '';
+ };
+
+ environment = mkOption {
+ default = { };
+ type = with types; attrsOf str;
+ description = ''
+ Additional environment variables to be passed to the gocd-server process.
+ As a base environment, gocd-server receives NIX_PATH from
+ <option>environment.sessionVariables</option>, NIX_REMOTE is set to
+ "daemon".
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.groups = optional (cfg.group == "gocd-server") {
+ name = "gocd-server";
+ gid = config.ids.gids.gocd-server;
+ };
+
+ users.users = optional (cfg.user == "gocd-server") {
+ name = "gocd-server";
+ description = "gocd-server user";
+ createHome = true;
+ home = cfg.workDir;
+ group = cfg.group;
+ extraGroups = cfg.extraGroups;
+ useDefaultShell = true;
+ uid = config.ids.uids.gocd-server;
+ };
+
+ systemd.services.gocd-server = {
+ description = "GoCD Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ environment =
+ let
+ selectedSessionVars =
+ lib.filterAttrs (n: v: builtins.elem n [ "NIX_PATH" ])
+ config.environment.sessionVariables;
+ in
+ selectedSessionVars //
+ { NIX_REMOTE = "daemon";
+ } //
+ cfg.environment;
+
+ path = cfg.packages;
+
+ script = ''
+ ${pkgs.git}/bin/git config --global --add http.sslCAinfo /etc/ssl/certs/ca-certificates.crt
+ ${pkgs.jre}/bin/java -server ${concatStringsSep " " cfg.startupOptions} \
+ ${concatStringsSep " " cfg.extraOptions} \
+ -jar ${pkgs.gocd-server}/go-server/go.jar
+ '';
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ WorkingDirectory = cfg.workDir;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/hail.nix b/nixpkgs/nixos/modules/services/continuous-integration/hail.nix
new file mode 100644
index 00000000000..5d0c3f7b4ab
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/hail.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+ cfg = config.services.hail;
+in {
+
+
+ ###### interface
+
+ options.services.hail = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables the Hail Auto Update Service. Hail can automatically deploy artifacts
+ built by a Hydra Continous Integration server. A common use case is to provide
+ continous deployment for single services or a full NixOS configuration.'';
+ };
+ profile = mkOption {
+ type = types.str;
+ default = "hail-profile";
+ description = "The name of the Nix profile used by Hail.";
+ };
+ hydraJobUri = mkOption {
+ type = types.str;
+ description = "The URI of the Hydra Job.";
+ };
+ netrc = mkOption {
+ type = types.nullOr types.path;
+ description = "The netrc file to use when fetching data from Hydra.";
+ default = null;
+ };
+ package = mkOption {
+ type = types.package;
+ default = pkgs.haskellPackages.hail;
+ defaultText = "pkgs.haskellPackages.hail";
+ description = "Hail package to use.";
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.hail = {
+ description = "Hail Auto Update Service";
+ wants = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [ nix ];
+ environment = {
+ HOME = "/var/lib/empty";
+ };
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/hail --profile ${cfg.profile} --job-uri ${cfg.hydraJobUri}"
+ + lib.optionalString (cfg.netrc != null) " --netrc-file ${cfg.netrc}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix
new file mode 100644
index 00000000000..2da10a9a5e2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -0,0 +1,448 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.hydra;
+
+ baseDir = "/var/lib/hydra";
+
+ hydraConf = pkgs.writeScript "hydra.conf" cfg.extraConfig;
+
+ hydraEnv =
+ { HYDRA_DBI = cfg.dbi;
+ HYDRA_CONFIG = "${baseDir}/hydra.conf";
+ HYDRA_DATA = "${baseDir}";
+ };
+
+ env =
+ { NIX_REMOTE = "daemon";
+ SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; # Remove in 16.03
+ PGPASSFILE = "${baseDir}/pgpass";
+ NIX_REMOTE_SYSTEMS = concatStringsSep ":" cfg.buildMachinesFiles;
+ } // optionalAttrs (cfg.smtpHost != null) {
+ EMAIL_SENDER_TRANSPORT = "SMTP";
+ EMAIL_SENDER_TRANSPORT_host = cfg.smtpHost;
+ } // hydraEnv // cfg.extraEnv;
+
+ serverEnv = env //
+ { HYDRA_TRACKER = cfg.tracker;
+ XDG_CACHE_HOME = "${baseDir}/www/.cache";
+ COLUMNS = "80";
+ PGPASSFILE = "${baseDir}/pgpass-www"; # grrr
+ } // (optionalAttrs cfg.debugServer { DBIC_TRACE = "1"; });
+
+ localDB = "dbi:Pg:dbname=hydra;user=hydra;";
+
+ haveLocalDB = cfg.dbi == localDB;
+
+in
+
+{
+ ###### interface
+ options = {
+
+ services.hydra = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run Hydra services.
+ '';
+ };
+
+ dbi = mkOption {
+ type = types.str;
+ default = localDB;
+ example = "dbi:Pg:dbname=hydra;host=postgres.example.org;user=foo;";
+ description = ''
+ The DBI string for Hydra database connection.
+ '';
+ };
+
+ package = mkOption {
+ type = types.path;
+ default = pkgs.hydra;
+ defaultText = "pkgs.hydra";
+ description = "The Hydra package.";
+ };
+
+ hydraURL = mkOption {
+ type = types.str;
+ description = ''
+ The base URL for the Hydra webserver instance. Used for links in emails.
+ '';
+ };
+
+ listenHost = mkOption {
+ type = types.str;
+ default = "*";
+ example = "localhost";
+ description = ''
+ The hostname or address to listen on or <literal>*</literal> to listen
+ on all interfaces.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 3000;
+ description = ''
+ TCP port the web server should listen to.
+ '';
+ };
+
+ minimumDiskFree = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Threshold of minimum disk space (GiB) to determine if the queue runner should run or not.
+ '';
+ };
+
+ minimumDiskFreeEvaluator = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Threshold of minimum disk space (GiB) to determine if the evaluator should run or not.
+ '';
+ };
+
+ notificationSender = mkOption {
+ type = types.str;
+ description = ''
+ Sender email address used for email notifications.
+ '';
+ };
+
+ smtpHost = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = ["localhost"];
+ description = ''
+ Hostname of the SMTP server to use to send email.
+ '';
+ };
+
+ tracker = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Piece of HTML that is included on all pages.
+ '';
+ };
+
+ logo = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Path to a file containing the logo of your Hydra instance.
+ '';
+ };
+
+ debugServer = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to run the server in debug mode.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ description = "Extra lines for the Hydra configuration.";
+ };
+
+ extraEnv = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ description = "Extra environment variables for Hydra.";
+ };
+
+ gcRootsDir = mkOption {
+ type = types.path;
+ default = "/nix/var/nix/gcroots/hydra";
+ description = "Directory that holds Hydra garbage collector roots.";
+ };
+
+ buildMachinesFiles = mkOption {
+ type = types.listOf types.path;
+ default = [ "/etc/nix/machines" ];
+ example = [ "/etc/nix/machines" "/var/lib/hydra/provisioner/machines" ];
+ description = "List of files containing build machines.";
+ };
+
+ useSubstitutes = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use binary caches for downloading store paths. Note that
+ binary substitutions trigger (a potentially large number of) additional
+ HTTP requests that slow down the queue monitor thread significantly.
+ Also, this Hydra instance will serve those downloaded store paths to
+ its users with its own signature attached as if it had built them
+ itself, so don't enable this feature unless your active binary caches
+ are absolute trustworthy.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.groups.hydra = {
+ gid = config.ids.gids.hydra;
+ };
+
+ users.users.hydra =
+ { description = "Hydra";
+ group = "hydra";
+ createHome = true;
+ home = baseDir;
+ useDefaultShell = true;
+ uid = config.ids.uids.hydra;
+ };
+
+ users.users.hydra-queue-runner =
+ { description = "Hydra queue runner";
+ group = "hydra";
+ useDefaultShell = true;
+ home = "${baseDir}/queue-runner"; # really only to keep SSH happy
+ uid = config.ids.uids.hydra-queue-runner;
+ };
+
+ users.users.hydra-www =
+ { description = "Hydra web server";
+ group = "hydra";
+ useDefaultShell = true;
+ uid = config.ids.uids.hydra-www;
+ };
+
+ nix.trustedUsers = [ "hydra-queue-runner" ];
+
+ services.hydra.extraConfig =
+ ''
+ using_frontend_proxy = 1
+ base_uri = ${cfg.hydraURL}
+ notification_sender = ${cfg.notificationSender}
+ max_servers = 25
+ ${optionalString (cfg.logo != null) ''
+ hydra_logo = ${cfg.logo}
+ ''}
+ gc_roots_dir = ${cfg.gcRootsDir}
+ use-substitutes = ${if cfg.useSubstitutes then "1" else "0"}
+ '';
+
+ environment.systemPackages = [ cfg.package ];
+
+ environment.variables = hydraEnv;
+
+ nix.extraOptions = ''
+ gc-keep-outputs = true
+ gc-keep-derivations = true
+
+ # The default (`true') slows Nix down a lot since the build farm
+ # has so many GC roots.
+ gc-check-reachability = false
+ '';
+
+ systemd.services.hydra-init =
+ { wantedBy = [ "multi-user.target" ];
+ requires = optional haveLocalDB "postgresql.service";
+ after = optional haveLocalDB "postgresql.service";
+ environment = env;
+ preStart = ''
+ mkdir -p ${baseDir}
+ chown hydra.hydra ${baseDir}
+ chmod 0750 ${baseDir}
+
+ ln -sf ${hydraConf} ${baseDir}/hydra.conf
+
+ mkdir -m 0700 -p ${baseDir}/www
+ chown hydra-www.hydra ${baseDir}/www
+
+ mkdir -m 0700 -p ${baseDir}/queue-runner
+ mkdir -m 0750 -p ${baseDir}/build-logs
+ chown hydra-queue-runner.hydra ${baseDir}/queue-runner ${baseDir}/build-logs
+
+ ${optionalString haveLocalDB ''
+ if ! [ -e ${baseDir}/.db-created ]; then
+ ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createuser hydra
+ ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createdb -O hydra hydra
+ touch ${baseDir}/.db-created
+ fi
+ echo "create extension if not exists pg_trgm" | ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql hydra
+ ''}
+
+ if [ ! -e ${cfg.gcRootsDir} ]; then
+
+ # Move legacy roots directory.
+ if [ -e /nix/var/nix/gcroots/per-user/hydra/hydra-roots ]; then
+ mv /nix/var/nix/gcroots/per-user/hydra/hydra-roots ${cfg.gcRootsDir}
+ fi
+
+ mkdir -p ${cfg.gcRootsDir}
+ fi
+
+ # Move legacy hydra-www roots.
+ if [ -e /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots ]; then
+ find /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots/ -type f \
+ | xargs -r mv -f -t ${cfg.gcRootsDir}/
+ rmdir /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots
+ fi
+
+ chown hydra.hydra ${cfg.gcRootsDir}
+ chmod 2775 ${cfg.gcRootsDir}
+ '';
+ serviceConfig.ExecStart = "${cfg.package}/bin/hydra-init";
+ serviceConfig.PermissionsStartOnly = true;
+ serviceConfig.User = "hydra";
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ };
+
+ systemd.services.hydra-server =
+ { wantedBy = [ "multi-user.target" ];
+ requires = [ "hydra-init.service" ];
+ after = [ "hydra-init.service" ];
+ environment = serverEnv;
+ restartTriggers = [ hydraConf ];
+ serviceConfig =
+ { ExecStart =
+ "@${cfg.package}/bin/hydra-server hydra-server -f -h '${cfg.listenHost}' "
+ + "-p ${toString cfg.port} --max_spare_servers 5 --max_servers 25 "
+ + "--max_requests 100 ${optionalString cfg.debugServer "-d"}";
+ User = "hydra-www";
+ PermissionsStartOnly = true;
+ Restart = "always";
+ };
+ };
+
+ systemd.services.hydra-queue-runner =
+ { wantedBy = [ "multi-user.target" ];
+ requires = [ "hydra-init.service" ];
+ after = [ "hydra-init.service" "network.target" ];
+ path = [ cfg.package pkgs.nettools pkgs.openssh pkgs.bzip2 config.nix.package ];
+ restartTriggers = [ hydraConf ];
+ environment = env // {
+ PGPASSFILE = "${baseDir}/pgpass-queue-runner"; # grrr
+ IN_SYSTEMD = "1"; # to get log severity levels
+ };
+ serviceConfig =
+ { ExecStart = "@${cfg.package}/bin/hydra-queue-runner hydra-queue-runner -v --option build-use-substitutes ${boolToString cfg.useSubstitutes}";
+ ExecStopPost = "${cfg.package}/bin/hydra-queue-runner --unlock";
+ User = "hydra-queue-runner";
+ Restart = "always";
+
+ # Ensure we can get core dumps.
+ LimitCORE = "infinity";
+ WorkingDirectory = "${baseDir}/queue-runner";
+ };
+ };
+
+ systemd.services.hydra-evaluator =
+ { wantedBy = [ "multi-user.target" ];
+ requires = [ "hydra-init.service" ];
+ after = [ "hydra-init.service" "network.target" ];
+ path = with pkgs; [ cfg.package nettools jq ];
+ restartTriggers = [ hydraConf ];
+ environment = env;
+ serviceConfig =
+ { ExecStart = "@${cfg.package}/bin/hydra-evaluator hydra-evaluator";
+ User = "hydra";
+ Restart = "always";
+ WorkingDirectory = baseDir;
+ };
+ };
+
+ systemd.services.hydra-update-gc-roots =
+ { requires = [ "hydra-init.service" ];
+ after = [ "hydra-init.service" ];
+ environment = env;
+ serviceConfig =
+ { ExecStart = "@${cfg.package}/bin/hydra-update-gc-roots hydra-update-gc-roots";
+ User = "hydra";
+ };
+ startAt = "2,14:15";
+ };
+
+ systemd.services.hydra-send-stats =
+ { wantedBy = [ "multi-user.target" ];
+ after = [ "hydra-init.service" ];
+ environment = env;
+ serviceConfig =
+ { ExecStart = "@${cfg.package}/bin/hydra-send-stats hydra-send-stats";
+ User = "hydra";
+ };
+ };
+
+ systemd.services.hydra-notify =
+ { wantedBy = [ "multi-user.target" ];
+ requires = [ "hydra-init.service" ];
+ after = [ "hydra-init.service" ];
+ restartTriggers = [ hydraConf ];
+ environment = env // {
+ PGPASSFILE = "${baseDir}/pgpass-queue-runner";
+ };
+ serviceConfig =
+ { ExecStart = "@${cfg.package}/bin/hydra-notify hydra-notify";
+ # FIXME: run this under a less privileged user?
+ User = "hydra-queue-runner";
+ Restart = "always";
+ RestartSec = 5;
+ };
+ };
+
+ # If there is less than a certain amount of free disk space, stop
+ # the queue/evaluator to prevent builds from failing or aborting.
+ systemd.services.hydra-check-space =
+ { script =
+ ''
+ if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFree} * 1024**3)) ]; then
+ echo "stopping Hydra queue runner due to lack of free space..."
+ systemctl stop hydra-queue-runner
+ fi
+ if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFreeEvaluator} * 1024**3)) ]; then
+ echo "stopping Hydra evaluator due to lack of free space..."
+ systemctl stop hydra-evaluator
+ fi
+ '';
+ startAt = "*:0/5";
+ };
+
+ # Periodically compress build logs. The queue runner compresses
+ # logs automatically after a step finishes, but this doesn't work
+ # if the queue runner is stopped prematurely.
+ systemd.services.hydra-compress-logs =
+ { path = [ pkgs.bzip2 ];
+ script =
+ ''
+ find /var/lib/hydra/build-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f
+ '';
+ startAt = "Sun 01:45";
+ };
+
+ services.postgresql.enable = mkIf haveLocalDB true;
+
+ services.postgresql.identMap = optionalString haveLocalDB
+ ''
+ hydra-users hydra hydra
+ hydra-users hydra-queue-runner hydra
+ hydra-users hydra-www hydra
+ hydra-users root hydra
+ # The postgres user is used to create the pg_trgm extension for the hydra database
+ hydra-users postgres postgres
+ '';
+
+ services.postgresql.authentication = optionalString haveLocalDB
+ ''
+ local hydra all ident map=hydra-users
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix
new file mode 100644
index 00000000000..0ec90671388
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix
@@ -0,0 +1,228 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.jenkins;
+in {
+ options = {
+ services.jenkins = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the jenkins continuous integration server.
+ '';
+ };
+
+ user = mkOption {
+ default = "jenkins";
+ type = types.str;
+ description = ''
+ User the jenkins server should execute under.
+ '';
+ };
+
+ group = mkOption {
+ default = "jenkins";
+ type = types.str;
+ description = ''
+ If the default user "jenkins" is configured then this is the primary
+ group of that user.
+ '';
+ };
+
+ extraGroups = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "wheel" "dialout" ];
+ description = ''
+ List of extra groups that the "jenkins" user should be a part of.
+ '';
+ };
+
+ home = mkOption {
+ default = "/var/lib/jenkins";
+ type = types.path;
+ description = ''
+ The path to use as JENKINS_HOME. If the default user "jenkins" is configured then
+ this is the home of the "jenkins" user.
+ '';
+ };
+
+ listenAddress = mkOption {
+ default = "0.0.0.0";
+ example = "localhost";
+ type = types.str;
+ description = ''
+ Specifies the bind address on which the jenkins HTTP interface listens.
+ The default is the wildcard address.
+ '';
+ };
+
+ port = mkOption {
+ default = 8080;
+ type = types.int;
+ description = ''
+ Specifies port number on which the jenkins HTTP interface listens.
+ The default is 8080.
+ '';
+ };
+
+ prefix = mkOption {
+ default = "";
+ example = "/jenkins";
+ type = types.str;
+ description = ''
+ Specifies a urlPrefix to use with jenkins.
+ If the example /jenkins is given, the jenkins server will be
+ accessible using localhost:8080/jenkins.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.jenkins;
+ defaultText = "pkgs.jenkins";
+ type = types.package;
+ description = "Jenkins package to use.";
+ };
+
+ packages = mkOption {
+ default = [ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ];
+ defaultText = "[ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ]";
+ type = types.listOf types.package;
+ description = ''
+ Packages to add to PATH for the jenkins process.
+ '';
+ };
+
+ environment = mkOption {
+ default = { };
+ type = with types; attrsOf str;
+ description = ''
+ Additional environment variables to be passed to the jenkins process.
+ As a base environment, jenkins receives NIX_PATH from
+ <option>environment.sessionVariables</option>, NIX_REMOTE is set to
+ "daemon" and JENKINS_HOME is set to the value of
+ <option>services.jenkins.home</option>.
+ This option has precedence and can be used to override those
+ mentioned variables.
+ '';
+ };
+
+ plugins = mkOption {
+ default = null;
+ type = types.nullOr (types.attrsOf types.package);
+ description = ''
+ A set of plugins to activate. Note that this will completely
+ remove and replace any previously installed plugins. If you
+ have manually-installed plugins that you want to keep while
+ using this module, set this option to
+ <literal>null</literal>. You can generate this set with a
+ tool such as <literal>jenkinsPlugins2nix</literal>.
+ '';
+ example = literalExample ''
+ import path/to/jenkinsPlugins2nix-generated-plugins.nix { inherit (pkgs) fetchurl stdenv; }
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--debug=9" ];
+ description = ''
+ Additional command line arguments to pass to Jenkins.
+ '';
+ };
+
+ extraJavaOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "-Xmx80m" ];
+ description = ''
+ Additional command line arguments to pass to the Java run time (as opposed to Jenkins).
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # server references the dejavu fonts
+ environment.systemPackages = [
+ pkgs.dejavu_fonts
+ ];
+
+ users.groups = optional (cfg.group == "jenkins") {
+ name = "jenkins";
+ gid = config.ids.gids.jenkins;
+ };
+
+ users.users = optional (cfg.user == "jenkins") {
+ name = "jenkins";
+ description = "jenkins user";
+ createHome = true;
+ home = cfg.home;
+ group = cfg.group;
+ extraGroups = cfg.extraGroups;
+ useDefaultShell = true;
+ uid = config.ids.uids.jenkins;
+ };
+
+ systemd.services.jenkins = {
+ description = "Jenkins Continuous Integration Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ environment =
+ let
+ selectedSessionVars =
+ lib.filterAttrs (n: v: builtins.elem n [ "NIX_PATH" ])
+ config.environment.sessionVariables;
+ in
+ selectedSessionVars //
+ { JENKINS_HOME = cfg.home;
+ NIX_REMOTE = "daemon";
+ } //
+ cfg.environment;
+
+ path = cfg.packages;
+
+ # Force .war (re)extraction, or else we might run stale Jenkins.
+
+ preStart =
+ let replacePlugins =
+ if cfg.plugins == null
+ then ""
+ else
+ let pluginCmds = lib.attrsets.mapAttrsToList
+ (n: v: "cp ${v} ${cfg.home}/plugins/${n}.jpi")
+ cfg.plugins;
+ in ''
+ rm -r ${cfg.home}/plugins || true
+ mkdir -p ${cfg.home}/plugins
+ ${lib.strings.concatStringsSep "\n" pluginCmds}
+ '';
+ in ''
+ rm -rf ${cfg.home}/war
+ ${replacePlugins}
+ '';
+
+ # For reference: https://wiki.jenkins.io/display/JENKINS/JenkinsLinuxStartupScript
+ script = ''
+ ${pkgs.jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOptions} -jar ${cfg.package}/webapps/jenkins.war --httpListenAddress=${cfg.listenAddress} \
+ --httpPort=${toString cfg.port} \
+ --prefix=${cfg.prefix} \
+ -Djava.awt.headless=true \
+ ${concatStringsSep " " cfg.extraOptions}
+ '';
+
+ postStart = ''
+ until [[ $(${pkgs.curl.bin}/bin/curl -L -s --head -w '\n%{http_code}' http://${cfg.listenAddress}:${toString cfg.port}${cfg.prefix} | tail -n1) =~ ^(200|403)$ ]]; do
+ sleep 1
+ done
+ '';
+
+ serviceConfig = {
+ User = cfg.user;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
new file mode 100644
index 00000000000..5d1bfe4ec40
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
@@ -0,0 +1,205 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ jenkinsCfg = config.services.jenkins;
+ cfg = config.services.jenkins.jobBuilder;
+
+in {
+ options = {
+ services.jenkins.jobBuilder = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether or not to enable the Jenkins Job Builder (JJB) service. It
+ allows defining jobs for Jenkins in a declarative manner.
+
+ Jobs managed through the Jenkins WebUI (or by other means) are left
+ unchanged.
+
+ Note that it really is declarative configuration; if you remove a
+ previously defined job, the corresponding job directory will be
+ deleted.
+
+ Please see the Jenkins Job Builder documentation for more info:
+ <link xlink:href="http://docs.openstack.org/infra/jenkins-job-builder/">
+ http://docs.openstack.org/infra/jenkins-job-builder/</link>
+ '';
+ };
+
+ accessUser = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ User id in Jenkins used to reload config.
+ '';
+ };
+
+ accessToken = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ User token in Jenkins used to reload config.
+ WARNING: This token will be world readable in the Nix store. To keep
+ it secret, use the <option>accessTokenFile</option> option instead.
+ '';
+ };
+
+ accessTokenFile = mkOption {
+ default = "";
+ type = types.str;
+ example = "/run/keys/jenkins-job-builder-access-token";
+ description = ''
+ File containing the API token for the <option>accessUser</option>
+ user.
+ '';
+ };
+
+ yamlJobs = mkOption {
+ default = "";
+ type = types.lines;
+ example = ''
+ - job:
+ name: jenkins-job-test-1
+ builders:
+ - shell: echo 'Hello world!'
+ '';
+ description = ''
+ Job descriptions for Jenkins Job Builder in YAML format.
+ '';
+ };
+
+ jsonJobs = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ example = literalExample ''
+ [
+ '''
+ [ { "job":
+ { "name": "jenkins-job-test-2",
+ "builders": [ "shell": "echo 'Hello world!'" ]
+ }
+ }
+ ]
+ '''
+ ]
+ '';
+ description = ''
+ Job descriptions for Jenkins Job Builder in JSON format.
+ '';
+ };
+
+ nixJobs = mkOption {
+ default = [ ];
+ type = types.listOf types.attrs;
+ example = literalExample ''
+ [ { job =
+ { name = "jenkins-job-test-3";
+ builders = [
+ { shell = "echo 'Hello world!'"; }
+ ];
+ };
+ }
+ ]
+ '';
+ description = ''
+ Job descriptions for Jenkins Job Builder in Nix format.
+
+ This is a trivial wrapper around jsonJobs, using builtins.toJSON
+ behind the scene.
+ '';
+ };
+ };
+ };
+
+ config = mkIf (jenkinsCfg.enable && cfg.enable) {
+ assertions = [
+ { assertion =
+ if cfg.accessUser != ""
+ then (cfg.accessToken != "" && cfg.accessTokenFile == "") ||
+ (cfg.accessToken == "" && cfg.accessTokenFile != "")
+ else true;
+ message = ''
+ One of accessToken and accessTokenFile options must be non-empty
+ strings, but not both. Current values:
+ services.jenkins.jobBuilder.accessToken = "${cfg.accessToken}"
+ services.jenkins.jobBuilder.accessTokenFile = "${cfg.accessTokenFile}"
+ '';
+ }
+ ];
+
+ systemd.services.jenkins-job-builder = {
+ description = "Jenkins Job Builder Service";
+ # JJB can run either before or after jenkins. We chose after, so we can
+ # always use curl to notify (running) jenkins to reload its config.
+ after = [ "jenkins.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ path = with pkgs; [ jenkins-job-builder curl ];
+
+ # Q: Why manipulate files directly instead of using "jenkins-jobs upload [...]"?
+ # A: Because this module is for administering a local jenkins install,
+ # and using local file copy allows us to not worry about
+ # authentication.
+ script =
+ let
+ yamlJobsFile = builtins.toFile "jobs.yaml" cfg.yamlJobs;
+ jsonJobsFiles =
+ map (x: (builtins.toFile "jobs.json" x))
+ (cfg.jsonJobs ++ [(builtins.toJSON cfg.nixJobs)]);
+ jobBuilderOutputDir = "/run/jenkins-job-builder/output";
+ # Stamp file is placed in $JENKINS_HOME/jobs/$JOB_NAME/ to indicate
+ # ownership. Enables tracking and removal of stale jobs.
+ ownerStamp = ".config-xml-managed-by-nixos-jenkins-job-builder";
+ reloadScript = ''
+ echo "Asking Jenkins to reload config"
+ curl_opts="--silent --fail --show-error"
+ access_token=${if cfg.accessTokenFile != ""
+ then "$(cat '${cfg.accessTokenFile}')"
+ else cfg.accessToken}
+ jenkins_url="http://${cfg.accessUser}:$access_token@${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}"
+ crumb=$(curl $curl_opts "$jenkins_url"'/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)')
+ curl $curl_opts -X POST -H "$crumb" "$jenkins_url"/reload
+ '';
+ in
+ ''
+ rm -rf ${jobBuilderOutputDir}
+ cur_decl_jobs=/run/jenkins-job-builder/declarative-jobs
+ rm -f "$cur_decl_jobs"
+
+ # Create / update jobs
+ mkdir -p ${jobBuilderOutputDir}
+ for inputFile in ${yamlJobsFile} ${concatStringsSep " " jsonJobsFiles}; do
+ HOME="${jenkinsCfg.home}" "${pkgs.jenkins-job-builder}/bin/jenkins-jobs" --ignore-cache test -o "${jobBuilderOutputDir}" "$inputFile"
+ done
+
+ for file in "${jobBuilderOutputDir}/"*; do
+ test -f "$file" || continue
+ jobname="$(basename $file)"
+ jobdir="${jenkinsCfg.home}/jobs/$jobname"
+ echo "Creating / updating job \"$jobname\""
+ mkdir -p "$jobdir"
+ touch "$jobdir/${ownerStamp}"
+ cp "$file" "$jobdir/config.xml"
+ echo "$jobname" >> "$cur_decl_jobs"
+ done
+
+ # Remove stale jobs
+ for file in "${jenkinsCfg.home}"/jobs/*/${ownerStamp}; do
+ test -f "$file" || continue
+ jobdir="$(dirname $file)"
+ jobname="$(basename "$jobdir")"
+ grep --quiet --line-regexp "$jobname" "$cur_decl_jobs" 2>/dev/null && continue
+ echo "Deleting stale job \"$jobname\""
+ rm -rf "$jobdir"
+ done
+ '' + (if cfg.accessUser != "" then reloadScript else "");
+ serviceConfig = {
+ User = jenkinsCfg.user;
+ RuntimeDirectory = "jenkins-job-builder";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix
new file mode 100644
index 00000000000..92deabc3dd3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix
@@ -0,0 +1,68 @@
+{ config, lib, ... }:
+with lib;
+let
+ cfg = config.services.jenkinsSlave;
+ masterCfg = config.services.jenkins;
+in {
+ options = {
+ services.jenkinsSlave = {
+ # todo:
+ # * assure the profile of the jenkins user has a JRE and any specified packages. This would
+ # enable ssh slaves.
+ # * Optionally configure the node as a jenkins ad-hoc slave. This would imply configuration
+ # properties for the master node.
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If true the system will be configured to work as a jenkins slave.
+ If the system is also configured to work as a jenkins master then this has no effect.
+ In progress: Currently only assures the jenkins user is configured.
+ '';
+ };
+
+ user = mkOption {
+ default = "jenkins";
+ type = types.str;
+ description = ''
+ User the jenkins slave agent should execute under.
+ '';
+ };
+
+ group = mkOption {
+ default = "jenkins";
+ type = types.str;
+ description = ''
+ If the default slave agent user "jenkins" is configured then this is
+ the primary group of that user.
+ '';
+ };
+
+ home = mkOption {
+ default = "/var/lib/jenkins";
+ type = types.path;
+ description = ''
+ The path to use as JENKINS_HOME. If the default user "jenkins" is configured then
+ this is the home of the "jenkins" user.
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.enable && !masterCfg.enable) {
+ users.groups = optional (cfg.group == "jenkins") {
+ name = "jenkins";
+ gid = config.ids.gids.jenkins;
+ };
+
+ users.users = optional (cfg.user == "jenkins") {
+ name = "jenkins";
+ description = "jenkins user";
+ createHome = true;
+ home = cfg.home;
+ group = cfg.group;
+ useDefaultShell = true;
+ uid = config.ids.uids.jenkins;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/4store-endpoint.nix b/nixpkgs/nixos/modules/services/databases/4store-endpoint.nix
new file mode 100644
index 00000000000..59ed0e5f0af
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/4store-endpoint.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.services.fourStoreEndpoint;
+ endpointUser = "fourstorehttp";
+ run = "${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${endpointUser} -c";
+in
+with lib;
+{
+
+ ###### interface
+
+ options = {
+
+ services.fourStoreEndpoint = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable 4Store SPARQL endpoint.";
+ };
+
+ database = mkOption {
+ default = config.services.fourStore.database;
+ description = "RDF database name to expose via the endpoint. Defaults to local 4Store database name.";
+ };
+
+ listenAddress = mkOption {
+ default = null;
+ description = "IP address to listen on.";
+ };
+
+ port = mkOption {
+ default = 8080;
+ description = "port to listen on.";
+ };
+
+ options = mkOption {
+ default = "";
+ description = "Extra CLI options to pass to 4Store's 4s-httpd process.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = singleton
+ { assertion = cfg.enable -> cfg.database != "";
+ message = "Must specify 4Store database name";
+ };
+
+ users.users = singleton
+ { name = endpointUser;
+ uid = config.ids.uids.fourstorehttp;
+ description = "4Store SPARQL endpoint user";
+ };
+
+ services.avahi.enable = true;
+
+ systemd.services."4store-endpoint" = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ script = ''
+ ${run} '${pkgs.rdf4store}/bin/4s-httpd -D ${cfg.options} ${if cfg.listenAddress!=null then "-H ${cfg.listenAddress}" else "" } -p ${toString cfg.port} ${cfg.database}'
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/4store.nix b/nixpkgs/nixos/modules/services/databases/4store.nix
new file mode 100644
index 00000000000..be4351c1c38
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/4store.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.services.fourStore;
+ stateDir = "/var/lib/4store";
+ fourStoreUser = "fourstore";
+ run = "${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${fourStoreUser}";
+in
+with lib;
+{
+
+ ###### interface
+
+ options = {
+
+ services.fourStore = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable 4Store RDF database server.";
+ };
+
+ database = mkOption {
+ default = "";
+ description = "RDF database name. If it doesn't exist, it will be created. Databases are stored in ${stateDir}.";
+ };
+
+ options = mkOption {
+ default = "";
+ description = "Extra CLI options to pass to 4Store.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = singleton
+ { assertion = cfg.enable -> cfg.database != "";
+ message = "Must specify 4Store database name.";
+ };
+
+ users.users = singleton
+ { name = fourStoreUser;
+ uid = config.ids.uids.fourstore;
+ description = "4Store database user";
+ home = stateDir;
+ };
+
+ services.avahi.enable = true;
+
+ systemd.services."4store" = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ mkdir -p ${stateDir}/
+ chown ${fourStoreUser} ${stateDir}
+ if ! test -e "${stateDir}/${cfg.database}"; then
+ ${run} -c '${pkgs.rdf4store}/bin/4s-backend-setup ${cfg.database}'
+ fi
+ '';
+
+ script = ''
+ ${run} -c '${pkgs.rdf4store}/bin/4s-backend -D ${cfg.options} ${cfg.database}'
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/aerospike.nix b/nixpkgs/nixos/modules/services/databases/aerospike.nix
new file mode 100644
index 00000000000..4b905f90529
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/aerospike.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.aerospike;
+
+ aerospikeConf = pkgs.writeText "aerospike.conf" ''
+ # This stanza must come first.
+ service {
+ user aerospike
+ group aerospike
+ paxos-single-replica-limit 1 # Number of nodes where the replica count is automatically reduced to 1.
+ proto-fd-max 15000
+ work-directory ${cfg.workDir}
+ }
+ logging {
+ console {
+ context any info
+ }
+ }
+ mod-lua {
+ system-path ${cfg.package}/share/udf/lua
+ user-path ${cfg.workDir}/udf/lua
+ }
+ network {
+ ${cfg.networkConfig}
+ }
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.aerospike = {
+ enable = mkEnableOption "Aerospike server";
+
+ package = mkOption {
+ default = pkgs.aerospike;
+ defaultText = "pkgs.aerospike";
+ type = types.package;
+ description = "Which Aerospike derivation to use";
+ };
+
+ workDir = mkOption {
+ type = types.str;
+ default = "/var/lib/aerospike";
+ description = "Location where Aerospike stores its files";
+ };
+
+ networkConfig = mkOption {
+ type = types.lines;
+ default = ''
+ service {
+ address any
+ port 3000
+ }
+
+ heartbeat {
+ address any
+ mode mesh
+ port 3002
+ interval 150
+ timeout 10
+ }
+
+ fabric {
+ address any
+ port 3001
+ }
+
+ info {
+ address any
+ port 3003
+ }
+ '';
+ description = "network section of configuration file";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ namespace test {
+ replication-factor 2
+ memory-size 4G
+ default-ttl 30d
+ storage-engine memory
+ }
+ '';
+ description = "Extra configuration";
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.aerospike.enable {
+
+ users.users.aerospike = {
+ name = "aerospike";
+ group = "aerospike";
+ uid = config.ids.uids.aerospike;
+ description = "Aerospike server user";
+ };
+ users.groups.aerospike.gid = config.ids.gids.aerospike;
+
+ systemd.services.aerospike = rec {
+ description = "Aerospike server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/asd --fgdaemon --config-file ${aerospikeConf}";
+ User = "aerospike";
+ Group = "aerospike";
+ LimitNOFILE = 100000;
+ PermissionsStartOnly = true;
+ };
+
+ preStart = ''
+ if [ $(echo "$(${pkgs.procps}/bin/sysctl -n kernel.shmall) < 4294967296" | ${pkgs.bc}/bin/bc) == "1" ]; then
+ echo "kernel.shmall too low, setting to 4G pages"
+ ${pkgs.procps}/bin/sysctl -w kernel.shmall=4294967296
+ fi
+ if [ $(echo "$(${pkgs.procps}/bin/sysctl -n kernel.shmmax) < 1073741824" | ${pkgs.bc}/bin/bc) == "1" ]; then
+ echo "kernel.shmmax too low, setting to 1GB"
+ ${pkgs.procps}/bin/sysctl -w kernel.shmmax=1073741824
+ fi
+ if [ $(echo "$(cat /proc/sys/net/core/rmem_max) < 15728640" | ${pkgs.bc}/bin/bc) == "1" ]; then
+ echo "increasing socket buffer limit (/proc/sys/net/core/rmem_max): $(cat /proc/sys/net/core/rmem_max) -> 15728640"
+ echo 15728640 > /proc/sys/net/core/rmem_max
+ fi
+ if [ $(echo "$(cat /proc/sys/net/core/wmem_max) < 5242880" | ${pkgs.bc}/bin/bc) == "1" ]; then
+ echo "increasing socket buffer limit (/proc/sys/net/core/wmem_max): $(cat /proc/sys/net/core/wmem_max) -> 5242880"
+ echo 5242880 > /proc/sys/net/core/wmem_max
+ fi
+ install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}"
+ install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}/smd"
+ install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}/udf"
+ install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}/udf/lua"
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/cassandra.nix b/nixpkgs/nixos/modules/services/databases/cassandra.nix
new file mode 100644
index 00000000000..90c094f68b6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/cassandra.nix
@@ -0,0 +1,479 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cassandra;
+ defaultUser = "cassandra";
+ cassandraConfig = flip recursiveUpdate cfg.extraConfig
+ ({ commitlog_sync = "batch";
+ commitlog_sync_batch_window_in_ms = 2;
+ start_native_transport = cfg.allowClients;
+ cluster_name = cfg.clusterName;
+ partitioner = "org.apache.cassandra.dht.Murmur3Partitioner";
+ endpoint_snitch = "SimpleSnitch";
+ data_file_directories = [ "${cfg.homeDir}/data" ];
+ commitlog_directory = "${cfg.homeDir}/commitlog";
+ saved_caches_directory = "${cfg.homeDir}/saved_caches";
+ } // (lib.optionalAttrs (cfg.seedAddresses != []) {
+ seed_provider = [{
+ class_name = "org.apache.cassandra.locator.SimpleSeedProvider";
+ parameters = [ { seeds = concatStringsSep "," cfg.seedAddresses; } ];
+ }];
+ }) // (lib.optionalAttrs (lib.versionAtLeast cfg.package.version "3") {
+ hints_directory = "${cfg.homeDir}/hints";
+ })
+ );
+ cassandraConfigWithAddresses = cassandraConfig //
+ ( if cfg.listenAddress == null
+ then { listen_interface = cfg.listenInterface; }
+ else { listen_address = cfg.listenAddress; }
+ ) // (
+ if cfg.rpcAddress == null
+ then { rpc_interface = cfg.rpcInterface; }
+ else { rpc_address = cfg.rpcAddress; }
+ );
+ cassandraEtc = pkgs.stdenv.mkDerivation
+ { name = "cassandra-etc";
+ cassandraYaml = builtins.toJSON cassandraConfigWithAddresses;
+ cassandraEnvPkg = "${cfg.package}/conf/cassandra-env.sh";
+ cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig;
+ buildCommand = ''
+ mkdir -p "$out"
+
+ echo "$cassandraYaml" > "$out/cassandra.yaml"
+ ln -s "$cassandraLogbackConfig" "$out/logback.xml"
+
+ cp "$cassandraEnvPkg" "$out/cassandra-env.sh"
+
+ # Delete default JMX Port, otherwise we can't set it using env variable
+ sed -i '/JMX_PORT="7199"/d' "$out/cassandra-env.sh"
+
+ # Delete default password file
+ sed -i '/-Dcom.sun.management.jmxremote.password.file=\/etc\/cassandra\/jmxremote.password/d' "$out/cassandra-env.sh"
+ '';
+ };
+ defaultJmxRolesFile = builtins.foldl'
+ (left: right: left + right) ""
+ (map (role: "${role.username} ${role.password}") cfg.jmxRoles);
+ fullJvmOptions = cfg.jvmOpts
+ ++ lib.optionals (cfg.jmxRoles != []) [
+ "-Dcom.sun.management.jmxremote.authenticate=true"
+ "-Dcom.sun.management.jmxremote.password.file=${cfg.jmxRolesFile}"
+ ]
+ ++ lib.optionals cfg.remoteJmx [
+ "-Djava.rmi.server.hostname=${cfg.rpcAddress}"
+ ];
+in {
+ options.services.cassandra = {
+ enable = mkEnableOption ''
+ Apache Cassandra – Scalable and highly available database.
+ '';
+ clusterName = mkOption {
+ type = types.str;
+ default = "Test Cluster";
+ description = ''
+ The name of the cluster.
+ This setting prevents nodes in one logical cluster from joining
+ another. All nodes in a cluster must have the same value.
+ '';
+ };
+ user = mkOption {
+ type = types.str;
+ default = defaultUser;
+ description = "Run Apache Cassandra under this user.";
+ };
+ group = mkOption {
+ type = types.str;
+ default = defaultUser;
+ description = "Run Apache Cassandra under this group.";
+ };
+ homeDir = mkOption {
+ type = types.path;
+ default = "/var/lib/cassandra";
+ description = ''
+ Home directory for Apache Cassandra.
+ '';
+ };
+ package = mkOption {
+ type = types.package;
+ default = pkgs.cassandra;
+ defaultText = "pkgs.cassandra";
+ example = literalExample "pkgs.cassandra_3_11";
+ description = ''
+ The Apache Cassandra package to use.
+ '';
+ };
+ jvmOpts = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Populate the JVM_OPT environment variable.
+ '';
+ };
+ listenAddress = mkOption {
+ type = types.nullOr types.str;
+ default = "127.0.0.1";
+ example = literalExample "null";
+ description = ''
+ Address or interface to bind to and tell other Cassandra nodes
+ to connect to. You _must_ change this if you want multiple
+ nodes to be able to communicate!
+
+ Set listenAddress OR listenInterface, not both.
+
+ Leaving it blank leaves it up to
+ InetAddress.getLocalHost(). This will always do the Right
+ Thing _if_ the node is properly configured (hostname, name
+ resolution, etc), and the Right Thing is to use the address
+ associated with the hostname (it might not be).
+
+ Setting listen_address to 0.0.0.0 is always wrong.
+ '';
+ };
+ listenInterface = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "eth1";
+ description = ''
+ Set listenAddress OR listenInterface, not both. Interfaces
+ must correspond to a single address, IP aliasing is not
+ supported.
+ '';
+ };
+ rpcAddress = mkOption {
+ type = types.nullOr types.str;
+ default = "127.0.0.1";
+ example = literalExample "null";
+ description = ''
+ The address or interface to bind the native transport server to.
+
+ Set rpcAddress OR rpcInterface, not both.
+
+ Leaving rpcAddress blank has the same effect as on
+ listenAddress (i.e. it will be based on the configured hostname
+ of the node).
+
+ Note that unlike listenAddress, you can specify 0.0.0.0, but you
+ must also set extraConfig.broadcast_rpc_address to a value other
+ than 0.0.0.0.
+
+ For security reasons, you should not expose this port to the
+ internet. Firewall it if needed.
+ '';
+ };
+ rpcInterface = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "eth1";
+ description = ''
+ Set rpcAddress OR rpcInterface, not both. Interfaces must
+ correspond to a single address, IP aliasing is not supported.
+ '';
+ };
+ logbackConfig = mkOption {
+ type = types.lines;
+ default = ''
+ <configuration scan="false">
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%-5level %date{HH:mm:ss,SSS} %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <root level="INFO">
+ <appender-ref ref="STDOUT" />
+ </root>
+
+ <logger name="com.thinkaurelius.thrift" level="ERROR"/>
+ </configuration>
+ '';
+ description = ''
+ XML logback configuration for cassandra
+ '';
+ };
+ seedAddresses = mkOption {
+ type = types.listOf types.str;
+ default = [ "127.0.0.1" ];
+ description = ''
+ The addresses of hosts designated as contact points in the cluster. A
+ joining node contacts one of the nodes in the seeds list to learn the
+ topology of the ring.
+ Set to 127.0.0.1 for a single node cluster.
+ '';
+ };
+ allowClients = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enables or disables the native transport server (CQL binary protocol).
+ This server uses the same address as the <literal>rpcAddress</literal>,
+ but the port it uses is not <literal>rpc_port</literal> but
+ <literal>native_transport_port</literal>. See the official Cassandra
+ docs for more information on these variables and set them using
+ <literal>extraConfig</literal>.
+ '';
+ };
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ example =
+ { commitlog_sync_batch_window_in_ms = 3;
+ };
+ description = ''
+ Extra options to be merged into cassandra.yaml as nix attribute set.
+ '';
+ };
+ fullRepairInterval = mkOption {
+ type = types.nullOr types.str;
+ default = "3w";
+ example = literalExample "null";
+ description = ''
+ Set the interval how often full repairs are run, i.e.
+ <literal>nodetool repair --full</literal> is executed. See
+ https://cassandra.apache.org/doc/latest/operating/repair.html
+ for more information.
+
+ Set to <literal>null</literal> to disable full repairs.
+ '';
+ };
+ fullRepairOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "--partitioner-range" ];
+ description = ''
+ Options passed through to the full repair command.
+ '';
+ };
+ incrementalRepairInterval = mkOption {
+ type = types.nullOr types.str;
+ default = "3d";
+ example = literalExample "null";
+ description = ''
+ Set the interval how often incremental repairs are run, i.e.
+ <literal>nodetool repair</literal> is executed. See
+ https://cassandra.apache.org/doc/latest/operating/repair.html
+ for more information.
+
+ Set to <literal>null</literal> to disable incremental repairs.
+ '';
+ };
+ incrementalRepairOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "--partitioner-range" ];
+ description = ''
+ Options passed through to the incremental repair command.
+ '';
+ };
+ maxHeapSize = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "4G";
+ description = ''
+ Must be left blank or set together with heapNewSize.
+ If left blank a sensible value for the available amount of RAM and CPU
+ cores is calculated.
+
+ Override to set the amount of memory to allocate to the JVM at
+ start-up. For production use you may wish to adjust this for your
+ environment. MAX_HEAP_SIZE is the total amount of memory dedicated
+ to the Java heap. HEAP_NEWSIZE refers to the size of the young
+ generation.
+
+ The main trade-off for the young generation is that the larger it
+ is, the longer GC pause times will be. The shorter it is, the more
+ expensive GC will be (usually).
+ '';
+ };
+ heapNewSize = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "800M";
+ description = ''
+ Must be left blank or set together with heapNewSize.
+ If left blank a sensible value for the available amount of RAM and CPU
+ cores is calculated.
+
+ Override to set the amount of memory to allocate to the JVM at
+ start-up. For production use you may wish to adjust this for your
+ environment. HEAP_NEWSIZE refers to the size of the young
+ generation.
+
+ The main trade-off for the young generation is that the larger it
+ is, the longer GC pause times will be. The shorter it is, the more
+ expensive GC will be (usually).
+
+ The example HEAP_NEWSIZE assumes a modern 8-core+ machine for decent pause
+ times. If in doubt, and if you do not particularly want to tweak, go with
+ 100 MB per physical CPU core.
+ '';
+ };
+ mallocArenaMax = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 4;
+ description = ''
+ Set this to control the amount of arenas per-thread in glibc.
+ '';
+ };
+ remoteJmx = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Cassandra ships with JMX accessible *only* from localhost.
+ To enable remote JMX connections set to true.
+
+ Be sure to also enable authentication and/or TLS.
+ See: https://wiki.apache.org/cassandra/JmxSecurity
+ '';
+ };
+ jmxPort = mkOption {
+ type = types.int;
+ default = 7199;
+ description = ''
+ Specifies the default port over which Cassandra will be available for
+ JMX connections.
+ For security reasons, you should not expose this port to the internet.
+ Firewall it if needed.
+ '';
+ };
+ jmxRoles = mkOption {
+ default = [];
+ description = ''
+ Roles that are allowed to access the JMX (e.g. nodetool)
+ BEWARE: The passwords will be stored world readable in the nix-store.
+ It's recommended to use your own protected file using
+ <literal>jmxRolesFile</literal>
+
+ Doesn't work in versions older than 3.11 because they don't like that
+ it's world readable.
+ '';
+ type = types.listOf (types.submodule {
+ options = {
+ username = mkOption {
+ type = types.str;
+ description = "Username for JMX";
+ };
+ password = mkOption {
+ type = types.str;
+ description = "Password for JMX";
+ };
+ };
+ });
+ };
+ jmxRolesFile = mkOption {
+ type = types.nullOr types.path;
+ default = if (lib.versionAtLeast cfg.package.version "3.11")
+ then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile
+ else null;
+ example = "/var/lib/cassandra/jmx.password";
+ description = ''
+ Specify your own jmx roles file.
+
+ Make sure the permissions forbid "others" from reading the file if
+ you're using Cassandra below version 3.11.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions =
+ [ { assertion = (cfg.listenAddress == null) != (cfg.listenInterface == null);
+ message = "You have to set either listenAddress or listenInterface";
+ }
+ { assertion = (cfg.rpcAddress == null) != (cfg.rpcInterface == null);
+ message = "You have to set either rpcAddress or rpcInterface";
+ }
+ { assertion = (cfg.maxHeapSize == null) == (cfg.heapNewSize == null);
+ message = "If you set either of maxHeapSize or heapNewSize you have to set both";
+ }
+ { assertion = cfg.remoteJmx -> cfg.jmxRolesFile != null;
+ message = ''
+ If you want JMX available remotely you need to set a password using
+ <literal>jmxRoles</literal> or <literal>jmxRolesFile</literal> if
+ using Cassandra older than v3.11.
+ '';
+ }
+ ];
+ users = mkIf (cfg.user == defaultUser) {
+ extraUsers.${defaultUser} =
+ { group = cfg.group;
+ home = cfg.homeDir;
+ createHome = true;
+ uid = config.ids.uids.cassandra;
+ description = "Cassandra service user";
+ };
+ extraGroups.${defaultUser}.gid = config.ids.gids.cassandra;
+ };
+
+ systemd.services.cassandra =
+ { description = "Apache Cassandra service";
+ after = [ "network.target" ];
+ environment =
+ { CASSANDRA_CONF = "${cassandraEtc}";
+ JVM_OPTS = builtins.concatStringsSep " " fullJvmOptions;
+ MAX_HEAP_SIZE = toString cfg.maxHeapSize;
+ HEAP_NEWSIZE = toString cfg.heapNewSize;
+ MALLOC_ARENA_MAX = toString cfg.mallocArenaMax;
+ LOCAL_JMX = if cfg.remoteJmx then "no" else "yes";
+ JMX_PORT = toString cfg.jmxPort;
+ };
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig =
+ { User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${cfg.package}/bin/cassandra -f";
+ SuccessExitStatus = 143;
+ };
+ };
+
+ systemd.services.cassandra-full-repair =
+ { description = "Perform a full repair on this Cassandra node";
+ after = [ "cassandra.service" ];
+ requires = [ "cassandra.service" ];
+ serviceConfig =
+ { User = cfg.user;
+ Group = cfg.group;
+ ExecStart =
+ lib.concatStringsSep " "
+ ([ "${cfg.package}/bin/nodetool" "repair" "--full"
+ ] ++ cfg.fullRepairOptions);
+ };
+ };
+ systemd.timers.cassandra-full-repair =
+ mkIf (cfg.fullRepairInterval != null) {
+ description = "Schedule full repairs on Cassandra";
+ wantedBy = [ "timers.target" ];
+ timerConfig =
+ { OnBootSec = cfg.fullRepairInterval;
+ OnUnitActiveSec = cfg.fullRepairInterval;
+ Persistent = true;
+ };
+ };
+
+ systemd.services.cassandra-incremental-repair =
+ { description = "Perform an incremental repair on this cassandra node.";
+ after = [ "cassandra.service" ];
+ requires = [ "cassandra.service" ];
+ serviceConfig =
+ { User = cfg.user;
+ Group = cfg.group;
+ ExecStart =
+ lib.concatStringsSep " "
+ ([ "${cfg.package}/bin/nodetool" "repair"
+ ] ++ cfg.incrementalRepairOptions);
+ };
+ };
+ systemd.timers.cassandra-incremental-repair =
+ mkIf (cfg.incrementalRepairInterval != null) {
+ description = "Schedule incremental repairs on Cassandra";
+ wantedBy = [ "timers.target" ];
+ timerConfig =
+ { OnBootSec = cfg.incrementalRepairInterval;
+ OnUnitActiveSec = cfg.incrementalRepairInterval;
+ Persistent = true;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/clickhouse.nix b/nixpkgs/nixos/modules/services/databases/clickhouse.nix
new file mode 100644
index 00000000000..dbabcae43ee
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/clickhouse.nix
@@ -0,0 +1,71 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.services.clickhouse;
+in
+with lib;
+{
+
+ ###### interface
+
+ options = {
+
+ services.clickhouse = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable ClickHouse database server.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users.clickhouse = {
+ name = "clickhouse";
+ uid = config.ids.uids.clickhouse;
+ group = "clickhouse";
+ description = "ClickHouse server user";
+ };
+
+ users.groups.clickhouse.gid = config.ids.gids.clickhouse;
+
+ systemd.services.clickhouse = {
+ description = "ClickHouse server";
+
+ wantedBy = [ "multi-user.target" ];
+
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ User = "clickhouse";
+ Group = "clickhouse";
+ ConfigurationDirectory = "clickhouse-server";
+ StateDirectory = "clickhouse";
+ LogsDirectory = "clickhouse";
+ ExecStart = "${pkgs.clickhouse}/bin/clickhouse-server --config-file=${pkgs.clickhouse}/etc/clickhouse-server/config.xml";
+ };
+ };
+
+ environment.etc = {
+ "clickhouse-server/config.xml" = {
+ source = "${pkgs.clickhouse}/etc/clickhouse-server/config.xml";
+ };
+
+ "clickhouse-server/users.xml" = {
+ source = "${pkgs.clickhouse}/etc/clickhouse-server/users.xml";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.clickhouse ];
+
+ # startup requires a `/etc/localtime` which only if exists if `time.timeZone != null`
+ time.timeZone = mkDefault "UTC";
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/cockroachdb.nix b/nixpkgs/nixos/modules/services/databases/cockroachdb.nix
new file mode 100644
index 00000000000..268fdcc819f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/cockroachdb.nix
@@ -0,0 +1,217 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cockroachdb;
+ crdb = cfg.package;
+
+ escape = builtins.replaceStrings ["%"] ["%%"];
+ ifNotNull = v: s: optionalString (v != null) s;
+
+ startupCommand = lib.concatStringsSep " "
+ [ # Basic startup
+ "${crdb}/bin/cockroach start"
+ "--logtostderr"
+ "--store=/var/lib/cockroachdb"
+ (ifNotNull cfg.locality "--locality='${cfg.locality}'")
+
+ # WebUI settings
+ "--http-addr='${cfg.http.address}:${toString cfg.http.port}'"
+
+ # Cluster listen address
+ "--listen-addr='${cfg.listen.address}:${toString cfg.listen.port}'"
+
+ # Cluster configuration
+ (ifNotNull cfg.join "--join=${cfg.join}")
+
+ # Cache and memory settings. Must be escaped.
+ "--cache='${escape cfg.cache}'"
+ "--max-sql-memory='${escape cfg.maxSqlMemory}'"
+
+ # Certificate/security settings.
+ (if cfg.insecure then "--insecure" else "--certs-dir=${cfg.certsDir}")
+ ];
+
+ addressOption = descr: defaultPort: {
+ address = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Address to bind to for ${descr}";
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = defaultPort;
+ description = "Port to bind to for ${descr}";
+ };
+ };
+in
+
+{
+ options = {
+ services.cockroachdb = {
+ enable = mkEnableOption "CockroachDB Server";
+
+ listen = addressOption "intra-cluster communication" 26257;
+
+ http = addressOption "http-based Admin UI" 8080;
+
+ locality = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ An ordered, comma-separated list of key-value pairs that describe the
+ topography of the machine. Topography might include country,
+ datacenter or rack designations. Data is automatically replicated to
+ maximize diversities of each tier. The order of tiers is used to
+ determine the priority of the diversity, so the more inclusive
+ localities like country should come before less inclusive localities
+ like datacenter. The tiers and order must be the same on all nodes.
+ Including more tiers is better than including fewer. For example:
+
+ <literal>
+ country=us,region=us-west,datacenter=us-west-1b,rack=12
+ country=ca,region=ca-east,datacenter=ca-east-2,rack=4
+
+ planet=earth,province=manitoba,colo=secondary,power=3
+ </literal>
+ '';
+ };
+
+ join = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "The addresses for connecting the node to a cluster.";
+ };
+
+ insecure = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Run in insecure mode.";
+ };
+
+ certsDir = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "The path to the certificate directory.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "cockroachdb";
+ description = "User account under which CockroachDB runs";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "cockroachdb";
+ description = "User account under which CockroachDB runs";
+ };
+
+ openPorts = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Open firewall ports for cluster communication by default";
+ };
+
+ cache = mkOption {
+ type = types.str;
+ default = "25%";
+ description = ''
+ The total size for caches.
+
+ This can be a percentage, expressed with a fraction sign or as a
+ decimal-point number, or any bytes-based unit. For example,
+ <literal>"25%"</literal>, <literal>"0.25"</literal> both represent
+ 25% of the available system memory. The values
+ <literal>"1000000000"</literal> and <literal>"1GB"</literal> both
+ represent 1 gigabyte of memory.
+
+ '';
+ };
+
+ maxSqlMemory = mkOption {
+ type = types.str;
+ default = "25%";
+ description = ''
+ The maximum in-memory storage capacity available to store temporary
+ data for SQL queries.
+
+ This can be a percentage, expressed with a fraction sign or as a
+ decimal-point number, or any bytes-based unit. For example,
+ <literal>"25%"</literal>, <literal>"0.25"</literal> both represent
+ 25% of the available system memory. The values
+ <literal>"1000000000"</literal> and <literal>"1GB"</literal> both
+ represent 1 gigabyte of memory.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.cockroachdb;
+ defaultText = "pkgs.cockroachdb";
+ description = ''
+ The CockroachDB derivation to use for running the service.
+
+ This would primarily be useful to enable Enterprise Edition features
+ in your own custom CockroachDB build (Nixpkgs CockroachDB binaries
+ only contain open source features and open source code).
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.cockroachdb.enable {
+ assertions = [
+ { assertion = !cfg.insecure -> cfg.certsDir != null;
+ message = "CockroachDB must have a set of SSL certificates (.certsDir), or run in Insecure Mode (.insecure = true)";
+ }
+ ];
+
+ environment.systemPackages = [ crdb ];
+
+ users.users = optionalAttrs (cfg.user == "cockroachdb") (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/nixpkgs/nixos/modules/services/databases/couchdb.nix b/nixpkgs/nixos/modules/services/databases/couchdb.nix
new file mode 100644
index 00000000000..53224db1d89
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/couchdb.nix
@@ -0,0 +1,201 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.couchdb;
+ useVersion2 = strings.versionAtLeast (strings.getVersion cfg.package) "2.0";
+ configFile = pkgs.writeText "couchdb.ini" (
+ ''
+ [couchdb]
+ database_dir = ${cfg.databaseDir}
+ uri_file = ${cfg.uriFile}
+ view_index_dir = ${cfg.viewIndexDir}
+ '' + (if useVersion2 then
+ ''
+ [chttpd]
+ '' else
+ ''
+ [httpd]
+ '') +
+ ''
+ port = ${toString cfg.port}
+ bind_address = ${cfg.bindAddress}
+
+ [log]
+ file = ${cfg.logFile}
+ '');
+ executable = if useVersion2 then "${cfg.package}/bin/couchdb"
+ else ''${cfg.package}/bin/couchdb -a ${configFile} -a ${pkgs.writeText "couchdb-extra.ini" cfg.extraConfig} -a ${cfg.configFile}'';
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.couchdb = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run CouchDB Server.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.couchdb;
+ defaultText = "pkgs.couchdb";
+ example = literalExample "pkgs.couchdb";
+ description = ''
+ CouchDB package to use.
+ '';
+ };
+
+
+ user = mkOption {
+ type = types.str;
+ default = "couchdb";
+ description = ''
+ User account under which couchdb runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "couchdb";
+ description = ''
+ Group account under which couchdb runs.
+ '';
+ };
+
+ # couchdb options: http://docs.couchdb.org/en/latest/config/index.html
+
+ databaseDir = mkOption {
+ type = types.path;
+ default = "/var/lib/couchdb";
+ description = ''
+ Specifies location of CouchDB database files (*.couch named). This
+ location should be writable and readable for the user the CouchDB
+ service runs as (couchdb by default).
+ '';
+ };
+
+ uriFile = mkOption {
+ type = types.path;
+ default = "/run/couchdb/couchdb.uri";
+ description = ''
+ This file contains the full URI that can be used to access this
+ instance of CouchDB. It is used to help discover the port CouchDB is
+ running on (if it was set to 0 (e.g. automatically assigned any free
+ one). This file should be writable and readable for the user that
+ runs the CouchDB service (couchdb by default).
+ '';
+ };
+
+ viewIndexDir = mkOption {
+ type = types.path;
+ default = "/var/lib/couchdb";
+ description = ''
+ Specifies location of CouchDB view index files. This location should
+ be writable and readable for the user that runs the CouchDB service
+ (couchdb by default).
+ '';
+ };
+
+ bindAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ Defines the IP address by which CouchDB will be accessible.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 5984;
+ description = ''
+ Defined the port number to listen.
+ '';
+ };
+
+ logFile = mkOption {
+ type = types.path;
+ default = "/var/log/couchdb.log";
+ description = ''
+ Specifies the location of file for logging output.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration. Overrides any other cofiguration.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ description = ''
+ Configuration file for persisting runtime changes. File
+ needs to be readable and writable from couchdb user/group.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.couchdb.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ services.couchdb.configFile = mkDefault
+ (if useVersion2 then "/var/lib/couchdb/local.ini" else "/var/lib/couchdb/couchdb.ini");
+
+ systemd.tmpfiles.rules = [
+ "d '${dirOf cfg.uriFile}' - ${cfg.user} ${cfg.group} - -"
+ "f '${cfg.logFile}' - ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.databaseDir}' - ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.viewIndexDir}' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.couchdb = {
+ description = "CouchDB Server";
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ touch ${cfg.configFile}
+ '';
+
+ environment = mkIf useVersion2 {
+ # we are actually specifying 4 configuration files:
+ # 1. the preinstalled default.ini
+ # 2. the module configuration
+ # 3. the extraConfig from the module options
+ # 4. the locally writable config file, which couchdb itself writes to
+ ERL_FLAGS= ''-couch_ini ${cfg.package}/etc/default.ini ${configFile} ${pkgs.writeText "couchdb-extra.ini" cfg.extraConfig} ${cfg.configFile}'';
+ };
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = executable;
+ };
+ };
+
+ users.users.couchdb = {
+ description = "CouchDB Server user";
+ group = "couchdb";
+ uid = config.ids.uids.couchdb;
+ };
+
+ users.groups.couchdb.gid = config.ids.gids.couchdb;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/firebird.nix b/nixpkgs/nixos/modules/services/databases/firebird.nix
new file mode 100644
index 00000000000..042c9841df5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/firebird.nix
@@ -0,0 +1,166 @@
+{ config, lib, pkgs, ... }:
+
+# TODO: This may file may need additional review, eg which configuartions to
+# expose to the user.
+#
+# I only used it to access some simple databases.
+
+# test:
+# isql, then type the following commands:
+# CREATE DATABASE '/var/db/firebird/data/test.fdb' USER 'SYSDBA' PASSWORD 'masterkey';
+# CONNECT '/var/db/firebird/data/test.fdb' USER 'SYSDBA' PASSWORD 'masterkey';
+# CREATE TABLE test ( text varchar(100) );
+# DROP DATABASE;
+#
+# Be careful, virtuoso-opensource also provides a different isql command !
+
+# There are at least two ways to run firebird. superserver has been choosen
+# however there are no strong reasons to prefer this or the other one AFAIK
+# Eg superserver is said to be most efficiently using resources according to
+# http://www.firebirdsql.org/manual/qsg25-classic-or-super.html
+
+with lib;
+
+let
+
+ cfg = config.services.firebird;
+
+ firebird = cfg.package;
+
+ dataDir = "${cfg.baseDir}/data";
+ systemDir = "${cfg.baseDir}/system";
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.firebird = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable the Firebird super server.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.firebirdSuper;
+ defaultText = "pkgs.firebirdSuper";
+ type = types.package;
+ /*
+ Example: <code>package = pkgs.firebirdSuper.override { icu =
+ pkgs.icu; };</code> which is not recommended for compatibility
+ reasons. See comments at the firebirdSuper derivation
+ */
+
+ description = ''
+ Which firebird derivation to use.
+ '';
+ };
+
+ port = mkOption {
+ default = "3050";
+ description = ''
+ Port Firebird uses.
+ '';
+ };
+
+ user = mkOption {
+ default = "firebird";
+ description = ''
+ User account under which firebird runs.
+ '';
+ };
+
+ baseDir = mkOption {
+ default = "/var/db/firebird"; # ubuntu is using /var/lib/firebird/2.1/data/.. ?
+ description = ''
+ Location containing data/ and system/ directories.
+ data/ stores the databases, system/ stores the password database security2.fdb.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.firebird.enable {
+
+ environment.systemPackages = [cfg.package];
+
+ systemd.tmpfiles.rules = [
+ "d '${dataDir}' 0700 ${cfg.user} - - -"
+ "d '${systemDir}' 0700 ${cfg.user} - - -"
+ ];
+
+ systemd.services.firebird =
+ { description = "Firebird Super-Server";
+
+ wantedBy = [ "multi-user.target" ];
+
+ # TODO: moving security2.fdb into the data directory works, maybe there
+ # is a better way
+ preStart =
+ ''
+ if ! test -e "${systemDir}/security2.fdb"; then
+ cp ${firebird}/security2.fdb "${systemDir}"
+ fi
+
+ chmod -R 700 "${dataDir}" "${systemDir}" /var/log/firebird
+ '';
+
+ serviceConfig.User = cfg.user;
+ serviceConfig.LogsDirectory = "firebird";
+ serviceConfig.LogsDirectoryMode = "0700";
+ serviceConfig.ExecStart = ''${firebird}/bin/fbserver -d'';
+
+ # TODO think about shutdown
+ };
+
+ environment.etc."firebird/firebird.msg".source = "${firebird}/firebird.msg";
+
+ # think about this again - and eventually make it an option
+ environment.etc."firebird/firebird.conf".text = ''
+ # RootDirectory = Restrict ${dataDir}
+ DatabaseAccess = Restrict ${dataDir}
+ ExternalFileAccess = Restrict ${dataDir}
+ # what is this? is None allowed?
+ UdfAccess = None
+ # "Native" = traditional interbase/firebird, "mixed" is windows only
+ Authentication = Native
+
+ # defaults to -1 on non Win32
+ #MaxUnflushedWrites = 100
+ #MaxUnflushedWriteTime = 100
+
+ # show trace if trouble occurs (does this require debug build?)
+ # BugcheckAbort = 0
+ # ConnectionTimeout = 180
+
+ #RemoteServiceName = gds_db
+ RemoteServicePort = ${cfg.port}
+
+ # randomly choose port for server Event Notification
+ #RemoteAuxPort = 0
+ # rsetrict connections to a network card:
+ #RemoteBindAddress =
+ # there are some additional settings which should be reviewed
+ '';
+
+ users.users.firebird = {
+ description = "Firebird server user";
+ group = "firebird";
+ uid = config.ids.uids.firebird;
+ };
+
+ users.groups.firebird.gid = config.ids.gids.firebird;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/foundationdb.nix b/nixpkgs/nixos/modules/services/databases/foundationdb.nix
new file mode 100644
index 00000000000..8f8d0da7c8d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/foundationdb.nix
@@ -0,0 +1,429 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.foundationdb;
+ pkg = cfg.package;
+
+ # used for initial cluster configuration
+ initialIpAddr = if (cfg.publicAddress != "auto") then cfg.publicAddress else "127.0.0.1";
+
+ fdbServers = n:
+ concatStringsSep "\n" (map (x: "[fdbserver.${toString (x+cfg.listenPortStart)}]") (range 0 (n - 1)));
+
+ backupAgents = n:
+ concatStringsSep "\n" (map (x: "[backup_agent.${toString x}]") (range 1 n));
+
+ configFile = pkgs.writeText "foundationdb.conf" ''
+ [general]
+ cluster_file = /etc/foundationdb/fdb.cluster
+
+ [fdbmonitor]
+ restart_delay = ${toString cfg.restartDelay}
+ user = ${cfg.user}
+ group = ${cfg.group}
+
+ [fdbserver]
+ command = ${pkg}/bin/fdbserver
+ public_address = ${cfg.publicAddress}:$ID
+ listen_address = ${cfg.listenAddress}
+ datadir = ${cfg.dataDir}/$ID
+ logdir = ${cfg.logDir}
+ logsize = ${cfg.logSize}
+ maxlogssize = ${cfg.maxLogSize}
+ ${optionalString (cfg.class != null) "class = ${cfg.class}"}
+ memory = ${cfg.memory}
+ storage_memory = ${cfg.storageMemory}
+
+ ${optionalString (lib.versionAtLeast cfg.package.version "6.1") ''
+ trace_format = ${cfg.traceFormat}
+ ''}
+
+ ${optionalString (cfg.tls != null) ''
+ tls_plugin = ${pkg}/libexec/plugins/FDBLibTLS.so
+ tls_certificate_file = ${cfg.tls.certificate}
+ tls_key_file = ${cfg.tls.key}
+ tls_verify_peers = ${cfg.tls.allowedPeers}
+ ''}
+
+ ${optionalString (cfg.locality.machineId != null) "locality_machineid=${cfg.locality.machineId}"}
+ ${optionalString (cfg.locality.zoneId != null) "locality_zoneid=${cfg.locality.zoneId}"}
+ ${optionalString (cfg.locality.datacenterId != null) "locality_dcid=${cfg.locality.datacenterId}"}
+ ${optionalString (cfg.locality.dataHall != null) "locality_data_hall=${cfg.locality.dataHall}"}
+
+ ${fdbServers cfg.serverProcesses}
+
+ [backup_agent]
+ command = ${pkg}/libexec/backup_agent
+ ${backupAgents cfg.backupProcesses}
+ '';
+in
+{
+ options.services.foundationdb = {
+
+ enable = mkEnableOption "FoundationDB Server";
+
+ package = mkOption {
+ type = types.package;
+ description = ''
+ The FoundationDB package to use for this server. This must be specified by the user
+ in order to ensure migrations and upgrades are controlled appropriately.
+ '';
+ };
+
+ publicAddress = mkOption {
+ type = types.str;
+ default = "auto";
+ description = "Publicly visible IP address of the process. Port is determined by process ID";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "public";
+ description = "Publicly visible IP address of the process. Port is determined by process ID";
+ };
+
+ listenPortStart = mkOption {
+ type = types.int;
+ default = 4500;
+ description = ''
+ Starting port number for database listening sockets. Every FDB process binds to a
+ subsequent port, to this number reflects the start of the overall range. e.g. having
+ 8 server processes will use all ports between 4500 and 4507.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open the firewall ports corresponding to FoundationDB processes and coordinators
+ using <option>config.networking.firewall.*</option>.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/foundationdb";
+ description = "Data directory. All cluster data will be put under here.";
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ default = "/var/log/foundationdb";
+ description = "Log directory.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "foundationdb";
+ description = "User account under which FoundationDB runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "foundationdb";
+ description = "Group account under which FoundationDB runs.";
+ };
+
+ class = mkOption {
+ type = types.nullOr (types.enum [ "storage" "transaction" "stateless" ]);
+ default = null;
+ description = "Process class";
+ };
+
+ restartDelay = mkOption {
+ type = types.int;
+ default = 10;
+ description = "Number of seconds to wait before restarting servers.";
+ };
+
+ logSize = mkOption {
+ type = types.str;
+ default = "10MiB";
+ description = ''
+ Roll over to a new log file after the current log file
+ reaches the specified size.
+ '';
+ };
+
+ maxLogSize = mkOption {
+ type = types.str;
+ default = "100MiB";
+ description = ''
+ Delete the oldest log file when the total size of all log
+ files exceeds the specified size. If set to 0, old log files
+ will not be deleted.
+ '';
+ };
+
+ serverProcesses = mkOption {
+ type = types.int;
+ default = 1;
+ description = "Number of fdbserver processes to run.";
+ };
+
+ backupProcesses = mkOption {
+ type = types.int;
+ default = 1;
+ description = "Number of backup_agent processes to run for snapshots.";
+ };
+
+ memory = mkOption {
+ type = types.str;
+ default = "8GiB";
+ description = ''
+ Maximum memory used by the process. The default value is
+ <literal>8GiB</literal>. When specified without a unit,
+ <literal>MiB</literal> is assumed. This parameter does not
+ change the memory allocation of the program. Rather, it sets
+ a hard limit beyond which the process will kill itself and
+ be restarted. The default value of <literal>8GiB</literal>
+ is double the intended memory usage in the default
+ configuration (providing an emergency buffer to deal with
+ memory leaks or similar problems). It is not recommended to
+ decrease the value of this parameter below its default
+ value. It may be increased if you wish to allocate a very
+ large amount of storage engine memory or cache. In
+ particular, when the <literal>storageMemory</literal>
+ parameter is increased, the <literal>memory</literal>
+ parameter should be increased by an equal amount.
+ '';
+ };
+
+ storageMemory = mkOption {
+ type = types.str;
+ default = "1GiB";
+ description = ''
+ Maximum memory used for data storage. The default value is
+ <literal>1GiB</literal>. When specified without a unit,
+ <literal>MB</literal> is assumed. Clusters using the memory
+ storage engine will be restricted to using this amount of
+ memory per process for purposes of data storage. Memory
+ overhead associated with storing the data is counted against
+ this total. If you increase the
+ <literal>storageMemory</literal>, you should also increase
+ the <literal>memory</literal> parameter by the same amount.
+ '';
+ };
+
+ tls = mkOption {
+ default = null;
+ description = ''
+ FoundationDB Transport Security Layer (TLS) settings.
+ '';
+
+ type = types.nullOr (types.submodule ({
+ options = {
+ certificate = mkOption {
+ type = types.str;
+ description = ''
+ Path to the TLS certificate file. This certificate will
+ be offered to, and may be verified by, clients.
+ '';
+ };
+
+ key = mkOption {
+ type = types.str;
+ description = "Private key file for the certificate.";
+ };
+
+ allowedPeers = mkOption {
+ type = types.str;
+ default = "Check.Valid=1,Check.Unexpired=1";
+ description = ''
+ "Peer verification string". This may be used to adjust which TLS
+ client certificates a server will accept, as a form of user
+ authorization; for example, it may only accept TLS clients who
+ offer a certificate abiding by some locality or organization name.
+
+ For more information, please see the FoundationDB documentation.
+ '';
+ };
+ };
+ }));
+ };
+
+ locality = mkOption {
+ default = {
+ machineId = null;
+ zoneId = null;
+ datacenterId = null;
+ dataHall = null;
+ };
+
+ description = ''
+ FoundationDB locality settings.
+ '';
+
+ type = types.submodule ({
+ options = {
+ machineId = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Machine identifier key. All processes on a machine should share a
+ unique id. By default, processes on a machine determine a unique id to share.
+ This does not generally need to be set.
+ '';
+ };
+
+ zoneId = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Zone identifier key. Processes that share a zone id are
+ considered non-unique for the purposes of data replication.
+ If unset, defaults to machine id.
+ '';
+ };
+
+ datacenterId = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Data center identifier key. All processes physically located in a
+ data center should share the id. If you are depending on data
+ center based replication this must be set on all processes.
+ '';
+ };
+
+ dataHall = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Data hall identifier key. All processes physically located in a
+ data hall should share the id. If you are depending on data
+ hall based replication this must be set on all processes.
+ '';
+ };
+ };
+ });
+ };
+
+ extraReadWritePaths = mkOption {
+ default = [ ];
+ type = types.listOf types.path;
+ description = ''
+ An extra set of filesystem paths that FoundationDB can read to
+ and write from. By default, FoundationDB runs under a heavily
+ namespaced systemd environment without write access to most of
+ the filesystem outside of its data and log directories. By
+ adding paths to this list, the set of writeable paths will be
+ expanded. This is useful for allowing e.g. backups to local files,
+ which must be performed on behalf of the foundationdb service.
+ '';
+ };
+
+ pidfile = mkOption {
+ type = types.path;
+ default = "/run/foundationdb.pid";
+ description = "Path to pidfile for fdbmonitor.";
+ };
+
+ traceFormat = mkOption {
+ type = types.enum [ "xml" "json" ];
+ default = "xml";
+ description = "Trace logging format.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ { assertion = lib.versionOlder cfg.package.version "6.1" -> cfg.traceFormat == "xml";
+ message = ''
+ Versions of FoundationDB before 6.1 do not support configurable trace formats (only XML is supported).
+ This option has no effect for version '' + cfg.package.version + '', and enabling it is an error.
+ '';
+ }
+ ];
+
+ environment.systemPackages = [ pkg ];
+
+ users.users = optionalAttrs (cfg.user == "foundationdb") (singleton
+ { name = "foundationdb";
+ description = "FoundationDB User";
+ uid = config.ids.uids.foundationdb;
+ group = cfg.group;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "foundationdb") (singleton
+ { name = "foundationdb";
+ gid = config.ids.gids.foundationdb;
+ });
+
+ networking.firewall.allowedTCPPortRanges = mkIf cfg.openFirewall
+ [ { from = cfg.listenPortStart;
+ to = (cfg.listenPortStart + cfg.serverProcesses) - 1;
+ }
+ ];
+
+ systemd.tmpfiles.rules = [
+ "d /etc/foundationdb 0755 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.logDir}' 0770 ${cfg.user} ${cfg.group} - -"
+ "F '${cfg.pidfile}' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.foundationdb = {
+ description = "FoundationDB Service";
+
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ unitConfig =
+ { RequiresMountsFor = "${cfg.dataDir} ${cfg.logDir}";
+ };
+
+ serviceConfig =
+ let rwpaths = [ cfg.dataDir cfg.logDir cfg.pidfile "/etc/foundationdb" ]
+ ++ cfg.extraReadWritePaths;
+ in
+ { Type = "simple";
+ Restart = "always";
+ RestartSec = 5;
+ User = cfg.user;
+ Group = cfg.group;
+ PIDFile = "${cfg.pidfile}";
+
+ PermissionsStartOnly = true; # setup needs root perms
+ TimeoutSec = 120; # give reasonable time to shut down
+
+ # Security options
+ NoNewPrivileges = true;
+ ProtectHome = true;
+ ProtectSystem = "strict";
+ ProtectKernelTunables = true;
+ ProtectControlGroups = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ReadWritePaths = lib.concatStringsSep " " (map (x: "-" + x) rwpaths);
+ };
+
+ path = [ pkg pkgs.coreutils ];
+
+ preStart = ''
+ if [ ! -f /etc/foundationdb/fdb.cluster ]; then
+ cf=/etc/foundationdb/fdb.cluster
+ desc=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c8)
+ rand=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c8)
+ echo ''${desc}:''${rand}@${initialIpAddr}:${builtins.toString cfg.listenPortStart} > $cf
+ chmod 0664 $cf
+ touch "${cfg.dataDir}/.first_startup"
+ fi
+ '';
+
+ script = "exec fdbmonitor --lockfile ${cfg.pidfile} --conffile ${configFile}";
+
+ postStart = ''
+ if [ -e "${cfg.dataDir}/.first_startup" ]; then
+ fdbcli --exec "configure new single ssd"
+ rm -f "${cfg.dataDir}/.first_startup";
+ fi
+ '';
+ };
+ };
+
+ meta.doc = ./foundationdb.xml;
+ meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+}
diff --git a/nixpkgs/nixos/modules/services/databases/foundationdb.xml b/nixpkgs/nixos/modules/services/databases/foundationdb.xml
new file mode 100644
index 00000000000..b0b1ebeab45
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/foundationdb.xml
@@ -0,0 +1,443 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="module-services-foundationdb">
+ <title>FoundationDB</title>
+ <para>
+ <emphasis>Source:</emphasis>
+ <filename>modules/services/databases/foundationdb.nix</filename>
+ </para>
+ <para>
+ <emphasis>Upstream documentation:</emphasis>
+ <link xlink:href="https://apple.github.io/foundationdb/"/>
+ </para>
+ <para>
+ <emphasis>Maintainer:</emphasis> Austin Seipp
+ </para>
+ <para>
+ <emphasis>Available version(s):</emphasis> 5.1.x, 5.2.x, 6.0.x
+ </para>
+ <para>
+ FoundationDB (or "FDB") is an open source, distributed, transactional
+ key-value store.
+ </para>
+ <section xml:id="module-services-foundationdb-configuring">
+ <title>Configuring and basic setup</title>
+
+ <para>
+ To enable FoundationDB, add the following to your
+ <filename>configuration.nix</filename>:
+<programlisting>
+services.foundationdb.enable = true;
+services.foundationdb.package = pkgs.foundationdb52; # FoundationDB 5.2.x
+</programlisting>
+ </para>
+
+ <para>
+ The <option>services.foundationdb.package</option> option is required, and
+ must always be specified. Due to the fact FoundationDB network protocols and
+ on-disk storage formats may change between (major) versions, and upgrades
+ must be explicitly handled by the user, you must always manually specify
+ this yourself so that the NixOS module will use the proper version. Note
+ that minor, bugfix releases are always compatible.
+ </para>
+
+ <para>
+ After running <command>nixos-rebuild</command>, you can verify whether
+ FoundationDB is running by executing <command>fdbcli</command> (which is
+ added to <option>environment.systemPackages</option>):
+<screen>
+<prompt>$ </prompt>sudo -u foundationdb fdbcli
+Using cluster file `/etc/foundationdb/fdb.cluster'.
+
+The database is available.
+
+Welcome to the fdbcli. For help, type `help'.
+<prompt>fdb> </prompt>status
+
+Using cluster file `/etc/foundationdb/fdb.cluster'.
+
+Configuration:
+ Redundancy mode - single
+ Storage engine - memory
+ Coordinators - 1
+
+Cluster:
+ FoundationDB processes - 1
+ Machines - 1
+ Memory availability - 5.4 GB per process on machine with least available
+ Fault Tolerance - 0 machines
+ Server time - 04/20/18 15:21:14
+
+...
+
+<prompt>fdb></prompt>
+</screen>
+ </para>
+
+ <para>
+ You can also write programs using the available client libraries. For
+ example, the following Python program can be run in order to grab the
+ cluster status, as a quick example. (This example uses
+ <command>nix-shell</command> shebang support to automatically supply the
+ necessary Python modules).
+<screen>
+<prompt>a@link> </prompt>cat fdb-status.py
+#! /usr/bin/env nix-shell
+#! nix-shell -i python -p python pythonPackages.foundationdb52
+
+import fdb
+import json
+
+def main():
+ fdb.api_version(520)
+ db = fdb.open()
+
+ @fdb.transactional
+ def get_status(tr):
+ return str(tr['\xff\xff/status/json'])
+
+ obj = json.loads(get_status(db))
+ print('FoundationDB available: %s' % obj['client']['database_status']['available'])
+
+if __name__ == "__main__":
+ main()
+<prompt>a@link> </prompt>chmod +x fdb-status.py
+<prompt>a@link> </prompt>./fdb-status.py
+FoundationDB available: True
+<prompt>a@link></prompt>
+</screen>
+ </para>
+
+ <para>
+ FoundationDB is run under the <command>foundationdb</command> user and group
+ by default, but this may be changed in the NixOS configuration. The systemd
+ unit <command>foundationdb.service</command> controls the
+ <command>fdbmonitor</command> process.
+ </para>
+
+ <para>
+ By default, the NixOS module for FoundationDB creates a single SSD-storage
+ based database for development and basic usage. This storage engine is
+ designed for SSDs and will perform poorly on HDDs; however it can handle far
+ more data than the alternative "memory" engine and is a better default
+ choice for most deployments. (Note that you can change the storage backend
+ on-the-fly for a given FoundationDB cluster using
+ <command>fdbcli</command>.)
+ </para>
+
+ <para>
+ Furthermore, only 1 server process and 1 backup agent are started in the
+ default configuration. See below for more on scaling to increase this.
+ </para>
+
+ <para>
+ FoundationDB stores all data for all server processes under
+ <filename>/var/lib/foundationdb</filename>. You can override this using
+ <option>services.foundationdb.dataDir</option>, e.g.
+<programlisting>
+services.foundationdb.dataDir = "/data/fdb";
+</programlisting>
+ </para>
+
+ <para>
+ Similarly, logs are stored under <filename>/var/log/foundationdb</filename>
+ by default, and there is a corresponding
+ <option>services.foundationdb.logDir</option> as well.
+ </para>
+ </section>
+ <section xml:id="module-services-foundationdb-scaling">
+ <title>Scaling processes and backup agents</title>
+
+ <para>
+ Scaling the number of server processes is quite easy; simply specify
+ <option>services.foundationdb.serverProcesses</option> to be the number of
+ FoundationDB worker processes that should be started on the machine.
+ </para>
+
+ <para>
+ FoundationDB worker processes typically require 4GB of RAM per-process at
+ minimum for good performance, so this option is set to 1 by default since
+ the maximum amount of RAM is unknown. You're advised to abide by this
+ restriction, so pick a number of processes so that each has 4GB or more.
+ </para>
+
+ <para>
+ A similar option exists in order to scale backup agent processes,
+ <option>services.foundationdb.backupProcesses</option>. Backup agents are
+ not as performance/RAM sensitive, so feel free to experiment with the number
+ of available backup processes.
+ </para>
+ </section>
+ <section xml:id="module-services-foundationdb-clustering">
+ <title>Clustering</title>
+
+ <para>
+ FoundationDB on NixOS works similarly to other Linux systems, so this
+ section will be brief. Please refer to the full FoundationDB documentation
+ for more on clustering.
+ </para>
+
+ <para>
+ FoundationDB organizes clusters using a set of
+ <emphasis>coordinators</emphasis>, which are just specially-designated
+ worker processes. By default, every installation of FoundationDB on NixOS
+ will start as its own individual cluster, with a single coordinator: the
+ first worker process on <command>localhost</command>.
+ </para>
+
+ <para>
+ Coordinators are specified globally using the
+ <command>/etc/foundationdb/fdb.cluster</command> file, which all servers and
+ client applications will use to find and join coordinators. Note that this
+ file <emphasis>can not</emphasis> be managed by NixOS so easily:
+ FoundationDB is designed so that it will rewrite the file at runtime for all
+ clients and nodes when cluster coordinators change, with clients
+ transparently handling this without intervention. It is fundamentally a
+ mutable file, and you should not try to manage it in any way in NixOS.
+ </para>
+
+ <para>
+ When dealing with a cluster, there are two main things you want to do:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Add a node to the cluster for storage/compute.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Promote an ordinary worker to a coordinator.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ A node must already be a member of the cluster in order to properly be
+ promoted to a coordinator, so you must always add it first if you wish to
+ promote it.
+ </para>
+
+ <para>
+ To add a machine to a FoundationDB cluster:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Choose one of the servers to start as the initial coordinator.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Copy the <command>/etc/foundationdb/fdb.cluster</command> file from this
+ server to all the other servers. Restart FoundationDB on all of these
+ other servers, so they join the cluster.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ All of these servers are now connected and working together in the
+ cluster, under the chosen coordinator.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ At this point, you can add as many nodes as you want by just repeating the
+ above steps. By default there will still be a single coordinator: you can
+ use <command>fdbcli</command> to change this and add new coordinators.
+ </para>
+
+ <para>
+ As a convenience, FoundationDB can automatically assign coordinators based
+ on the redundancy mode you wish to achieve for the cluster. Once all the
+ nodes have been joined, simply set the replication policy, and then issue
+ the <command>coordinators auto</command> command
+ </para>
+
+ <para>
+ For example, assuming we have 3 nodes available, we can enable double
+ redundancy mode, then auto-select coordinators. For double redundancy, 3
+ coordinators is ideal: therefore FoundationDB will make
+ <emphasis>every</emphasis> node a coordinator automatically:
+ </para>
+
+<screen>
+<prompt>fdbcli> </prompt>configure double ssd
+<prompt>fdbcli> </prompt>coordinators auto
+</screen>
+
+ <para>
+ This will transparently update all the servers within seconds, and
+ appropriately rewrite the <command>fdb.cluster</command> file, as well as
+ informing all client processes to do the same.
+ </para>
+ </section>
+ <section xml:id="module-services-foundationdb-connectivity">
+ <title>Client connectivity</title>
+
+ <para>
+ By default, all clients must use the current <command>fdb.cluster</command>
+ file to access a given FoundationDB cluster. This file is located by default
+ in <command>/etc/foundationdb/fdb.cluster</command> on all machines with the
+ FoundationDB service enabled, so you may copy the active one from your
+ cluster to a new node in order to connect, if it is not part of the cluster.
+ </para>
+ </section>
+ <section xml:id="module-services-foundationdb-authorization">
+ <title>Client authorization and TLS</title>
+
+ <para>
+ By default, any user who can connect to a FoundationDB process with the
+ correct cluster configuration can access anything. FoundationDB uses a
+ pluggable design to transport security, and out of the box it supports a
+ LibreSSL-based plugin for TLS support. This plugin not only does in-flight
+ encryption, but also performs client authorization based on the given
+ endpoint's certificate chain. For example, a FoundationDB server may be
+ configured to only accept client connections over TLS, where the client TLS
+ certificate is from organization <emphasis>Acme Co</emphasis> in the
+ <emphasis>Research and Development</emphasis> unit.
+ </para>
+
+ <para>
+ Configuring TLS with FoundationDB is done using the
+ <option>services.foundationdb.tls</option> options in order to control the
+ peer verification string, as well as the certificate and its private key.
+ </para>
+
+ <para>
+ Note that the certificate and its private key must be accessible to the
+ FoundationDB user account that the server runs under. These files are also
+ NOT managed by NixOS, as putting them into the store may reveal private
+ information.
+ </para>
+
+ <para>
+ After you have a key and certificate file in place, it is not enough to
+ simply set the NixOS module options -- you must also configure the
+ <command>fdb.cluster</command> file to specify that a given set of
+ coordinators use TLS. This is as simple as adding the suffix
+ <command>:tls</command> to your cluster coordinator configuration, after the
+ port number. For example, assuming you have a coordinator on localhost with
+ the default configuration, simply specifying:
+ </para>
+
+<programlisting>
+XXXXXX:XXXXXX@127.0.0.1:4500:tls
+</programlisting>
+
+ <para>
+ will configure all clients and server processes to use TLS from now on.
+ </para>
+ </section>
+ <section xml:id="module-services-foundationdb-disaster-recovery">
+ <title>Backups and Disaster Recovery</title>
+
+ <para>
+ The usual rules for doing FoundationDB backups apply on NixOS as written in
+ the FoundationDB manual. However, one important difference is the security
+ profile for NixOS: by default, the <command>foundationdb</command> systemd
+ unit uses <emphasis>Linux namespaces</emphasis> to restrict write access to
+ the system, except for the log directory, data directory, and the
+ <command>/etc/foundationdb/</command> directory. This is enforced by default
+ and cannot be disabled.
+ </para>
+
+ <para>
+ However, a side effect of this is that the <command>fdbbackup</command>
+ command doesn't work properly for local filesystem backups: FoundationDB
+ uses a server process alongside the database processes to perform backups
+ and copy the backups to the filesystem. As a result, this process is put
+ under the restricted namespaces above: the backup process can only write to
+ a limited number of paths.
+ </para>
+
+ <para>
+ In order to allow flexible backup locations on local disks, the FoundationDB
+ NixOS module supports a
+ <option>services.foundationdb.extraReadWritePaths</option> option. This
+ option takes a list of paths, and adds them to the systemd unit, allowing
+ the processes inside the service to write (and read) the specified
+ directories.
+ </para>
+
+ <para>
+ For example, to create backups in <command>/opt/fdb-backups</command>, first
+ set up the paths in the module options:
+ </para>
+
+<programlisting>
+services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ];
+</programlisting>
+
+ <para>
+ Restart the FoundationDB service, and it will now be able to write to this
+ directory (even if it does not yet exist.) Note: this path
+ <emphasis>must</emphasis> exist before restarting the unit. Otherwise,
+ systemd will not include it in the private FoundationDB namespace (and it
+ will not add it dynamically at runtime).
+ </para>
+
+ <para>
+ You can now perform a backup:
+ </para>
+
+<screen>
+<prompt>$ </prompt>sudo -u foundationdb fdbbackup start -t default -d file:///opt/fdb-backups
+<prompt>$ </prompt>sudo -u foundationdb fdbbackup status -t default
+</screen>
+ </section>
+ <section xml:id="module-services-foundationdb-limitations">
+ <title>Known limitations</title>
+
+ <para>
+ The FoundationDB setup for NixOS should currently be considered beta.
+ FoundationDB is not new software, but the NixOS compilation and integration
+ has only undergone fairly basic testing of all the available functionality.
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ There is no way to specify individual parameters for individual
+ <command>fdbserver</command> processes. Currently, all server processes
+ inherit all the global <command>fdbmonitor</command> settings.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Ruby bindings are not currently installed.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Go bindings are not currently installed.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </section>
+ <section xml:id="module-services-foundationdb-options">
+ <title>Options</title>
+
+ <para>
+ NixOS's FoundationDB module allows you to configure all of the most relevant
+ configuration options for <command>fdbmonitor</command>, matching it quite
+ closely. A complete list of options for the FoundationDB module may be found
+ <link linkend="opt-services.foundationdb.enable">here</link>. You should
+ also read the FoundationDB documentation as well.
+ </para>
+ </section>
+ <section xml:id="module-services-foundationdb-full-docs">
+ <title>Full documentation</title>
+
+ <para>
+ FoundationDB is a complex piece of software, and requires careful
+ administration to properly use. Full documentation for administration can be
+ found here: <link xlink:href="https://apple.github.io/foundationdb/"/>.
+ </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/databases/hbase.nix b/nixpkgs/nixos/modules/services/databases/hbase.nix
new file mode 100644
index 00000000000..2d1a47bbaa3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/hbase.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.hbase;
+
+ configFile = pkgs.writeText "hbase-site.xml" ''
+ <configuration>
+ <property>
+ <name>hbase.rootdir</name>
+ <value>file://${cfg.dataDir}/hbase</value>
+ </property>
+ <property>
+ <name>hbase.zookeeper.property.dataDir</name>
+ <value>${cfg.dataDir}/zookeeper</value>
+ </property>
+ </configuration>
+ '';
+
+ configDir = pkgs.runCommand "hbase-config-dir" { preferLocalBuild = true; } ''
+ mkdir -p $out
+ cp ${cfg.package}/conf/* $out/
+ rm $out/hbase-site.xml
+ ln -s ${configFile} $out/hbase-site.xml
+ '' ;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.hbase = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run HBase.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.hbase;
+ defaultText = "pkgs.hbase";
+ example = literalExample "pkgs.hbase";
+ description = ''
+ HBase package to use.
+ '';
+ };
+
+
+ user = mkOption {
+ type = types.str;
+ default = "hbase";
+ description = ''
+ User account under which HBase runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "hbase";
+ description = ''
+ Group account under which HBase runs.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/hbase";
+ description = ''
+ Specifies location of HBase database files. This location should be
+ writable and readable for the user the HBase service runs as
+ (hbase by default).
+ '';
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ default = "/var/log/hbase";
+ description = ''
+ Specifies the location of HBase log files.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.hbase.enable {
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.logDir}' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.hbase = {
+ description = "HBase Server";
+ wantedBy = [ "multi-user.target" ];
+
+ environment = {
+ JAVA_HOME = "${pkgs.jre}";
+ HBASE_LOG_DIR = cfg.logDir;
+ };
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${cfg.package}/bin/hbase --config ${configDir} master start";
+ };
+ };
+
+ users.users.hbase = {
+ description = "HBase Server user";
+ group = "hbase";
+ uid = config.ids.uids.hbase;
+ };
+
+ users.groups.hbase.gid = config.ids.gids.hbase;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/influxdb.nix b/nixpkgs/nixos/modules/services/databases/influxdb.nix
new file mode 100644
index 00000000000..2f176a03872
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/influxdb.nix
@@ -0,0 +1,197 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.influxdb;
+
+ configOptions = recursiveUpdate {
+ meta = {
+ bind-address = ":8088";
+ commit-timeout = "50ms";
+ dir = "${cfg.dataDir}/meta";
+ election-timeout = "1s";
+ heartbeat-timeout = "1s";
+ hostname = "localhost";
+ leader-lease-timeout = "500ms";
+ retention-autocreate = true;
+ };
+
+ data = {
+ dir = "${cfg.dataDir}/data";
+ wal-dir = "${cfg.dataDir}/wal";
+ max-wal-size = 104857600;
+ wal-enable-logging = true;
+ wal-flush-interval = "10m";
+ wal-partition-flush-delay = "2s";
+ };
+
+ cluster = {
+ shard-writer-timeout = "5s";
+ write-timeout = "5s";
+ };
+
+ retention = {
+ enabled = true;
+ check-interval = "30m";
+ };
+
+ http = {
+ enabled = true;
+ auth-enabled = false;
+ bind-address = ":8086";
+ https-enabled = false;
+ log-enabled = true;
+ pprof-enabled = false;
+ write-tracing = false;
+ };
+
+ monitor = {
+ store-enabled = false;
+ store-database = "_internal";
+ store-interval = "10s";
+ };
+
+ admin = {
+ enabled = true;
+ bind-address = ":8083";
+ https-enabled = false;
+ };
+
+ graphite = [{
+ enabled = false;
+ }];
+
+ udp = [{
+ enabled = false;
+ }];
+
+ collectd = [{
+ enabled = false;
+ typesdb = "${pkgs.collectd-data}/share/collectd/types.db";
+ database = "collectd_db";
+ bind-address = ":25826";
+ }];
+
+ opentsdb = [{
+ enabled = false;
+ }];
+
+ continuous_queries = {
+ enabled = true;
+ log-enabled = true;
+ recompute-previous-n = 2;
+ recompute-no-older-than = "10m";
+ compute-runs-per-interval = 10;
+ compute-no-more-than = "2m";
+ };
+
+ hinted-handoff = {
+ enabled = true;
+ dir = "${cfg.dataDir}/hh";
+ max-size = 1073741824;
+ max-age = "168h";
+ retry-rate-limit = 0;
+ retry-interval = "1s";
+ };
+ } cfg.extraConfig;
+
+ configFile = pkgs.runCommand "config.toml" {
+ buildInputs = [ pkgs.remarshal ];
+ preferLocalBuild = true;
+ } ''
+ remarshal -if json -of toml \
+ < ${pkgs.writeText "config.json" (builtins.toJSON configOptions)} \
+ > $out
+ '';
+in
+{
+
+ ###### interface
+
+ options = {
+
+ services.influxdb = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable the influxdb server";
+ type = types.bool;
+ };
+
+ package = mkOption {
+ default = pkgs.influxdb;
+ defaultText = "pkgs.influxdb";
+ description = "Which influxdb derivation to use";
+ type = types.package;
+ };
+
+ user = mkOption {
+ default = "influxdb";
+ description = "User account under which influxdb runs";
+ type = types.str;
+ };
+
+ group = mkOption {
+ default = "influxdb";
+ description = "Group under which influxdb runs";
+ type = types.str;
+ };
+
+ dataDir = mkOption {
+ default = "/var/db/influxdb";
+ description = "Data directory for influxd data files.";
+ type = types.path;
+ };
+
+ extraConfig = mkOption {
+ default = {};
+ description = "Extra configuration options for influxdb";
+ type = types.attrs;
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.influxdb.enable {
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.influxdb = {
+ description = "InfluxDB Server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = ''${cfg.package}/bin/influxd -config "${configFile}"'';
+ User = cfg.user;
+ Group = cfg.group;
+ };
+ postStart =
+ let
+ scheme = if configOptions.http.https-enabled then "-k https" else "http";
+ bindAddr = (ba: if hasPrefix ":" ba then "127.0.0.1${ba}" else "${ba}")(toString configOptions.http.bind-address);
+ in
+ mkBefore ''
+ until ${pkgs.curl.bin}/bin/curl -s -o /dev/null ${scheme}://${bindAddr}/ping; do
+ sleep 1;
+ done
+ '';
+ };
+
+ users.users = optional (cfg.user == "influxdb") {
+ name = "influxdb";
+ uid = config.ids.uids.influxdb;
+ description = "Influxdb daemon user";
+ };
+
+ users.groups = optional (cfg.group == "influxdb") {
+ name = "influxdb";
+ gid = config.ids.gids.influxdb;
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/memcached.nix b/nixpkgs/nixos/modules/services/databases/memcached.nix
new file mode 100644
index 00000000000..84d2c8674f4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/memcached.nix
@@ -0,0 +1,116 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.memcached;
+
+ memcached = pkgs.memcached;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.memcached = {
+
+ enable = mkOption {
+ default = false;
+ description = "
+ Whether to enable Memcached.
+ ";
+ };
+
+ user = mkOption {
+ default = "memcached";
+ description = "The user to run Memcached as";
+ };
+
+ listen = mkOption {
+ default = "127.0.0.1";
+ description = "The IP address to bind to";
+ };
+
+ port = mkOption {
+ default = 11211;
+ description = "The port to bind to";
+ };
+
+ enableUnixSocket = mkEnableOption "unix socket at /run/memcached/memcached.sock";
+
+ maxMemory = mkOption {
+ default = 64;
+ description = "The maximum amount of memory to use for storage, in megabytes.";
+ };
+
+ maxConnections = mkOption {
+ default = 1024;
+ description = "The maximum number of simultaneous connections";
+ };
+
+ extraOptions = mkOption {
+ default = [];
+ description = "A list of extra options that will be added as a suffix when running memcached";
+ };
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.memcached.enable {
+
+ users.users = optional (cfg.user == "memcached") {
+ name = "memcached";
+ description = "Memcached server user";
+ };
+
+ environment.systemPackages = [ memcached ];
+
+ systemd.services.memcached = {
+ description = "Memcached server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ ExecStart =
+ let
+ networking = if cfg.enableUnixSocket
+ then "-s /run/memcached/memcached.sock"
+ else "-l ${cfg.listen} -p ${toString cfg.port}";
+ in "${memcached}/bin/memcached ${networking} -m ${toString cfg.maxMemory} -c ${toString cfg.maxConnections} ${concatStringsSep " " cfg.extraOptions}";
+
+ User = cfg.user;
+
+ # Filesystem access
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+ RuntimeDirectory = "memcached";
+ # Caps
+ CapabilityBoundingSet = "";
+ NoNewPrivileges = true;
+ # Misc.
+ LockPersonality = true;
+ RestrictRealtime = true;
+ PrivateMounts = true;
+ MemoryDenyWriteExecute = true;
+ };
+ };
+ };
+ imports = [
+ (mkRemovedOptionModule ["services" "memcached" "socket"] ''
+ This option was replaced by a fixed unix socket path at /run/memcached/memcached.sock enabled using services.memcached.enableUnixSocket.
+ '')
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/monetdb.nix b/nixpkgs/nixos/modules/services/databases/monetdb.nix
new file mode 100644
index 00000000000..5c66fc7b2e3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/monetdb.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.monetdb;
+
+in {
+ meta.maintainers = with maintainers; [ StillerHarpo primeos ];
+
+ ###### interface
+ options = {
+ services.monetdb = {
+
+ enable = mkEnableOption "the MonetDB database server";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.monetdb;
+ defaultText = "pkgs.monetdb";
+ description = "MonetDB package to use.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "monetdb";
+ description = "User account under which MonetDB runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "monetdb";
+ description = "Group under which MonetDB runs.";
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/monetdb";
+ description = "Data directory for the dbfarm.";
+ };
+
+ port = mkOption {
+ type = types.ints.u16;
+ default = 50000;
+ description = "Port to listen on.";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ example = "0.0.0.0";
+ description = "Address to listen on.";
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+
+ users.users.monetdb = mkIf (cfg.user == "monetdb") {
+ uid = config.ids.uids.monetdb;
+ group = cfg.group;
+ description = "MonetDB user";
+ home = cfg.dataDir;
+ createHome = true;
+ };
+
+ users.groups.monetdb = mkIf (cfg.group == "monetdb") {
+ gid = config.ids.gids.monetdb;
+ members = [ cfg.user ];
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services.monetdb = {
+ description = "MonetDB database server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = [ cfg.package ];
+ unitConfig.RequiresMountsFor = "${cfg.dataDir}";
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${cfg.package}/bin/monetdbd start -n ${cfg.dataDir}";
+ ExecStop = "${cfg.package}/bin/monetdbd stop ${cfg.dataDir}";
+ };
+ preStart = ''
+ if [ ! -e ${cfg.dataDir}/.merovingian_properties ]; then
+ # Create the dbfarm (as cfg.user)
+ ${cfg.package}/bin/monetdbd create ${cfg.dataDir}
+ fi
+
+ # Update the properties
+ ${cfg.package}/bin/monetdbd set port=${toString cfg.port} ${cfg.dataDir}
+ ${cfg.package}/bin/monetdbd set listenaddr=${cfg.listenAddress} ${cfg.dataDir}
+ '';
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/mongodb.nix b/nixpkgs/nixos/modules/services/databases/mongodb.nix
new file mode 100644
index 00000000000..12879afed47
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/mongodb.nix
@@ -0,0 +1,193 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.mongodb;
+
+ mongodb = cfg.package;
+
+ mongoCnf = cfg: pkgs.writeText "mongodb.conf"
+ ''
+ net.bindIp: ${cfg.bind_ip}
+ ${optionalString cfg.quiet "systemLog.quiet: true"}
+ systemLog.destination: syslog
+ storage.dbPath: ${cfg.dbpath}
+ ${optionalString cfg.enableAuth "security.authorization: enabled"}
+ ${optionalString (cfg.replSetName != "") "replication.replSetName: ${cfg.replSetName}"}
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.mongodb = {
+
+ enable = mkOption {
+ default = false;
+ description = "
+ Whether to enable the MongoDB server.
+ ";
+ };
+
+ package = mkOption {
+ default = pkgs.mongodb;
+ defaultText = "pkgs.mongodb";
+ type = types.package;
+ description = "
+ Which MongoDB derivation to use.
+ ";
+ };
+
+ user = mkOption {
+ default = "mongodb";
+ description = "User account under which MongoDB runs";
+ };
+
+ bind_ip = mkOption {
+ default = "127.0.0.1";
+ description = "IP to bind to";
+ };
+
+ quiet = mkOption {
+ default = false;
+ description = "quieter output";
+ };
+
+ enableAuth = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable client authentication. Creates a default superuser with username root!";
+ };
+
+ initialRootPassword = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Password for the root user if auth is enabled.";
+ };
+
+ dbpath = mkOption {
+ default = "/var/db/mongodb";
+ description = "Location where MongoDB stores its files";
+ };
+
+ pidFile = mkOption {
+ default = "/run/mongodb.pid";
+ description = "Location of MongoDB pid file";
+ };
+
+ replSetName = mkOption {
+ default = "";
+ description = ''
+ If this instance is part of a replica set, set its name here.
+ Otherwise, leave empty to run as single node.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ example = ''
+ storage.journal.enabled: false
+ '';
+ description = "MongoDB extra configuration in YAML format";
+ };
+
+ initialScript = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ A file containing MongoDB statements to execute on first startup.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.mongodb.enable {
+ assertions = [
+ { assertion = !cfg.enableAuth || cfg.initialRootPassword != null;
+ message = "`enableAuth` requires `initialRootPassword` to be set.";
+ }
+ ];
+
+ users.users.mongodb = mkIf (cfg.user == "mongodb")
+ { name = "mongodb";
+ uid = config.ids.uids.mongodb;
+ description = "MongoDB server user";
+ };
+
+ environment.systemPackages = [ mongodb ];
+
+ systemd.services.mongodb =
+ { description = "MongoDB server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ ExecStart = "${mongodb}/bin/mongod --config ${mongoCnf cfg} --fork --pidfilepath ${cfg.pidFile}";
+ User = cfg.user;
+ PIDFile = cfg.pidFile;
+ Type = "forking";
+ TimeoutStartSec=120; # intial creating of journal can take some time
+ PermissionsStartOnly = true;
+ };
+
+ preStart = let
+ cfg_ = cfg // { enableAuth = false; bind_ip = "127.0.0.1"; };
+ in ''
+ rm ${cfg.dbpath}/mongod.lock || true
+ if ! test -e ${cfg.dbpath}; then
+ install -d -m0700 -o ${cfg.user} ${cfg.dbpath}
+ # See postStart!
+ touch ${cfg.dbpath}/.first_startup
+ fi
+ if ! test -e ${cfg.pidFile}; then
+ install -D -o ${cfg.user} /dev/null ${cfg.pidFile}
+ fi '' + lib.optionalString cfg.enableAuth ''
+
+ if ! test -e "${cfg.dbpath}/.auth_setup_complete"; then
+ systemd-run --unit=mongodb-for-setup --uid=${cfg.user} ${mongodb}/bin/mongod --config ${mongoCnf cfg_}
+ # wait for mongodb
+ while ! ${mongodb}/bin/mongo --eval "db.version()" > /dev/null 2>&1; do sleep 0.1; done
+
+ ${mongodb}/bin/mongo <<EOF
+ use admin
+ db.createUser(
+ {
+ user: "root",
+ pwd: "${cfg.initialRootPassword}",
+ roles: [
+ { role: "userAdminAnyDatabase", db: "admin" },
+ { role: "dbAdminAnyDatabase", db: "admin" },
+ { role: "readWriteAnyDatabase", db: "admin" }
+ ]
+ }
+ )
+ EOF
+ touch "${cfg.dbpath}/.auth_setup_complete"
+ systemctl stop mongodb-for-setup
+ fi
+ '';
+ postStart = ''
+ if test -e "${cfg.dbpath}/.first_startup"; then
+ ${optionalString (cfg.initialScript != null) ''
+ ${mongodb}/bin/mongo -u root -p ${cfg.initialRootPassword} admin "${cfg.initialScript}"
+ ''}
+ rm -f "${cfg.dbpath}/.first_startup"
+ fi
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/mysql.nix b/nixpkgs/nixos/modules/services/databases/mysql.nix
new file mode 100644
index 00000000000..df74cfc9a26
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/mysql.nix
@@ -0,0 +1,425 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.mysql;
+
+ mysql = cfg.package;
+
+ isMariaDB =
+ let
+ pName = _p: (builtins.parseDrvName (_p.name)).name;
+ in pName mysql == pName pkgs.mariadb;
+ isMysqlAtLeast57 =
+ let
+ pName = _p: (builtins.parseDrvName (_p.name)).name;
+ in (pName mysql == pName pkgs.mysql57)
+ && ((builtins.compareVersions mysql.version "5.7") >= 0);
+
+ mysqldOptions =
+ "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
+ # For MySQL 5.7+, --insecure creates the root user without password
+ # (earlier versions and MariaDB do this by default).
+ installOptions =
+ "${mysqldOptions} ${lib.optionalString isMysqlAtLeast57 "--insecure"}";
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.mysql = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "
+ Whether to enable the MySQL server.
+ ";
+ };
+
+ package = mkOption {
+ type = types.package;
+ example = literalExample "pkgs.mysql";
+ description = "
+ Which MySQL derivation to use. MariaDB packages are supported too.
+ ";
+ };
+
+ bind = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = literalExample "0.0.0.0";
+ description = "Address to bind to. The default is to bind to all addresses";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 3306;
+ description = "Port of MySQL";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "mysql";
+ description = "User account under which MySQL runs";
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ example = "/var/lib/mysql";
+ description = "Location where MySQL stores its table files";
+ };
+
+ extraOptions = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ key_buffer_size = 6G
+ table_cache = 1600
+ log-error = /var/log/mysql_err.log
+ '';
+ description = ''
+ Provide extra options to the MySQL configuration file.
+
+ Please note, that these options are added to the
+ <literal>[mysqld]</literal> section so you don't need to explicitly
+ state it again.
+ '';
+ };
+
+ initialDatabases = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ name = mkOption {
+ type = types.str;
+ description = ''
+ The name of the database to create.
+ '';
+ };
+ schema = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The initial schema of the database; if null (the default),
+ an empty database is created.
+ '';
+ };
+ };
+ });
+ default = [];
+ description = ''
+ List of database names and their initial schemas that should be used to create databases on the first startup
+ of MySQL. The schema attribute is optional: If not specified, an empty database is created.
+ '';
+ example = [
+ { name = "foodatabase"; schema = literalExample "./foodatabase.sql"; }
+ { name = "bardatabase"; }
+ ];
+ };
+
+ initialScript = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database";
+ };
+
+ ensureDatabases = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Ensures that the specified databases exist.
+ This option will never delete existing databases, especially not when the value of this
+ option is changed. This means that databases created once through this option or
+ otherwise have to be removed manually.
+ '';
+ example = [
+ "nextcloud"
+ "matomo"
+ ];
+ };
+
+ ensureUsers = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ name = mkOption {
+ type = types.str;
+ description = ''
+ Name of the user to ensure.
+ '';
+ };
+ ensurePermissions = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ description = ''
+ Permissions to ensure for the user, specified as attribute set.
+ The attribute names specify the database and tables to grant the permissions for,
+ separated by a dot. You may use wildcards here.
+ The attribute values specfiy the permissions to grant.
+ You may specify one or multiple comma-separated SQL privileges here.
+
+ For more information on how to specify the target
+ and on which privileges exist, see the
+ <link xlink:href="https://mariadb.com/kb/en/library/grant/">GRANT syntax</link>.
+ The attributes are used as <code>GRANT ''${attrName} ON ''${attrValue}</code>.
+ '';
+ example = literalExample ''
+ {
+ "database.*" = "ALL PRIVILEGES";
+ "*.*" = "SELECT, LOCK TABLES";
+ }
+ '';
+ };
+ };
+ });
+ default = [];
+ description = ''
+ Ensures that the specified users exist and have at least the ensured permissions.
+ The MySQL users will be identified using Unix socket authentication. This authenticates the Unix user with the
+ same name only, and that without the need for a password.
+ This option will never delete existing users or remove permissions, especially not when the value of this
+ option is changed. This means that users created and permissions assigned once through this option or
+ otherwise have to be removed manually.
+ '';
+ example = literalExample ''
+ [
+ {
+ name = "nextcloud";
+ ensurePermissions = {
+ "nextcloud.*" = "ALL PRIVILEGES";
+ };
+ }
+ {
+ name = "backup";
+ ensurePermissions = {
+ "*.*" = "SELECT, LOCK TABLES";
+ };
+ }
+ ]
+ '';
+ };
+
+ replication = {
+ role = mkOption {
+ type = types.enum [ "master" "slave" "none" ];
+ default = "none";
+ description = "Role of the MySQL server instance.";
+ };
+
+ serverId = mkOption {
+ type = types.int;
+ default = 1;
+ description = "Id of the MySQL server instance. This number must be unique for each instance";
+ };
+
+ masterHost = mkOption {
+ type = types.str;
+ description = "Hostname of the MySQL master server";
+ };
+
+ slaveHost = mkOption {
+ type = types.str;
+ description = "Hostname of the MySQL slave server";
+ };
+
+ masterUser = mkOption {
+ type = types.str;
+ description = "Username of the MySQL replication user";
+ };
+
+ masterPassword = mkOption {
+ type = types.str;
+ description = "Password of the MySQL replication user";
+ };
+
+ masterPort = mkOption {
+ type = types.int;
+ default = 3306;
+ description = "Port number on which the MySQL master server runs";
+ };
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.mysql.enable {
+
+ services.mysql.dataDir =
+ mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/mysql"
+ else "/var/mysql");
+
+ users.users.mysql = {
+ description = "MySQL server user";
+ group = "mysql";
+ uid = config.ids.uids.mysql;
+ };
+
+ users.groups.mysql.gid = config.ids.gids.mysql;
+
+ environment.systemPackages = [mysql];
+
+ environment.etc."my.cnf".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.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0700 ${cfg.user} mysql -"
+ ];
+
+ systemd.services.mysql = let
+ hasNotify = (cfg.package == pkgs.mariadb);
+ in {
+ description = "MySQL Server";
+
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ config.environment.etc."my.cnf".source ];
+
+ unitConfig.RequiresMountsFor = "${cfg.dataDir}";
+
+ path = [
+ # Needed for the mysql_install_db command in the preStart script
+ # which calls the hostname command.
+ pkgs.nettools
+ ];
+
+ preStart = ''
+ if ! test -e ${cfg.dataDir}/mysql; then
+ ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${installOptions}
+ touch /tmp/mysql_init
+ fi
+ '';
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = "mysql";
+ Type = if hasNotify then "notify" else "simple";
+ RuntimeDirectory = "mysqld";
+ RuntimeDirectoryMode = "0755";
+ # The last two environment variables are used for starting Galera clusters
+ ExecStart = "${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION";
+ ExecStartPost =
+ let
+ setupScript = pkgs.writeScript "mysql-setup" ''
+ #!${pkgs.runtimeShell} -e
+
+ ${optionalString (!hasNotify) ''
+ # Wait until the MySQL server is available for use
+ count=0
+ while [ ! -e /run/mysqld/mysqld.sock ]
+ do
+ if [ $count -eq 30 ]
+ then
+ echo "Tried 30 times, giving up..."
+ exit 1
+ fi
+
+ echo "MySQL daemon not yet started. Waiting for 1 second..."
+ count=$((count++))
+ sleep 1
+ done
+ ''}
+
+ if [ -f /tmp/mysql_init ]
+ then
+ ${concatMapStrings (database: ''
+ # Create initial databases
+ if ! test -e "${cfg.dataDir}/${database.name}"; then
+ echo "Creating initial database: ${database.name}"
+ ( echo 'create database `${database.name}`;'
+
+ ${optionalString (database.schema != null) ''
+ echo 'use `${database.name}`;'
+
+ # TODO: this silently falls through if database.schema does not exist,
+ # we should catch this somehow and exit, but can't do it here because we're in a subshell.
+ if [ -f "${database.schema}" ]
+ then
+ cat ${database.schema}
+ elif [ -d "${database.schema}" ]
+ then
+ cat ${database.schema}/mysql-databases/*.sql
+ fi
+ ''}
+ ) | ${mysql}/bin/mysql -u root -N
+ fi
+ '') cfg.initialDatabases}
+
+ ${optionalString (cfg.replication.role == "master")
+ ''
+ # Set up the replication master
+
+ ( echo "use mysql;"
+ echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
+ echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
+ echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
+ ) | ${mysql}/bin/mysql -u root -N
+ ''}
+
+ ${optionalString (cfg.replication.role == "slave")
+ ''
+ # Set up the replication slave
+
+ ( echo "stop slave;"
+ echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
+ echo "start slave;"
+ ) | ${mysql}/bin/mysql -u root -N
+ ''}
+
+ ${optionalString (cfg.initialScript != null)
+ ''
+ # Execute initial script
+ # using toString to avoid copying the file to nix store if given as path instead of string,
+ # as it might contain credentials
+ cat ${toString cfg.initialScript} | ${mysql}/bin/mysql -u root -N
+ ''}
+
+ rm /tmp/mysql_init
+ fi
+
+ ${optionalString (cfg.ensureDatabases != []) ''
+ (
+ ${concatMapStrings (database: ''
+ echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
+ '') cfg.ensureDatabases}
+ ) | ${mysql}/bin/mysql -u root -N
+ ''}
+
+ ${concatMapStrings (user:
+ ''
+ ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
+ ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
+ echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
+ '') user.ensurePermissions)}
+ ) | ${mysql}/bin/mysql -u root -N
+ '') cfg.ensureUsers}
+ '';
+ in
+ # ensureDatbases & ensureUsers depends on this script being run as root
+ # when the user has secured their mysql install
+ "+${setupScript}";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/neo4j.nix b/nixpkgs/nixos/modules/services/databases/neo4j.nix
new file mode 100644
index 00000000000..5533182c311
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/neo4j.nix
@@ -0,0 +1,652 @@
+{ config, options, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.neo4j;
+ certDirOpt = options.services.neo4j.directories.certificates;
+ isDefaultPathOption = opt: isOption opt && opt.type == types.path && opt.highestPrio >= 1500;
+
+ sslPolicies = mapAttrsToList (
+ name: conf: ''
+ dbms.ssl.policy.${name}.allow_key_generation=${boolToString conf.allowKeyGeneration}
+ dbms.ssl.policy.${name}.base_directory=${conf.baseDirectory}
+ ${optionalString (conf.ciphers != null) ''
+ dbms.ssl.policy.${name}.ciphers=${concatStringsSep "," conf.ciphers}
+ ''}
+ dbms.ssl.policy.${name}.client_auth=${conf.clientAuth}
+ ${if length (splitString "/" conf.privateKey) > 1 then
+ ''dbms.ssl.policy.${name}.private_key=${conf.privateKey}''
+ else
+ ''dbms.ssl.policy.${name}.private_key=${conf.baseDirectory}/${conf.privateKey}''
+ }
+ ${if length (splitString "/" conf.privateKey) > 1 then
+ ''dbms.ssl.policy.${name}.public_certificate=${conf.publicCertificate}''
+ else
+ ''dbms.ssl.policy.${name}.public_certificate=${conf.baseDirectory}/${conf.publicCertificate}''
+ }
+ dbms.ssl.policy.${name}.revoked_dir=${conf.revokedDir}
+ dbms.ssl.policy.${name}.tls_versions=${concatStringsSep "," conf.tlsVersions}
+ dbms.ssl.policy.${name}.trust_all=${boolToString conf.trustAll}
+ dbms.ssl.policy.${name}.trusted_dir=${conf.trustedDir}
+ ''
+ ) cfg.ssl.policies;
+
+ serverConfig = pkgs.writeText "neo4j.conf" ''
+ # General
+ dbms.allow_upgrade=${boolToString cfg.allowUpgrade}
+ dbms.connectors.default_listen_address=${cfg.defaultListenAddress}
+ dbms.read_only=${boolToString cfg.readOnly}
+ ${optionalString (cfg.workerCount > 0) ''
+ dbms.threads.worker_count=${toString cfg.workerCount}
+ ''}
+
+ # Directories
+ dbms.directories.certificates=${cfg.directories.certificates}
+ dbms.directories.data=${cfg.directories.data}
+ dbms.directories.logs=${cfg.directories.home}/logs
+ dbms.directories.plugins=${cfg.directories.plugins}
+ ${optionalString (cfg.constrainLoadCsv) ''
+ dbms.directories.import=${cfg.directories.imports}
+ ''}
+
+ # HTTP Connector
+ ${optionalString (cfg.http.enable) ''
+ dbms.connector.http.enabled=${boolToString cfg.http.enable}
+ dbms.connector.http.listen_address=${cfg.http.listenAddress}
+ ''}
+ ${optionalString (!cfg.http.enable) ''
+ # It is not possible to disable the HTTP connector. To fully prevent
+ # clients from connecting to HTTP, block the HTTP port (7474 by default)
+ # via firewall. listen_address is set to the loopback interface to
+ # prevent remote clients from connecting.
+ dbms.connector.http.listen_address=127.0.0.1
+ ''}
+
+ # HTTPS Connector
+ dbms.connector.https.enabled=${boolToString cfg.https.enable}
+ dbms.connector.https.listen_address=${cfg.https.listenAddress}
+ https.ssl_policy=${cfg.https.sslPolicy}
+
+ # BOLT Connector
+ dbms.connector.bolt.enabled=${boolToString cfg.bolt.enable}
+ dbms.connector.bolt.listen_address=${cfg.bolt.listenAddress}
+ bolt.ssl_policy=${cfg.bolt.sslPolicy}
+ dbms.connector.bolt.tls_level=${cfg.bolt.tlsLevel}
+
+ # neo4j-shell
+ dbms.shell.enabled=${boolToString cfg.shell.enable}
+
+ # SSL Policies
+ ${concatStringsSep "\n" sslPolicies}
+
+ # Default retention policy from neo4j.conf
+ dbms.tx_log.rotation.retention_policy=1 days
+
+ # Default JVM parameters from neo4j.conf
+ dbms.jvm.additional=-XX:+UseG1GC
+ dbms.jvm.additional=-XX:-OmitStackTraceInFastThrow
+ dbms.jvm.additional=-XX:+AlwaysPreTouch
+ dbms.jvm.additional=-XX:+UnlockExperimentalVMOptions
+ dbms.jvm.additional=-XX:+TrustFinalNonStaticFields
+ dbms.jvm.additional=-XX:+DisableExplicitGC
+ dbms.jvm.additional=-Djdk.tls.ephemeralDHKeySize=2048
+ dbms.jvm.additional=-Djdk.tls.rejectClientInitiatedRenegotiation=true
+ dbms.jvm.additional=-Dunsupported.dbms.udc.source=tarball
+
+ # Usage Data Collector
+ dbms.udc.enabled=${boolToString cfg.udc.enable}
+
+ # Extra Configuration
+ ${cfg.extraServerConfig}
+ '';
+
+in {
+
+ ###### interface
+
+ options.services.neo4j = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Neo4j Community Edition.
+ '';
+ };
+
+ allowUpgrade = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow upgrade of Neo4j database files from an older version.
+ '';
+ };
+
+ constrainLoadCsv = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Sets the root directory for file URLs used with the Cypher
+ <literal>LOAD CSV</literal> clause to be that defined by
+ <option>directories.imports</option>. It restricts
+ access to only those files within that directory and its
+ subdirectories.
+ </para>
+ <para>
+ Setting this option to <literal>false</literal> introduces
+ possible security problems.
+ '';
+ };
+
+ defaultListenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ Default network interface to listen for incoming connections. To
+ listen for connections on all interfaces, use "0.0.0.0".
+ </para>
+ <para>
+ Specifies the default IP address and address part of connector
+ specific <option>listenAddress</option> options. To bind specific
+ connectors to a specific network interfaces, specify the entire
+ <option>listenAddress</option> option for that connector.
+ '';
+ };
+
+ extraServerConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration for Neo4j Community server. Refer to the
+ <link xlink:href="https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/">complete reference</link>
+ of Neo4j configuration settings.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.neo4j;
+ defaultText = "pkgs.neo4j";
+ description = ''
+ Neo4j package to use.
+ '';
+ };
+
+ readOnly = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Only allow read operations from this Neo4j instance.
+ '';
+ };
+
+ workerCount = mkOption {
+ type = types.ints.between 0 44738;
+ default = 0;
+ description = ''
+ Number of Neo4j worker threads, where the default of
+ <literal>0</literal> indicates a worker count equal to the number of
+ available processors.
+ '';
+ };
+
+ bolt = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable the BOLT connector for Neo4j. Setting this option to
+ <literal>false</literal> will stop Neo4j from listening for incoming
+ connections on the BOLT port (7687 by default).
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = ":7687";
+ description = ''
+ Neo4j listen address for BOLT traffic. The listen address is
+ expressed in the format <literal>&lt;ip-address&gt;:&lt;port-number&gt;</literal>.
+ '';
+ };
+
+ sslPolicy = mkOption {
+ type = types.str;
+ default = "legacy";
+ description = ''
+ Neo4j SSL policy for BOLT traffic.
+ </para>
+ <para>
+ The legacy policy is a special policy which is not defined in
+ the policy configuration section, but rather derives from
+ <option>directories.certificates</option> and
+ associated files (by default: <filename>neo4j.key</filename> and
+ <filename>neo4j.cert</filename>). Its use will be deprecated.
+ </para>
+ <para>
+ Note: This connector must be configured to support/require
+ SSL/TLS for the legacy policy to actually be utilized. See
+ <option>bolt.tlsLevel</option>.
+ '';
+ };
+
+ tlsLevel = mkOption {
+ type = types.enum [ "REQUIRED" "OPTIONAL" "DISABLED" ];
+ default = "OPTIONAL";
+ description = ''
+ SSL/TSL requirement level for BOLT traffic.
+ '';
+ };
+ };
+
+ directories = {
+ certificates = mkOption {
+ type = types.path;
+ default = "${cfg.directories.home}/certificates";
+ description = ''
+ Directory for storing certificates to be used by Neo4j for
+ TLS connections.
+ </para>
+ <para>
+ When setting this directory to something other than its default,
+ ensure the directory's existence, and that read/write permissions are
+ given to the Neo4j daemon user <literal>neo4j</literal>.
+ </para>
+ <para>
+ Note that changing this directory from its default will prevent
+ the directory structure required for each SSL policy from being
+ automatically generated. A policy's directory structure as defined by
+ its <option>baseDirectory</option>,<option>revokedDir</option> and
+ <option>trustedDir</option> must then be setup manually. The
+ existence of these directories is mandatory, as well as the presence
+ of the certificate file and the private key. Ensure the correct
+ permissions are set on these directories and files.
+ '';
+ };
+
+ data = mkOption {
+ type = types.path;
+ default = "${cfg.directories.home}/data";
+ description = ''
+ Path of the data directory. You must not configure more than one
+ Neo4j installation to use the same data directory.
+ </para>
+ <para>
+ When setting this directory to something other than its default,
+ ensure the directory's existence, and that read/write permissions are
+ given to the Neo4j daemon user <literal>neo4j</literal>.
+ '';
+ };
+
+ home = mkOption {
+ type = types.path;
+ default = "/var/lib/neo4j";
+ description = ''
+ Path of the Neo4j home directory. Other default directories are
+ subdirectories of this path. This directory will be created if
+ non-existent, and its ownership will be <command>chown</command> to
+ the Neo4j daemon user <literal>neo4j</literal>.
+ '';
+ };
+
+ imports = mkOption {
+ type = types.path;
+ default = "${cfg.directories.home}/import";
+ description = ''
+ The root directory for file URLs used with the Cypher
+ <literal>LOAD CSV</literal> clause. Only meaningful when
+ <option>constrainLoadCvs</option> is set to
+ <literal>true</literal>.
+ </para>
+ <para>
+ When setting this directory to something other than its default,
+ ensure the directory's existence, and that read permission is
+ given to the Neo4j daemon user <literal>neo4j</literal>.
+ '';
+ };
+
+ plugins = mkOption {
+ type = types.path;
+ default = "${cfg.directories.home}/plugins";
+ description = ''
+ Path of the database plugin directory. Compiled Java JAR files that
+ contain database procedures will be loaded if they are placed in
+ this directory.
+ </para>
+ <para>
+ When setting this directory to something other than its default,
+ ensure the directory's existence, and that read permission is
+ given to the Neo4j daemon user <literal>neo4j</literal>.
+ '';
+ };
+ };
+
+ http = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ The HTTP connector is required for Neo4j, and cannot be disabled.
+ Setting this option to <literal>false</literal> will force the HTTP
+ connector's <option>listenAddress</option> to the loopback
+ interface to prevent connection of remote clients. To prevent all
+ clients from connecting, block the HTTP port (7474 by default) by
+ firewall.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = ":7474";
+ description = ''
+ Neo4j listen address for HTTP traffic. The listen address is
+ expressed in the format <literal>&lt;ip-address&gt;:&lt;port-number&gt;</literal>.
+ '';
+ };
+ };
+
+ https = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable the HTTPS connector for Neo4j. Setting this option to
+ <literal>false</literal> will stop Neo4j from listening for incoming
+ connections on the HTTPS port (7473 by default).
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = ":7473";
+ description = ''
+ Neo4j listen address for HTTPS traffic. The listen address is
+ expressed in the format <literal>&lt;ip-address&gt;:&lt;port-number&gt;</literal>.
+ '';
+ };
+
+ sslPolicy = mkOption {
+ type = types.str;
+ default = "legacy";
+ description = ''
+ Neo4j SSL policy for HTTPS traffic.
+ </para>
+ <para>
+ The legacy policy is a special policy which is not defined in the
+ policy configuration section, but rather derives from
+ <option>directories.certificates</option> and
+ associated files (by default: <filename>neo4j.key</filename> and
+ <filename>neo4j.cert</filename>). Its use will be deprecated.
+ '';
+ };
+ };
+
+ shell = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable a remote shell server which Neo4j Shell clients can log in to.
+ Only applicable to <command>neo4j-shell</command>.
+ '';
+ };
+ };
+
+ ssl.policies = mkOption {
+ type = with types; attrsOf (submodule ({ name, config, options, ... }: {
+ options = {
+
+ allowKeyGeneration = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allows the generation of a private key and associated self-signed
+ certificate. Only performed when both objects cannot be found for
+ this policy. It is recommended to turn this off again after keys
+ have been generated.
+ </para>
+ <para>
+ The public certificate is required to be duplicated to the
+ directory holding trusted certificates as defined by the
+ <option>trustedDir</option> option.
+ </para>
+ <para>
+ Keys should in general be generated and distributed offline by a
+ trusted certificate authority and not by utilizing this mode.
+ '';
+ };
+
+ baseDirectory = mkOption {
+ type = types.path;
+ default = "${cfg.directories.certificates}/${name}";
+ description = ''
+ The mandatory base directory for cryptographic objects of this
+ policy. This path is only automatically generated when this
+ option as well as <option>directories.certificates</option> are
+ left at their default. Ensure read/write permissions are given
+ to the Neo4j daemon user <literal>neo4j</literal>.
+ </para>
+ <para>
+ It is also possible to override each individual
+ configuration with absolute paths. See the
+ <option>privateKey</option> and <option>publicCertificate</option>
+ policy options.
+ '';
+ };
+
+ ciphers = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = ''
+ Restrict the allowed ciphers of this policy to those defined
+ here. The default ciphers are those of the JVM platform.
+ '';
+ };
+
+ clientAuth = mkOption {
+ type = types.enum [ "NONE" "OPTIONAL" "REQUIRE" ];
+ default = "REQUIRE";
+ description = ''
+ The client authentication stance for this policy.
+ '';
+ };
+
+ privateKey = mkOption {
+ type = types.str;
+ default = "private.key";
+ description = ''
+ The name of private PKCS #8 key file for this policy to be found
+ in the <option>baseDirectory</option>, or the absolute path to
+ the key file. It is mandatory that a key can be found or generated.
+ '';
+ };
+
+ publicCertificate = mkOption {
+ type = types.str;
+ default = "public.crt";
+ description = ''
+ The name of public X.509 certificate (chain) file in PEM format
+ for this policy to be found in the <option>baseDirectory</option>,
+ or the absolute path to the certificate file. It is mandatory
+ that a certificate can be found or generated.
+ </para>
+ <para>
+ The public certificate is required to be duplicated to the
+ directory holding trusted certificates as defined by the
+ <option>trustedDir</option> option.
+ '';
+ };
+
+ revokedDir = mkOption {
+ type = types.path;
+ default = "${config.baseDirectory}/revoked";
+ description = ''
+ Path to directory of CRLs (Certificate Revocation Lists) in
+ PEM format. Must be an absolute path. The existence of this
+ directory is mandatory and will need to be created manually when:
+ setting this option to something other than its default; setting
+ either this policy's <option>baseDirectory</option> or
+ <option>directories.certificates</option> to something other than
+ their default. Ensure read/write permissions are given to the
+ Neo4j daemon user <literal>neo4j</literal>.
+ '';
+ };
+
+ tlsVersions = mkOption {
+ type = types.listOf types.str;
+ default = [ "TLSv1.2" ];
+ description = ''
+ Restrict the TLS protocol versions of this policy to those
+ defined here.
+ '';
+ };
+
+ trustAll = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Makes this policy trust all remote parties. Enabling this is not
+ recommended and the policy's trusted directory will be ignored.
+ Use of this mode is discouraged. It would offer encryption but
+ no security.
+ '';
+ };
+
+ trustedDir = mkOption {
+ type = types.path;
+ default = "${config.baseDirectory}/trusted";
+ description = ''
+ Path to directory of X.509 certificates in PEM format for
+ trusted parties. Must be an absolute path. The existence of this
+ directory is mandatory and will need to be created manually when:
+ setting this option to something other than its default; setting
+ either this policy's <option>baseDirectory</option> or
+ <option>directories.certificates</option> to something other than
+ their default. Ensure read/write permissions are given to the
+ Neo4j daemon user <literal>neo4j</literal>.
+ </para>
+ <para>
+ The public certificate as defined by
+ <option>publicCertificate</option> is required to be duplicated
+ to this directory.
+ '';
+ };
+
+ directoriesToCreate = mkOption {
+ type = types.listOf types.path;
+ internal = true;
+ readOnly = true;
+ description = ''
+ Directories of this policy that will be created automatically
+ when the certificates directory is left at its default value.
+ This includes all options of type path that are left at their
+ default value.
+ '';
+ };
+
+ };
+
+ config.directoriesToCreate = optionals
+ (certDirOpt.highestPrio >= 1500 && options.baseDirectory.highestPrio >= 1500)
+ (map (opt: opt.value) (filter isDefaultPathOption (attrValues options)));
+
+ }));
+ default = {};
+ description = ''
+ Defines the SSL policies for use with Neo4j connectors. Each attribute
+ of this set defines a policy, with the attribute name defining the name
+ of the policy and its namespace. Refer to the operations manual section
+ on Neo4j's
+ <link xlink:href="https://neo4j.com/docs/operations-manual/current/security/ssl-framework/">SSL Framework</link>
+ for further details.
+ '';
+ };
+
+ udc = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the Usage Data Collector which Neo4j uses to collect usage
+ data. Refer to the operations manual section on the
+ <link xlink:href="https://neo4j.com/docs/operations-manual/current/configuration/usage-data-collector/">Usage Data Collector</link>
+ for more information.
+ '';
+ };
+ };
+
+ };
+
+ ###### implementation
+
+ config =
+ let
+ # Assertion helpers
+ policyNameList = attrNames cfg.ssl.policies;
+ validPolicyNameList = [ "legacy" ] ++ policyNameList;
+ validPolicyNameString = concatStringsSep ", " validPolicyNameList;
+
+ # Capture various directories left at their default so they can be created.
+ defaultDirectoriesToCreate = map (opt: opt.value) (filter isDefaultPathOption (attrValues options.services.neo4j.directories));
+ policyDirectoriesToCreate = concatMap (pol: pol.directoriesToCreate) (attrValues cfg.ssl.policies);
+ in
+
+ mkIf cfg.enable {
+ assertions = [
+ { assertion = !elem "legacy" policyNameList;
+ message = "The policy 'legacy' is special to Neo4j, and its name is reserved."; }
+ { assertion = elem cfg.bolt.sslPolicy validPolicyNameList;
+ message = "Invalid policy assigned: `services.neo4j.bolt.sslPolicy = \"${cfg.bolt.sslPolicy}\"`, defined policies are: ${validPolicyNameString}"; }
+ { assertion = elem cfg.https.sslPolicy validPolicyNameList;
+ message = "Invalid policy assigned: `services.neo4j.https.sslPolicy = \"${cfg.https.sslPolicy}\"`, defined policies are: ${validPolicyNameString}"; }
+ ];
+
+ systemd.services.neo4j = {
+ description = "Neo4j Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment = {
+ NEO4J_HOME = "${cfg.package}/share/neo4j";
+ NEO4J_CONF = "${cfg.directories.home}/conf";
+ };
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/neo4j console";
+ User = "neo4j";
+ PermissionsStartOnly = true;
+ LimitNOFILE = 40000;
+ };
+
+ preStart = ''
+ # Directories Setup
+ # Always ensure home exists with nested conf, logs directories.
+ mkdir -m 0700 -p ${cfg.directories.home}/{conf,logs}
+
+ # Create other sub-directories and policy directories that have been left at their default.
+ ${concatMapStringsSep "\n" (
+ dir: ''
+ mkdir -m 0700 -p ${dir}
+ '') (defaultDirectoriesToCreate ++ policyDirectoriesToCreate)}
+
+ # Place the configuration where Neo4j can find it.
+ ln -fs ${serverConfig} ${cfg.directories.home}/conf/neo4j.conf
+
+ # Ensure neo4j user ownership
+ chown -R neo4j ${cfg.directories.home}
+ '';
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ users.users = singleton {
+ name = "neo4j";
+ uid = config.ids.uids.neo4j;
+ description = "Neo4j daemon user";
+ home = cfg.directories.home;
+ };
+ };
+
+ meta = {
+ maintainers = with lib.maintainers; [ patternspandemic ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/openldap.nix b/nixpkgs/nixos/modules/services/databases/openldap.nix
new file mode 100644
index 00000000000..5bf57a1bf9c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/openldap.nix
@@ -0,0 +1,282 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.openldap;
+ openldap = pkgs.openldap;
+
+ dataFile = pkgs.writeText "ldap-contents.ldif" cfg.declarativeContents;
+ configFile = pkgs.writeText "slapd.conf" ((optionalString cfg.defaultSchemas ''
+ include ${pkgs.openldap.out}/etc/schema/core.schema
+ include ${pkgs.openldap.out}/etc/schema/cosine.schema
+ include ${pkgs.openldap.out}/etc/schema/inetorgperson.schema
+ include ${pkgs.openldap.out}/etc/schema/nis.schema
+ '') + ''
+ ${cfg.extraConfig}
+ database ${cfg.database}
+ suffix ${cfg.suffix}
+ rootdn ${cfg.rootdn}
+ ${if (cfg.rootpw != null) then ''
+ rootpw ${cfg.rootpw}
+ '' else ''
+ include ${cfg.rootpwFile}
+ ''}
+ directory ${cfg.dataDir}
+ ${cfg.extraDatabaseConfig}
+ '');
+ configOpts = if cfg.configDir == null then "-f ${configFile}"
+ else "-F ${cfg.configDir}";
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.openldap = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "
+ Whether to enable the ldap server.
+ ";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "openldap";
+ description = "User account under which slapd runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "openldap";
+ description = "Group account under which slapd runs.";
+ };
+
+ urlList = mkOption {
+ type = types.listOf types.str;
+ default = [ "ldap:///" ];
+ description = "URL list slapd should listen on.";
+ example = [ "ldaps:///" ];
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/db/openldap";
+ description = "The database directory.";
+ };
+
+ defaultSchemas = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Include the default schemas core, cosine, inetorgperson and nis.
+ This setting will be ignored if configDir is set.
+ '';
+ };
+
+ database = mkOption {
+ type = types.str;
+ default = "mdb";
+ description = ''
+ Database type to use for the LDAP.
+ This setting will be ignored if configDir is set.
+ '';
+ };
+
+ suffix = mkOption {
+ type = types.str;
+ example = "dc=example,dc=org";
+ description = ''
+ Specify the DN suffix of queries that will be passed to this backend
+ database.
+ This setting will be ignored if configDir is set.
+ '';
+ };
+
+ rootdn = mkOption {
+ type = types.str;
+ example = "cn=admin,dc=example,dc=org";
+ description = ''
+ Specify the distinguished name that is not subject to access control
+ or administrative limit restrictions for operations on this database.
+ This setting will be ignored if configDir is set.
+ '';
+ };
+
+ rootpw = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Password for the root user.
+ This setting will be ignored if configDir is set.
+ Using this option will store the root password in plain text in the
+ world-readable nix store. To avoid this the <literal>rootpwFile</literal> can be used.
+ '';
+ };
+
+ rootpwFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Password file for the root user.
+ The file should contain the string <literal>rootpw</literal> followed by the password.
+ e.g.: <literal>rootpw mysecurepassword</literal>
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.str;
+ default = "0";
+ example = "acl trace";
+ description = "The log level selector of slapd.";
+ };
+
+ configDir = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "Use this optional config directory instead of using slapd.conf";
+ example = "/var/db/slapd.d";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "
+ slapd.conf configuration
+ ";
+ example = literalExample ''
+ '''
+ include ${pkgs.openldap.out}/etc/schema/core.schema
+ include ${pkgs.openldap.out}/etc/schema/cosine.schema
+ include ${pkgs.openldap.out}/etc/schema/inetorgperson.schema
+ include ${pkgs.openldap.out}/etc/schema/nis.schema
+
+ database bdb
+ suffix dc=example,dc=org
+ rootdn cn=admin,dc=example,dc=org
+ # NOTE: change after first start
+ rootpw secret
+ directory /var/db/openldap
+ '''
+ '';
+ };
+
+ declarativeContents = mkOption {
+ type = with types; nullOr lines;
+ default = null;
+ description = ''
+ Declarative contents for the LDAP database, in LDIF format.
+
+ Note a few facts when using it. First, the database
+ <emphasis>must</emphasis> be stored in the directory defined by
+ <code>dataDir</code>. Second, all <code>dataDir</code> will be erased
+ when starting the LDAP server. Third, modifications to the database
+ are not prevented, they are just dropped on the next reboot of the
+ server. Finally, performance-wise the database and indexes are rebuilt
+ on each server startup, so this will slow down server startup,
+ especially with large databases.
+ '';
+ example = ''
+ dn: dc=example,dc=org
+ objectClass: domain
+ dc: example
+
+ dn: ou=users,dc=example,dc=org
+ objectClass = organizationalUnit
+ ou: users
+
+ # ...
+ '';
+ };
+
+ extraDatabaseConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ slapd.conf configuration after the database option.
+ This setting will be ignored if configDir is set.
+ '';
+ example = ''
+ # Indices to maintain for this directory
+ # unique id so equality match only
+ index uid eq
+ # allows general searching on commonname, givenname and email
+ index cn,gn,mail eq,sub
+ # allows multiple variants on surname searching
+ index sn eq,sub
+ # sub above includes subintial,subany,subfinal
+ # optimise department searches
+ index ou eq
+ # if searches will include objectClass uncomment following
+ # index objectClass eq
+ # shows use of default index parameter
+ index default eq,sub
+ # indices missing - uses default eq,sub
+ index telephonenumber
+
+ # other database parameters
+ # read more in slapd.conf reference section
+ cachesize 10000
+ checkpoint 128 15
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = cfg.configDir != null || cfg.rootpwFile != null || cfg.rootpw != null;
+ message = "services.openldap: Unless configDir is set, either rootpw or rootpwFile must be set";
+ }
+ ];
+
+ environment.systemPackages = [ openldap ];
+
+ systemd.services.openldap = {
+ description = "LDAP server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ preStart = ''
+ mkdir -p /run/slapd
+ chown -R "${cfg.user}:${cfg.group}" /run/slapd
+ ${optionalString (cfg.declarativeContents != null) ''
+ rm -Rf "${cfg.dataDir}"
+ ''}
+ mkdir -p "${cfg.dataDir}"
+ ${optionalString (cfg.declarativeContents != null) ''
+ ${openldap.out}/bin/slapadd ${configOpts} -l ${dataFile}
+ ''}
+ chown -R "${cfg.user}:${cfg.group}" "${cfg.dataDir}"
+ '';
+ serviceConfig.ExecStart =
+ "${openldap.out}/libexec/slapd -d '${cfg.logLevel}' " +
+ "-u '${cfg.user}' -g '${cfg.group}' " +
+ "-h '${concatStringsSep " " cfg.urlList}' " +
+ "${configOpts}";
+ };
+
+ users.users.openldap =
+ { name = cfg.user;
+ group = cfg.group;
+ uid = config.ids.uids.openldap;
+ };
+
+ users.groups.openldap =
+ { name = cfg.group;
+ gid = config.ids.gids.openldap;
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/opentsdb.nix b/nixpkgs/nixos/modules/services/databases/opentsdb.nix
new file mode 100644
index 00000000000..c4bd71f3d60
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/opentsdb.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.opentsdb;
+
+ configFile = pkgs.writeText "opentsdb.conf" cfg.config;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.opentsdb = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run OpenTSDB.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.opentsdb;
+ defaultText = "pkgs.opentsdb";
+ example = literalExample "pkgs.opentsdb";
+ description = ''
+ OpenTSDB package to use.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "opentsdb";
+ description = ''
+ User account under which OpenTSDB runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "opentsdb";
+ description = ''
+ Group account under which OpenTSDB runs.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 4242;
+ description = ''
+ Which port OpenTSDB listens on.
+ '';
+ };
+
+ config = mkOption {
+ type = types.lines;
+ default = ''
+ tsd.core.auto_create_metrics = true
+ tsd.http.request.enable_chunked = true
+ '';
+ description = ''
+ The contents of OpenTSDB's configuration file
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.opentsdb.enable {
+
+ systemd.services.opentsdb = {
+ description = "OpenTSDB Server";
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "hbase.service" ];
+
+ environment.JAVA_HOME = "${pkgs.jre}";
+ path = [ pkgs.gnuplot ];
+
+ preStart =
+ ''
+ COMPRESSION=NONE HBASE_HOME=${config.services.hbase.package} ${cfg.package}/share/opentsdb/tools/create_table.sh
+ '';
+
+ serviceConfig = {
+ PermissionsStartOnly = true;
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${cfg.package}/bin/tsdb tsd --staticroot=${cfg.package}/share/opentsdb/static --cachedir=/tmp/opentsdb --port=${toString cfg.port} --config=${configFile}";
+ };
+ };
+
+ users.users.opentsdb = {
+ description = "OpenTSDB Server user";
+ group = "opentsdb";
+ uid = config.ids.uids.opentsdb;
+ };
+
+ users.groups.opentsdb.gid = config.ids.gids.opentsdb;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/pgmanage.nix b/nixpkgs/nixos/modules/services/databases/pgmanage.nix
new file mode 100644
index 00000000000..0f8634dab31
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/pgmanage.nix
@@ -0,0 +1,206 @@
+{ lib, pkgs, config, ... } :
+
+with lib;
+
+let
+ cfg = config.services.pgmanage;
+
+ confFile = pkgs.writeTextFile {
+ name = "pgmanage.conf";
+ text = ''
+ connection_file = ${pgmanageConnectionsFile}
+
+ allow_custom_connections = ${builtins.toJSON cfg.allowCustomConnections}
+
+ pgmanage_port = ${toString cfg.port}
+
+ super_only = ${builtins.toJSON cfg.superOnly}
+
+ ${optionalString (cfg.loginGroup != null) "login_group = ${cfg.loginGroup}"}
+
+ login_timeout = ${toString cfg.loginTimeout}
+
+ web_root = ${cfg.package}/etc/pgmanage/web_root
+
+ sql_root = ${cfg.sqlRoot}
+
+ ${optionalString (cfg.tls != null) ''
+ tls_cert = ${cfg.tls.cert}
+ tls_key = ${cfg.tls.key}
+ ''}
+
+ log_level = ${cfg.logLevel}
+ '';
+ };
+
+ pgmanageConnectionsFile = pkgs.writeTextFile {
+ name = "pgmanage-connections.conf";
+ text = concatStringsSep "\n"
+ (mapAttrsToList (name : conn : "${name}: ${conn}") cfg.connections);
+ };
+
+ pgmanage = "pgmanage";
+
+in {
+
+ options.services.pgmanage = {
+ enable = mkEnableOption "PostgreSQL Administration for the web";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.pgmanage;
+ defaultText = "pkgs.pgmanage";
+ description = ''
+ The pgmanage package to use.
+ '';
+ };
+
+ connections = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = {
+ nuc-server = "hostaddr=192.168.0.100 port=5432 dbname=postgres";
+ mini-server = "hostaddr=127.0.0.1 port=5432 dbname=postgres sslmode=require";
+ };
+ description = ''
+ pgmanage requires at least one PostgreSQL server be defined.
+ </para><para>
+ Detailed information about PostgreSQL connection strings is available at:
+ <link xlink:href="http://www.postgresql.org/docs/current/static/libpq-connect.html"/>
+ </para><para>
+ Note that you should not specify your user name or password. That
+ information will be entered on the login screen. If you specify a
+ username or password, it will be removed by pgmanage before attempting to
+ connect to a database.
+ '';
+ };
+
+ allowCustomConnections = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This tells pgmanage whether or not to allow anyone to use a custom
+ connection from the login screen.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8080;
+ description = ''
+ This tells pgmanage what port to listen on for browser requests.
+ '';
+ };
+
+ localOnly = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ This tells pgmanage whether or not to set the listening socket to local
+ addresses only.
+ '';
+ };
+
+ superOnly = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ This tells pgmanage whether or not to only allow super users to
+ login. The recommended value is true and will restrict users who are not
+ super users from logging in to any PostgreSQL instance through
+ pgmanage. Note that a connection will be made to PostgreSQL in order to
+ test if the user is a superuser.
+ '';
+ };
+
+ loginGroup = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ This tells pgmanage to only allow users in a certain PostgreSQL group to
+ login to pgmanage. Note that a connection will be made to PostgreSQL in
+ order to test if the user is a member of the login group.
+ '';
+ };
+
+ loginTimeout = mkOption {
+ type = types.int;
+ default = 3600;
+ description = ''
+ Number of seconds of inactivity before user is automatically logged
+ out.
+ '';
+ };
+
+ sqlRoot = mkOption {
+ type = types.str;
+ default = "/var/lib/pgmanage";
+ description = ''
+ This tells pgmanage where to put the SQL file history. All tabs are saved
+ to this location so that if you get disconnected from pgmanage you
+ don't lose your work.
+ '';
+ };
+
+ tls = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ cert = mkOption {
+ type = types.str;
+ description = "TLS certificate";
+ };
+ key = mkOption {
+ type = types.str;
+ description = "TLS key";
+ };
+ };
+ });
+ default = null;
+ description = ''
+ These options tell pgmanage where the TLS Certificate and Key files
+ reside. If you use these options then you'll only be able to access
+ pgmanage through a secure TLS connection. These options are only
+ necessary if you wish to connect directly to pgmanage using a secure TLS
+ connection. As an alternative, you can set up pgmanage in a reverse proxy
+ configuration. This allows your web server to terminate the secure
+ connection and pass on the request to pgmanage. You can find help to set
+ up this configuration in:
+ <link xlink:href="https://github.com/pgManage/pgManage/blob/master/INSTALL_NGINX.md"/>
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.enum ["error" "warn" "notice" "info"];
+ default = "error";
+ description = ''
+ Verbosity of logs
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.pgmanage = {
+ description = "pgmanage - PostgreSQL Administration for the web";
+ wants = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = pgmanage;
+ Group = pgmanage;
+ ExecStart = "${pkgs.pgmanage}/sbin/pgmanage -c ${confFile}" +
+ optionalString cfg.localOnly " --local-only=true";
+ };
+ };
+ users = {
+ users.${pgmanage} = {
+ name = pgmanage;
+ group = pgmanage;
+ home = cfg.sqlRoot;
+ createHome = true;
+ };
+ groups.${pgmanage} = {
+ name = pgmanage;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/postgresql.nix b/nixpkgs/nixos/modules/services/databases/postgresql.nix
new file mode 100644
index 00000000000..7bba4dacddc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/postgresql.nix
@@ -0,0 +1,355 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.postgresql;
+
+ postgresql =
+ if cfg.extraPlugins == []
+ then cfg.package
+ else cfg.package.withPackages (_: cfg.extraPlugins);
+
+ # The main PostgreSQL configuration file.
+ configFile = pkgs.writeText "postgresql.conf"
+ ''
+ hba_file = '${pkgs.writeText "pg_hba.conf" cfg.authentication}'
+ ident_file = '${pkgs.writeText "pg_ident.conf" cfg.identMap}'
+ log_destination = 'stderr'
+ listen_addresses = '${if cfg.enableTCPIP then "*" else "localhost"}'
+ port = ${toString cfg.port}
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.postgresql = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run PostgreSQL.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ example = literalExample "pkgs.postgresql_11";
+ description = ''
+ PostgreSQL package to use.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 5432;
+ description = ''
+ The port on which PostgreSQL listens.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ example = "/var/lib/postgresql/11";
+ description = ''
+ Data directory for PostgreSQL.
+ '';
+ };
+
+ authentication = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Defines how users authenticate themselves to the server. By
+ default, "trust" access to local users will always be granted
+ along with any other custom options. If you do not want this,
+ set this option using "lib.mkForce" to override this
+ behaviour.
+ '';
+ };
+
+ identMap = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Defines the mapping from system users to database users.
+
+ The general form is:
+
+ map-name system-username database-username
+ '';
+ };
+
+ initialScript = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ A file containing SQL statements to execute on first startup.
+ '';
+ };
+
+ ensureDatabases = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Ensures that the specified databases exist.
+ This option will never delete existing databases, especially not when the value of this
+ option is changed. This means that databases created once through this option or
+ otherwise have to be removed manually.
+ '';
+ example = [
+ "gitea"
+ "nextcloud"
+ ];
+ };
+
+ ensureUsers = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ name = mkOption {
+ type = types.str;
+ description = ''
+ Name of the user to ensure.
+ '';
+ };
+ ensurePermissions = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ description = ''
+ Permissions to ensure for the user, specified as an attribute set.
+ The attribute names specify the database and tables to grant the permissions for.
+ The attribute values specify the permissions to grant. You may specify one or
+ multiple comma-separated SQL privileges here.
+
+ For more information on how to specify the target
+ and on which privileges exist, see the
+ <link xlink:href="https://www.postgresql.org/docs/current/sql-grant.html">GRANT syntax</link>.
+ The attributes are used as <code>GRANT ''${attrName} ON ''${attrValue}</code>.
+ '';
+ example = literalExample ''
+ {
+ "DATABASE nextcloud" = "ALL PRIVILEGES";
+ "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
+ }
+ '';
+ };
+ };
+ });
+ default = [];
+ description = ''
+ Ensures that the specified users exist and have at least the ensured permissions.
+ The PostgreSQL users will be identified using peer authentication. This authenticates the Unix user with the
+ same name only, and that without the need for a password.
+ This option will never delete existing users or remove permissions, especially not when the value of this
+ option is changed. This means that users created and permissions assigned once through this option or
+ otherwise have to be removed manually.
+ '';
+ example = literalExample ''
+ [
+ {
+ name = "nextcloud";
+ ensurePermissions = {
+ "DATABASE nextcloud" = "ALL PRIVILEGES";
+ };
+ }
+ {
+ name = "superuser";
+ ensurePermissions = {
+ "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
+ };
+ }
+ ]
+ '';
+ };
+
+ enableTCPIP = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether PostgreSQL should listen on all network interfaces.
+ If disabled, the database can only be accessed via its Unix
+ domain socket or via TCP connections to localhost.
+ '';
+ };
+
+ extraPlugins = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ example = literalExample "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]";
+ description = ''
+ List of PostgreSQL plugins. PostgreSQL version for each plugin should
+ match version for <literal>services.postgresql.package</literal> value.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional text to be appended to <filename>postgresql.conf</filename>.";
+ };
+
+ recoveryConfig = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Contents of the <filename>recovery.conf</filename> file.
+ '';
+ };
+ superUser = mkOption {
+ type = types.str;
+ default= if versionAtLeast config.system.stateVersion "17.09" then "postgres" else "root";
+ internal = true;
+ description = ''
+ NixOS traditionally used 'root' as superuser, most other distros use 'postgres'.
+ From 17.09 we also try to follow this standard. Internal since changing this value
+ would lead to breakage while setting up databases.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.postgresql.enable {
+
+ services.postgresql.package =
+ # Note: when changing the default, make it conditional on
+ # ‘system.stateVersion’ to maintain compatibility with existing
+ # systems!
+ mkDefault (if versionAtLeast config.system.stateVersion "17.09" then pkgs.postgresql_9_6
+ else if versionAtLeast config.system.stateVersion "16.03" then pkgs.postgresql_9_5
+ else throw "postgresql_9_4 was removed, please upgrade your postgresql version.");
+
+ services.postgresql.dataDir =
+ mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/postgresql/${config.services.postgresql.package.psqlSchema}"
+ else "/var/db/postgresql");
+
+ services.postgresql.authentication = mkAfter
+ ''
+ # Generated file; do not edit!
+ local all all ident
+ host all all 127.0.0.1/32 md5
+ host all all ::1/128 md5
+ '';
+
+ users.users.postgres =
+ { name = "postgres";
+ uid = config.ids.uids.postgres;
+ group = "postgres";
+ description = "PostgreSQL server user";
+ home = "${cfg.dataDir}";
+ useDefaultShell = true;
+ };
+
+ users.groups.postgres.gid = config.ids.gids.postgres;
+
+ environment.systemPackages = [ postgresql ];
+
+ environment.pathsToLink = [
+ "/share/postgresql"
+ ];
+
+ systemd.services.postgresql =
+ { description = "PostgreSQL Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ environment.PGDATA = cfg.dataDir;
+
+ path = [ postgresql ];
+
+ preStart =
+ ''
+ # Create data directory.
+ if ! test -e ${cfg.dataDir}/PG_VERSION; then
+ mkdir -m 0700 -p ${cfg.dataDir}
+ rm -f ${cfg.dataDir}/*.conf
+ chown -R postgres:postgres ${cfg.dataDir}
+ fi
+ ''; # */
+
+ script =
+ ''
+ # Initialise the database.
+ if ! test -e ${cfg.dataDir}/PG_VERSION; then
+ initdb -U ${cfg.superUser}
+ # See postStart!
+ touch "${cfg.dataDir}/.first_startup"
+ fi
+ ln -sfn "${configFile}" "${cfg.dataDir}/postgresql.conf"
+ ${optionalString (cfg.recoveryConfig != null) ''
+ ln -sfn "${pkgs.writeText "recovery.conf" cfg.recoveryConfig}" \
+ "${cfg.dataDir}/recovery.conf"
+ ''}
+
+ exec postgres
+ '';
+
+ serviceConfig =
+ { ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ User = "postgres";
+ Group = "postgres";
+ PermissionsStartOnly = true;
+ RuntimeDirectory = "postgresql";
+ 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
+ KillSignal = "SIGINT";
+ KillMode = "mixed";
+
+ # Give Postgres a decent amount of time to clean up after
+ # receiving systemd's SIGINT.
+ TimeoutSec = 120;
+ };
+
+ # Wait for PostgreSQL to be ready to accept connections.
+ postStart =
+ ''
+ PSQL="${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql --port=${toString cfg.port}"
+
+ while ! $PSQL -d postgres -c "" 2> /dev/null; do
+ if ! kill -0 "$MAINPID"; then exit 1; fi
+ sleep 0.1
+ done
+
+ if test -e "${cfg.dataDir}/.first_startup"; then
+ ${optionalString (cfg.initialScript != null) ''
+ $PSQL -f "${cfg.initialScript}" -d postgres
+ ''}
+ rm -f "${cfg.dataDir}/.first_startup"
+ fi
+ '' + optionalString (cfg.ensureDatabases != []) ''
+ ${concatMapStrings (database: ''
+ $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${database}"'
+ '') cfg.ensureDatabases}
+ '' + ''
+ ${concatMapStrings (user: ''
+ $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc "CREATE USER ${user.name}"
+ ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
+ $PSQL -tAc 'GRANT ${permission} ON ${database} TO ${user.name}'
+ '') user.ensurePermissions)}
+ '') cfg.ensureUsers}
+ '';
+
+ unitConfig.RequiresMountsFor = "${cfg.dataDir}";
+ };
+
+ };
+
+ meta.doc = ./postgresql.xml;
+ meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+}
diff --git a/nixpkgs/nixos/modules/services/databases/postgresql.xml b/nixpkgs/nixos/modules/services/databases/postgresql.xml
new file mode 100644
index 00000000000..72d4a8249a3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/postgresql.xml
@@ -0,0 +1,143 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="module-postgresql">
+ <title>PostgreSQL</title>
+<!-- FIXME: render nicely -->
+<!-- FIXME: source can be added automatically -->
+ <para>
+ <emphasis>Source:</emphasis>
+ <filename>modules/services/databases/postgresql.nix</filename>
+ </para>
+ <para>
+ <emphasis>Upstream documentation:</emphasis>
+ <link xlink:href="http://www.postgresql.org/docs/"/>
+ </para>
+<!-- FIXME: more stuff, like maintainer? -->
+ <para>
+ PostgreSQL is an advanced, free relational database.
+<!-- MORE -->
+ </para>
+ <section xml:id="module-services-postgres-configuring">
+ <title>Configuring</title>
+
+ <para>
+ To enable PostgreSQL, add the following to your
+ <filename>configuration.nix</filename>:
+<programlisting>
+<xref linkend="opt-services.postgresql.enable"/> = true;
+<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11;
+</programlisting>
+ Note that you are required to specify the desired version of PostgreSQL
+ (e.g. <literal>pkgs.postgresql_11</literal>). Since upgrading your
+ PostgreSQL version requires a database dump and reload (see below), NixOS
+ cannot provide a default value for
+ <xref linkend="opt-services.postgresql.package"/> such as the most recent
+ release of PostgreSQL.
+ </para>
+
+<!--
+<para>After running <command>nixos-rebuild</command>, you can verify
+whether PostgreSQL works by running <command>psql</command>:
+
+<screen>
+<prompt>$ </prompt>psql
+psql (9.2.9)
+Type "help" for help.
+
+<prompt>alice=></prompt>
+</screen>
+-->
+
+ <para>
+ By default, PostgreSQL stores its databases in
+ <filename>/var/lib/postgresql/$psqlSchema</filename>. You can override this using
+ <xref linkend="opt-services.postgresql.dataDir"/>, e.g.
+<programlisting>
+<xref linkend="opt-services.postgresql.dataDir"/> = "/data/postgresql";
+</programlisting>
+ </para>
+ </section>
+ <section xml:id="module-services-postgres-upgrading">
+ <title>Upgrading</title>
+
+ <para>
+ 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>
+ <section xml:id="module-services-postgres-plugins">
+ <title>Plugins</title>
+
+ <para>
+ Plugins collection for each PostgreSQL version can be accessed with
+ <literal>.pkgs</literal>. For example, for
+ <literal>pkgs.postgresql_11</literal> package, its plugin collection is
+ accessed by <literal>pkgs.postgresql_11.pkgs</literal>:
+<screen>
+<prompt>$ </prompt>nix repl '&lt;nixpkgs&gt;'
+
+Loading '&lt;nixpkgs&gt;'...
+Added 10574 variables.
+
+<prompt>nix-repl&gt; </prompt>postgresql_11.pkgs.&lt;TAB&gt;&lt;TAB&gt;
+postgresql_11.pkgs.cstore_fdw postgresql_11.pkgs.pg_repack
+postgresql_11.pkgs.pg_auto_failover postgresql_11.pkgs.pg_safeupdate
+postgresql_11.pkgs.pg_bigm postgresql_11.pkgs.pg_similarity
+postgresql_11.pkgs.pg_cron postgresql_11.pkgs.pg_topn
+postgresql_11.pkgs.pg_hll postgresql_11.pkgs.pgjwt
+postgresql_11.pkgs.pg_partman postgresql_11.pkgs.pgroonga
+...
+</screen>
+ </para>
+ <para>
+ To add plugins via NixOS configuration, set <literal>services.postgresql.extraPlugins</literal>:
+<programlisting>
+<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11;
+<xref linkend="opt-services.postgresql.extraPlugins"/> = with pkgs.postgresql_11.pkgs; [
+ pg_repack
+ postgis
+];
+</programlisting>
+ </para>
+ <para>
+ You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using
+ function <literal>.withPackages</literal>. For example, creating a custom
+ PostgreSQL package in an overlay can look like:
+<programlisting>
+self: super: {
+ postgresql_custom = self.postgresql_11.withPackages (ps: [
+ ps.pg_repack
+ ps.postgis
+ ]);
+}
+</programlisting>
+ </para>
+ <para>
+ Here's a recipe on how to override a particular plugin through an overlay:
+<programlisting>
+self: super: {
+ postgresql_11 = super.postgresql_11.override { this = self.postgresql_11; } // {
+ pkgs = super.postgresql_11.pkgs // {
+ pg_repack = super.postgresql_11.pkgs.pg_repack.overrideAttrs (_: {
+ name = "pg_repack-v20181024";
+ src = self.fetchzip {
+ url = "https://github.com/reorg/pg_repack/archive/923fa2f3c709a506e111cc963034bf2fd127aa00.tar.gz";
+ sha256 = "17k6hq9xaax87yz79j773qyigm4fwk8z4zh5cyp6z0sxnwfqxxw5";
+ };
+ });
+ };
+ };
+}
+</programlisting>
+ </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/databases/redis.nix b/nixpkgs/nixos/modules/services/databases/redis.nix
new file mode 100644
index 00000000000..9c389d80a6d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/redis.nix
@@ -0,0 +1,226 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.redis;
+ redisBool = b: if b then "yes" else "no";
+ condOption = name: value: if value != null then "${name} ${toString value}" else "";
+
+ redisConfig = pkgs.writeText "redis.conf" ''
+ port ${toString cfg.port}
+ ${condOption "bind" cfg.bind}
+ ${condOption "unixsocket" cfg.unixSocket}
+ daemonize yes
+ supervised systemd
+ loglevel ${cfg.logLevel}
+ logfile ${cfg.logfile}
+ syslog-enabled ${redisBool cfg.syslog}
+ pidfile /run/redis/redis.pid
+ databases ${toString cfg.databases}
+ ${concatMapStrings (d: "save ${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}\n") cfg.save}
+ dbfilename dump.rdb
+ dir /var/lib/redis
+ ${if cfg.slaveOf != null then "slaveof ${cfg.slaveOf.ip} ${toString cfg.slaveOf.port}" else ""}
+ ${condOption "masterauth" cfg.masterAuth}
+ ${condOption "requirepass" cfg.requirePass}
+ appendOnly ${redisBool cfg.appendOnly}
+ appendfsync ${cfg.appendFsync}
+ slowlog-log-slower-than ${toString cfg.slowLogLogSlowerThan}
+ slowlog-max-len ${toString cfg.slowLogMaxLen}
+ ${cfg.extraConfig}
+ '';
+in
+{
+
+ ###### interface
+
+ options = {
+
+ services.redis = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the Redis server. Note that the NixOS module for
+ Redis disables kernel support for Transparent Huge Pages (THP),
+ because this features causes major performance problems for Redis,
+ e.g. (https://redis.io/topics/latency).
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.redis;
+ defaultText = "pkgs.redis";
+ description = "Which Redis derivation to use.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 6379;
+ description = "The port for Redis to listen to.";
+ };
+
+ vmOverCommit = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Set vm.overcommit_memory to 1 (Suggested for Background Saving: http://redis.io/topics/faq)
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to open ports in the firewall for the server.
+ '';
+ };
+
+ bind = mkOption {
+ type = with types; nullOr str;
+ default = null; # All interfaces
+ description = "The IP interface to bind to.";
+ example = "127.0.0.1";
+ };
+
+ unixSocket = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = "The path to the socket to bind to.";
+ example = "/run/redis/redis.sock";
+ };
+
+ logLevel = mkOption {
+ type = types.str;
+ default = "notice"; # debug, verbose, notice, warning
+ example = "debug";
+ description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
+ };
+
+ logfile = mkOption {
+ type = types.str;
+ default = "/dev/null";
+ description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
+ example = "/var/log/redis.log";
+ };
+
+ syslog = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Enable logging to the system logger.";
+ };
+
+ databases = mkOption {
+ type = types.int;
+ default = 16;
+ description = "Set the number of databases.";
+ };
+
+ save = mkOption {
+ type = with types; listOf (listOf int);
+ default = [ [900 1] [300 10] [60 10000] ];
+ description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.";
+ example = [ [900 1] [300 10] [60 10000] ];
+ };
+
+ slaveOf = mkOption {
+ default = null; # { ip, port }
+ description = "An attribute set with two attributes: ip and port to which this redis instance acts as a slave.";
+ example = { ip = "192.168.1.100"; port = 6379; };
+ };
+
+ masterAuth = mkOption {
+ default = null;
+ description = ''If the master is password protected (using the requirePass configuration)
+ it is possible to tell the slave to authenticate before starting the replication synchronization
+ process, otherwise the master will refuse the slave request.
+ (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
+ };
+
+ requirePass = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)";
+ example = "letmein!";
+ };
+
+ appendOnly = mkOption {
+ type = types.bool;
+ default = false;
+ description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
+ };
+
+ appendFsync = mkOption {
+ type = types.str;
+ default = "everysec"; # no, always, everysec
+ description = "How often to fsync the append-only log, options: no, always, everysec.";
+ };
+
+ slowLogLogSlowerThan = mkOption {
+ type = types.int;
+ default = 10000;
+ description = "Log queries whose execution take longer than X in milliseconds.";
+ example = 1000;
+ };
+
+ slowLogMaxLen = mkOption {
+ type = types.int;
+ default = 128;
+ description = "Maximum number of items to keep in slow log.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra configuration options for redis.conf.";
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.redis.enable {
+
+ boot.kernel.sysctl = mkIf cfg.vmOverCommit {
+ "vm.overcommit_memory" = "1";
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.port ];
+ };
+
+ users.users.redis.description = "Redis database user";
+
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services.disable-transparent-huge-pages = {
+ description = "Disable Transparent Huge Pages (required by Redis)";
+ before = [ "redis.service" ];
+ wantedBy = [ "redis.service" ];
+ script = "echo never > /sys/kernel/mm/transparent_hugepage/enabled";
+ serviceConfig.Type = "oneshot";
+ };
+
+ systemd.services.redis =
+ { description = "Redis Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/redis-server ${redisConfig}";
+ RuntimeDirectory = "redis";
+ StateDirectory = "redis";
+ Type = "notify";
+ User = "redis";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/rethinkdb.nix b/nixpkgs/nixos/modules/services/databases/rethinkdb.nix
new file mode 100644
index 00000000000..4828e594b32
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/rethinkdb.nix
@@ -0,0 +1,110 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.rethinkdb;
+ rethinkdb = cfg.package;
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.rethinkdb = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable the RethinkDB server.";
+ };
+
+ #package = mkOption {
+ # default = pkgs.rethinkdb;
+ # description = "Which RethinkDB derivation to use.";
+ #};
+
+ user = mkOption {
+ default = "rethinkdb";
+ description = "User account under which RethinkDB runs.";
+ };
+
+ group = mkOption {
+ default = "rethinkdb";
+ description = "Group which rethinkdb user belongs to.";
+ };
+
+ dbpath = mkOption {
+ default = "/var/db/rethinkdb";
+ description = "Location where RethinkDB stores its data, 1 data directory per instance.";
+ };
+
+ pidpath = mkOption {
+ default = "/run/rethinkdb";
+ description = "Location where each instance's pid file is located.";
+ };
+
+ #cfgpath = mkOption {
+ # default = "/etc/rethinkdb/instances.d";
+ # description = "Location where RethinkDB stores it config files, 1 config file per instance.";
+ #};
+
+ # TODO: currently not used by our implementation.
+ #instances = mkOption {
+ # type = types.attrsOf types.str;
+ # default = {};
+ # description = "List of named RethinkDB instances in our cluster.";
+ #};
+
+ };
+
+ };
+
+ ###### implementation
+ config = mkIf config.services.rethinkdb.enable {
+
+ environment.systemPackages = [ rethinkdb ];
+
+ systemd.services.rethinkdb = {
+ description = "RethinkDB server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ # TODO: abstract away 'default', which is a per-instance directory name
+ # allowing end user of this nix module to provide multiple instances,
+ # and associated directory per instance
+ ExecStart = "${rethinkdb}/bin/rethinkdb -d ${cfg.dbpath}/default";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ User = cfg.user;
+ Group = cfg.group;
+ PIDFile = "${cfg.pidpath}/default.pid";
+ PermissionsStartOnly = true;
+ };
+
+ preStart = ''
+ if ! test -e ${cfg.dbpath}; then
+ install -d -m0755 -o ${cfg.user} -g ${cfg.group} ${cfg.dbpath}
+ install -d -m0755 -o ${cfg.user} -g ${cfg.group} ${cfg.dbpath}/default
+ chown -R ${cfg.user}:${cfg.group} ${cfg.dbpath}
+ fi
+ if ! test -e "${cfg.pidpath}/default.pid"; then
+ install -D -o ${cfg.user} -g ${cfg.group} /dev/null "${cfg.pidpath}/default.pid"
+ fi
+ '';
+ };
+
+ users.users.rethinkdb = mkIf (cfg.user == "rethinkdb")
+ { name = "rethinkdb";
+ description = "RethinkDB server user";
+ };
+
+ users.groups = optionalAttrs (cfg.group == "rethinkdb") (singleton
+ { name = "rethinkdb";
+ });
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/riak-cs.nix b/nixpkgs/nixos/modules/services/databases/riak-cs.nix
new file mode 100644
index 00000000000..2cb204f729a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/riak-cs.nix
@@ -0,0 +1,202 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.riak-cs;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.riak-cs = {
+
+ enable = mkEnableOption "riak-cs";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.riak-cs;
+ defaultText = "pkgs.riak-cs";
+ example = literalExample "pkgs.riak-cs";
+ description = ''
+ Riak package to use.
+ '';
+ };
+
+ nodeName = mkOption {
+ type = types.str;
+ default = "riak-cs@127.0.0.1";
+ description = ''
+ Name of the Erlang node.
+ '';
+ };
+
+ anonymousUserCreation = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Anonymous user creation.
+ '';
+ };
+
+ riakHost = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8087";
+ description = ''
+ Name of riak hosting service.
+ '';
+ };
+
+ listener = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8080";
+ description = ''
+ Name of Riak CS listening service.
+ '';
+ };
+
+ stanchionHost = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8085";
+ description = ''
+ Name of stanchion hosting service.
+ '';
+ };
+
+ stanchionSsl = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Tell stanchion to use SSL.
+ '';
+ };
+
+ distributedCookie = mkOption {
+ type = types.str;
+ default = "riak";
+ description = ''
+ Cookie for distributed node communication. All nodes in the
+ same cluster should use the same cookie or they will not be able to
+ communicate.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/db/riak-cs";
+ description = ''
+ Data directory for Riak CS.
+ '';
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ default = "/var/log/riak-cs";
+ description = ''
+ Log directory for Riak CS.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional text to be appended to <filename>riak-cs.conf</filename>.
+ '';
+ };
+
+ extraAdvancedConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional text to be appended to <filename>advanced.config</filename>.
+ '';
+ };
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+ environment.etc."riak-cs/riak-cs.conf".text = ''
+ nodename = ${cfg.nodeName}
+ distributed_cookie = ${cfg.distributedCookie}
+
+ platform_log_dir = ${cfg.logDir}
+
+ riak_host = ${cfg.riakHost}
+ listener = ${cfg.listener}
+ stanchion_host = ${cfg.stanchionHost}
+
+ anonymous_user_creation = ${if cfg.anonymousUserCreation then "on" else "off"}
+
+ ${cfg.extraConfig}
+ '';
+
+ environment.etc."riak-cs/advanced.config".text = ''
+ ${cfg.extraAdvancedConfig}
+ '';
+
+ users.users.riak-cs = {
+ name = "riak-cs";
+ uid = config.ids.uids.riak-cs;
+ group = "riak";
+ description = "Riak CS server user";
+ };
+
+ systemd.services.riak-cs = {
+ description = "Riak CS Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ path = [
+ pkgs.utillinux # for `logger`
+ pkgs.bash
+ ];
+
+ environment.HOME = "${cfg.dataDir}";
+ environment.RIAK_CS_DATA_DIR = "${cfg.dataDir}";
+ environment.RIAK_CS_LOG_DIR = "${cfg.logDir}";
+ environment.RIAK_CS_ETC_DIR = "/etc/riak";
+
+ preStart = ''
+ if ! test -e ${cfg.logDir}; then
+ mkdir -m 0755 -p ${cfg.logDir}
+ chown -R riak-cs ${cfg.logDir}
+ fi
+
+ if ! test -e ${cfg.dataDir}; then
+ mkdir -m 0700 -p ${cfg.dataDir}
+ chown -R riak-cs ${cfg.dataDir}
+ fi
+ '';
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/riak-cs console";
+ ExecStop = "${cfg.package}/bin/riak-cs stop";
+ StandardInput = "tty";
+ User = "riak-cs";
+ Group = "riak-cs";
+ PermissionsStartOnly = true;
+ # Give Riak a decent amount of time to clean up.
+ TimeoutStopSec = 120;
+ LimitNOFILE = 65536;
+ };
+
+ unitConfig.RequiresMountsFor = [
+ "${cfg.dataDir}"
+ "${cfg.logDir}"
+ "/etc/riak"
+ ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/riak.nix b/nixpkgs/nixos/modules/services/databases/riak.nix
new file mode 100644
index 00000000000..885215209bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/riak.nix
@@ -0,0 +1,163 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.riak;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.riak = {
+
+ enable = mkEnableOption "riak";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.riak;
+ defaultText = "pkgs.riak";
+ example = literalExample "pkgs.riak";
+ description = ''
+ Riak package to use.
+ '';
+ };
+
+ nodeName = mkOption {
+ type = types.str;
+ default = "riak@127.0.0.1";
+ description = ''
+ Name of the Erlang node.
+ '';
+ };
+
+ distributedCookie = mkOption {
+ type = types.str;
+ default = "riak";
+ description = ''
+ Cookie for distributed node communication. All nodes in the
+ same cluster should use the same cookie or they will not be able to
+ communicate.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/db/riak";
+ description = ''
+ Data directory for Riak.
+ '';
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ default = "/var/log/riak";
+ description = ''
+ Log directory for Riak.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional text to be appended to <filename>riak.conf</filename>.
+ '';
+ };
+
+ extraAdvancedConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional text to be appended to <filename>advanced.config</filename>.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+ environment.etc."riak/riak.conf".text = ''
+ nodename = ${cfg.nodeName}
+ distributed_cookie = ${cfg.distributedCookie}
+
+ platform_log_dir = ${cfg.logDir}
+ platform_etc_dir = /etc/riak
+ platform_data_dir = ${cfg.dataDir}
+
+ ${cfg.extraConfig}
+ '';
+
+ environment.etc."riak/advanced.config".text = ''
+ ${cfg.extraAdvancedConfig}
+ '';
+
+ users.users.riak = {
+ name = "riak";
+ uid = config.ids.uids.riak;
+ group = "riak";
+ description = "Riak server user";
+ };
+
+ users.groups.riak.gid = config.ids.gids.riak;
+
+ systemd.services.riak = {
+ description = "Riak Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ path = [
+ pkgs.utillinux # for `logger`
+ pkgs.bash
+ ];
+
+ environment.HOME = "${cfg.dataDir}";
+ environment.RIAK_DATA_DIR = "${cfg.dataDir}";
+ environment.RIAK_LOG_DIR = "${cfg.logDir}";
+ environment.RIAK_ETC_DIR = "/etc/riak";
+
+ preStart = ''
+ if ! test -e ${cfg.logDir}; then
+ mkdir -m 0755 -p ${cfg.logDir}
+ chown -R riak ${cfg.logDir}
+ fi
+
+ if ! test -e ${cfg.dataDir}; then
+ mkdir -m 0700 -p ${cfg.dataDir}
+ chown -R riak ${cfg.dataDir}
+ fi
+ '';
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/riak console";
+ ExecStop = "${cfg.package}/bin/riak stop";
+ StandardInput = "tty";
+ User = "riak";
+ Group = "riak";
+ PermissionsStartOnly = true;
+ # Give Riak a decent amount of time to clean up.
+ TimeoutStopSec = 120;
+ LimitNOFILE = 65536;
+ };
+
+ unitConfig.RequiresMountsFor = [
+ "${cfg.dataDir}"
+ "${cfg.logDir}"
+ "/etc/riak"
+ ];
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/stanchion.nix b/nixpkgs/nixos/modules/services/databases/stanchion.nix
new file mode 100644
index 00000000000..97e55bc70c4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/stanchion.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.stanchion;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.stanchion = {
+
+ enable = mkEnableOption "stanchion";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.stanchion;
+ defaultText = "pkgs.stanchion";
+ example = literalExample "pkgs.stanchion";
+ description = ''
+ Stanchion package to use.
+ '';
+ };
+
+ nodeName = mkOption {
+ type = types.str;
+ default = "stanchion@127.0.0.1";
+ description = ''
+ Name of the Erlang node.
+ '';
+ };
+
+ adminKey = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Name of admin user.
+ '';
+ };
+
+ adminSecret = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Name of admin secret
+ '';
+ };
+
+ riakHost = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8087";
+ description = ''
+ Name of riak hosting service.
+ '';
+ };
+
+ listener = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8085";
+ description = ''
+ Name of Riak CS listening service.
+ '';
+ };
+
+ stanchionHost = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8085";
+ description = ''
+ Name of stanchion hosting service.
+ '';
+ };
+
+ distributedCookie = mkOption {
+ type = types.str;
+ default = "riak";
+ description = ''
+ Cookie for distributed node communication. All nodes in the
+ same cluster should use the same cookie or they will not be able to
+ communicate.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/db/stanchion";
+ description = ''
+ Data directory for Stanchion.
+ '';
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ default = "/var/log/stanchion";
+ description = ''
+ Log directory for Stanchion.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional text to be appended to <filename>stanchion.conf</filename>.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ environment.etc."stanchion/advanced.config".text = ''
+ [{stanchion, []}].
+ '';
+
+ environment.etc."stanchion/stanchion.conf".text = ''
+ listener = ${cfg.listener}
+
+ riak_host = ${cfg.riakHost}
+
+ ${optionalString (cfg.adminKey == "") "#"} admin.key=${optionalString (cfg.adminKey != "") cfg.adminKey}
+ ${optionalString (cfg.adminSecret == "") "#"} admin.secret=${optionalString (cfg.adminSecret != "") cfg.adminSecret}
+
+ platform_bin_dir = ${pkgs.stanchion}/bin
+ platform_data_dir = ${cfg.dataDir}
+ platform_etc_dir = /etc/stanchion
+ platform_lib_dir = ${pkgs.stanchion}/lib
+ platform_log_dir = ${cfg.logDir}
+
+ nodename = ${cfg.nodeName}
+
+ distributed_cookie = ${cfg.distributedCookie}
+
+ ${cfg.extraConfig}
+ '';
+
+ users.users.stanchion = {
+ name = "stanchion";
+ uid = config.ids.uids.stanchion;
+ group = "stanchion";
+ description = "Stanchion server user";
+ };
+
+ users.groups.stanchion.gid = config.ids.gids.stanchion;
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.logDir}' - stanchion stanchion --"
+ "d '${cfg.dataDir}' 0700 stanchion stanchion --"
+ ];
+
+ systemd.services.stanchion = {
+ description = "Stanchion Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ path = [
+ pkgs.utillinux # for `logger`
+ pkgs.bash
+ ];
+
+ environment.HOME = "${cfg.dataDir}";
+ environment.STANCHION_DATA_DIR = "${cfg.dataDir}";
+ environment.STANCHION_LOG_DIR = "${cfg.logDir}";
+ environment.STANCHION_ETC_DIR = "/etc/stanchion";
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/stanchion console";
+ ExecStop = "${cfg.package}/bin/stanchion stop";
+ StandardInput = "tty";
+ User = "stanchion";
+ Group = "stanchion";
+ # Give Stanchion a decent amount of time to clean up.
+ TimeoutStopSec = 120;
+ LimitNOFILE = 65536;
+ };
+
+ unitConfig.RequiresMountsFor = [
+ "${cfg.dataDir}"
+ "${cfg.logDir}"
+ "/etc/stanchion"
+ ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/virtuoso.nix b/nixpkgs/nixos/modules/services/databases/virtuoso.nix
new file mode 100644
index 00000000000..6ffc44a5274
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/virtuoso.nix
@@ -0,0 +1,98 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.services.virtuoso;
+ virtuosoUser = "virtuoso";
+ stateDir = "/var/lib/virtuoso";
+in
+with lib;
+{
+
+ ###### interface
+
+ options = {
+
+ services.virtuoso = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable Virtuoso Opensource database server.";
+ };
+
+ config = mkOption {
+ default = "";
+ description = "Extra options to put into Virtuoso configuration file.";
+ };
+
+ parameters = mkOption {
+ default = "";
+ description = "Extra options to put into [Parameters] section of Virtuoso configuration file.";
+ };
+
+ listenAddress = mkOption {
+ default = "1111";
+ example = "myserver:1323";
+ description = "ip:port or port to listen on.";
+ };
+
+ httpListenAddress = mkOption {
+ default = null;
+ example = "myserver:8080";
+ description = "ip:port or port for Virtuoso HTTP server to listen on.";
+ };
+
+ dirsAllowed = mkOption {
+ default = null;
+ example = "/www, /home/";
+ description = "A list of directories Virtuoso is allowed to access";
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton
+ { name = virtuosoUser;
+ uid = config.ids.uids.virtuoso;
+ description = "virtuoso user";
+ home = stateDir;
+ };
+
+ systemd.services.virtuoso = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ mkdir -p ${stateDir}
+ chown ${virtuosoUser} ${stateDir}
+ '';
+
+ script = ''
+ cd ${stateDir}
+ ${pkgs.virtuoso}/bin/virtuoso-t +foreground +configfile ${pkgs.writeText "virtuoso.ini" cfg.config}
+ '';
+ };
+
+ services.virtuoso.config = ''
+ [Database]
+ DatabaseFile=${stateDir}/x-virtuoso.db
+ TransactionFile=${stateDir}/x-virtuoso.trx
+ ErrorLogFile=${stateDir}/x-virtuoso.log
+ xa_persistent_file=${stateDir}/x-virtuoso.pxa
+
+ [Parameters]
+ ServerPort=${cfg.listenAddress}
+ RunAs=${virtuosoUser}
+ ${optionalString (cfg.dirsAllowed != null) "DirsAllowed=${cfg.dirsAllowed}"}
+ ${cfg.parameters}
+
+ [HTTPServer]
+ ${optionalString (cfg.httpListenAddress != null) "ServerPort=${cfg.httpListenAddress}"}
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/accountsservice.nix b/nixpkgs/nixos/modules/services/desktops/accountsservice.nix
new file mode 100644
index 00000000000..c48036a99e8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/accountsservice.nix
@@ -0,0 +1,54 @@
+# AccountsService daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.accounts-daemon = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable AccountsService, a DBus service for accessing
+ the list of user accounts and information attached to those accounts.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.accounts-daemon.enable {
+
+ environment.systemPackages = [ pkgs.accountsservice ];
+
+ # Accounts daemon looks for dbus interfaces in $XDG_DATA_DIRS/accountsservice
+ environment.pathsToLink = [ "/share/accountsservice" ];
+
+ services.dbus.packages = [ pkgs.accountsservice ];
+
+ systemd.packages = [ pkgs.accountsservice ];
+
+ systemd.services.accounts-daemon = recursiveUpdate {
+
+ wantedBy = [ "graphical.target" ];
+
+ # Accounts daemon looks for dbus interfaces in $XDG_DATA_DIRS/accountsservice
+ environment.XDG_DATA_DIRS = "${config.system.path}/share";
+
+ } (optionalAttrs (!config.users.mutableUsers) {
+ environment.NIXOS_USERS_PURE = "true";
+ });
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/bamf.nix b/nixpkgs/nixos/modules/services/desktops/bamf.nix
new file mode 100644
index 00000000000..0928ee81a64
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/bamf.nix
@@ -0,0 +1,23 @@
+# Bamf
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+
+ options = {
+ services.bamf = {
+ enable = mkEnableOption "bamf";
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.bamf.enable {
+ services.dbus.packages = [ pkgs.bamf ];
+
+ systemd.packages = [ pkgs.bamf ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/blueman.nix b/nixpkgs/nixos/modules/services/desktops/blueman.nix
new file mode 100644
index 00000000000..18ad610247e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/blueman.nix
@@ -0,0 +1,25 @@
+# blueman service
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.blueman;
+in {
+ ###### interface
+ options = {
+ services.blueman = {
+ enable = mkEnableOption "blueman";
+ };
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.blueman ];
+
+ services.dbus.packages = [ pkgs.blueman ];
+
+ systemd.packages = [ pkgs.blueman ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/deepin/deepin.nix b/nixpkgs/nixos/modules/services/desktops/deepin/deepin.nix
new file mode 100644
index 00000000000..931bac58ace
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/deepin/deepin.nix
@@ -0,0 +1,125 @@
+# deepin
+
+{ config, pkgs, lib, ... }:
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.deepin.core.enable = lib.mkEnableOption "
+ Basic dbus and systemd services, groups and users needed by the
+ Deepin Desktop Environment.
+ ";
+
+ services.deepin.deepin-menu.enable = lib.mkEnableOption "
+ DBus service for unified menus in Deepin Desktop Environment.
+ ";
+
+ services.deepin.deepin-turbo.enable = lib.mkEnableOption "
+ Turbo service for the Deepin Desktop Environment. It is a daemon
+ that helps to launch applications faster.
+ ";
+
+ };
+
+
+ ###### implementation
+
+ config = lib.mkMerge [
+
+ (lib.mkIf config.services.deepin.core.enable {
+ environment.systemPackages = [
+ pkgs.deepin.dde-api
+ pkgs.deepin.dde-calendar
+ pkgs.deepin.dde-control-center
+ pkgs.deepin.dde-daemon
+ pkgs.deepin.dde-dock
+ pkgs.deepin.dde-launcher
+ pkgs.deepin.dde-file-manager
+ pkgs.deepin.dde-session-ui
+ pkgs.deepin.deepin-anything
+ pkgs.deepin.deepin-image-viewer
+ pkgs.deepin.deepin-screenshot
+ ];
+
+ services.dbus.packages = [
+ pkgs.deepin.dde-api
+ pkgs.deepin.dde-calendar
+ pkgs.deepin.dde-control-center
+ pkgs.deepin.dde-daemon
+ pkgs.deepin.dde-dock
+ pkgs.deepin.dde-launcher
+ pkgs.deepin.dde-file-manager
+ pkgs.deepin.dde-session-ui
+ pkgs.deepin.deepin-anything
+ pkgs.deepin.deepin-image-viewer
+ pkgs.deepin.deepin-screenshot
+ ];
+
+ systemd.packages = [
+ pkgs.deepin.dde-api
+ pkgs.deepin.dde-daemon
+ pkgs.deepin.dde-file-manager
+ pkgs.deepin.deepin-anything
+ ];
+
+ boot.extraModulePackages = [ config.boot.kernelPackages.deepin-anything ];
+
+ boot.kernelModules = [ "vfs_monitor" ];
+
+ users.groups.deepin-sound-player = { };
+
+ users.users.deepin-sound-player = {
+ description = "Deepin sound player";
+ group = "deepin-sound-player";
+ isSystemUser = true;
+ };
+
+ users.groups.deepin-daemon = { };
+
+ users.users.deepin-daemon = {
+ description = "Deepin daemon user";
+ group = "deepin-daemon";
+ isSystemUser = true;
+ };
+
+ users.groups.deepin_anything_server = { };
+
+ users.users.deepin_anything_server = {
+ description = "Deepin Anything Server";
+ group = "deepin_anything_server";
+ isSystemUser = true;
+ };
+
+ security.pam.services.deepin-auth-keyboard.text = ''
+ # original at ${pkgs.deepin.dde-daemon}/etc/pam.d/deepin-auth-keyboard
+ auth [success=2 default=ignore] pam_lsass.so
+ auth [success=1 default=ignore] pam_unix.so nullok_secure try_first_pass
+ auth requisite pam_deny.so
+ auth required pam_permit.so
+ '';
+
+ environment.etc = {
+ "polkit-1/localauthority/10-vendor.d/com.deepin.api.device.pkla".source = "${pkgs.deepin.dde-api}/etc/polkit-1/localauthority/10-vendor.d/com.deepin.api.device.pkla";
+ "polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Accounts.pkla".source = "${pkgs.deepin.dde-daemon}/etc/polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Accounts.pkla";
+ "polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Grub2.pkla".source = "${pkgs.deepin.dde-daemon}/etc/polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Grub2.pkla";
+ };
+
+ services.deepin.deepin-menu.enable = true;
+ services.deepin.deepin-turbo.enable = true;
+ })
+
+ (lib.mkIf config.services.deepin.deepin-menu.enable {
+ services.dbus.packages = [ pkgs.deepin.deepin-menu ];
+ })
+
+ (lib.mkIf config.services.deepin.deepin-turbo.enable {
+ environment.systemPackages = [ pkgs.deepin.deepin-turbo ];
+ systemd.packages = [ pkgs.deepin.deepin-turbo ];
+ })
+
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/dleyna-renderer.nix b/nixpkgs/nixos/modules/services/desktops/dleyna-renderer.nix
new file mode 100644
index 00000000000..7f88605f627
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/dleyna-renderer.nix
@@ -0,0 +1,28 @@
+# dleyna-renderer service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+ options = {
+ services.dleyna-renderer = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable dleyna-renderer service, a DBus service
+ for handling DLNA renderers.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+ config = mkIf config.services.dleyna-renderer.enable {
+ environment.systemPackages = [ pkgs.dleyna-renderer ];
+
+ services.dbus.packages = [ pkgs.dleyna-renderer ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/dleyna-server.nix b/nixpkgs/nixos/modules/services/desktops/dleyna-server.nix
new file mode 100644
index 00000000000..9a131a5e700
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/dleyna-server.nix
@@ -0,0 +1,28 @@
+# dleyna-server service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+ options = {
+ services.dleyna-server = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable dleyna-server service, a DBus service
+ for handling DLNA servers.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+ config = mkIf config.services.dleyna-server.enable {
+ environment.systemPackages = [ pkgs.dleyna-server ];
+
+ services.dbus.packages = [ pkgs.dleyna-server ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/flatpak.nix b/nixpkgs/nixos/modules/services/desktops/flatpak.nix
new file mode 100644
index 00000000000..7fb0024f37d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/flatpak.nix
@@ -0,0 +1,53 @@
+# flatpak service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.flatpak;
+in {
+ meta = {
+ doc = ./flatpak.xml;
+ maintainers = pkgs.flatpak.meta.maintainers;
+ };
+
+ ###### interface
+ options = {
+ services.flatpak = {
+ enable = mkEnableOption "flatpak";
+ };
+ };
+
+
+ ###### implementation
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = (config.xdg.portal.enable == true);
+ message = "To use Flatpak you must enable XDG Desktop Portals with xdg.portal.enable.";
+ }
+ ];
+
+ environment.systemPackages = [ pkgs.flatpak ];
+
+ services.dbus.packages = [ pkgs.flatpak ];
+
+ systemd.packages = [ pkgs.flatpak ];
+
+ environment.profiles = [
+ "$HOME/.local/share/flatpak/exports"
+ "/var/lib/flatpak/exports"
+ ];
+
+ # It has been possible since https://github.com/flatpak/flatpak/releases/tag/1.3.2
+ # to build a SELinux policy module.
+
+ users.users.flatpak = {
+ description = "Flatpak system helper";
+ group = "flatpak";
+ isSystemUser = true;
+ };
+
+ users.groups.flatpak = { };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/flatpak.xml b/nixpkgs/nixos/modules/services/desktops/flatpak.xml
new file mode 100644
index 00000000000..8f080b25022
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/flatpak.xml
@@ -0,0 +1,56 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="module-services-flatpak">
+ <title>Flatpak</title>
+ <para>
+ <emphasis>Source:</emphasis>
+ <filename>modules/services/desktop/flatpak.nix</filename>
+ </para>
+ <para>
+ <emphasis>Upstream documentation:</emphasis>
+ <link xlink:href="https://github.com/flatpak/flatpak/wiki"/>
+ </para>
+ <para>
+ Flatpak is a system for building, distributing, and running sandboxed desktop
+ applications on Linux.
+ </para>
+ <para>
+ To enable Flatpak, add the following to your
+ <filename>configuration.nix</filename>:
+<programlisting>
+ <xref linkend="opt-services.flatpak.enable"/> = true;
+</programlisting>
+ </para>
+ <para>
+ For the sandboxed apps to work correctly, desktop integration portals need to
+ be installed. If you run GNOME, this will be handled automatically for you;
+ in other cases, you will need to add something like the following to your
+ <filename>configuration.nix</filename>:
+<programlisting>
+ <xref linkend="opt-xdg.portal.extraPortals"/> = [ pkgs.xdg-desktop-portal-gtk ];
+</programlisting>
+ </para>
+ <para>
+ Then, you will need to add a repository, for example,
+ <link xlink:href="https://github.com/flatpak/flatpak/wiki">Flathub</link>,
+ either using the following commands:
+<screen>
+<prompt>$ </prompt>flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
+<prompt>$ </prompt>flatpak update
+</screen>
+ or by opening the
+ <link xlink:href="https://flathub.org/repo/flathub.flatpakrepo">repository
+ file</link> in GNOME Software.
+ </para>
+ <para>
+ Finally, you can search and install programs:
+<screen>
+<prompt>$ </prompt>flatpak search bustle
+<prompt>$ </prompt>flatpak install flathub org.freedesktop.Bustle
+<prompt>$ </prompt>flatpak run org.freedesktop.Bustle
+</screen>
+ Again, GNOME Software offers graphical interface for these tasks.
+ </para>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/desktops/geoclue2.nix b/nixpkgs/nixos/modules/services/desktops/geoclue2.nix
new file mode 100644
index 00000000000..6007dddf50c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/geoclue2.nix
@@ -0,0 +1,259 @@
+# GeoClue 2 daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ # the demo agent isn't built by default, but we need it here
+ package = pkgs.geoclue2.override { withDemoAgent = config.services.geoclue2.enableDemoAgent; };
+
+ cfg = config.services.geoclue2;
+
+ defaultWhitelist = [ "gnome-shell" "io.elementary.desktop.agent-geoclue2" ];
+
+ appConfigModule = types.submodule ({ name, ... }: {
+ options = {
+ desktopID = mkOption {
+ type = types.str;
+ description = "Desktop ID of the application.";
+ };
+
+ isAllowed = mkOption {
+ type = types.bool;
+ default = null;
+ description = ''
+ Whether the application will be allowed access to location information.
+ '';
+ };
+
+ isSystem = mkOption {
+ type = types.bool;
+ default = null;
+ description = ''
+ Whether the application is a system component or not.
+ '';
+ };
+
+ users = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ List of UIDs of all users for which this application is allowed location
+ info access, Defaults to an empty string to allow it for all users.
+ '';
+ };
+ };
+
+ config.desktopID = mkDefault name;
+ });
+
+ appConfigToINICompatible = _: { desktopID, isAllowed, isSystem, users, ... }: {
+ name = desktopID;
+ value = {
+ allowed = isAllowed;
+ system = isSystem;
+ users = concatStringsSep ";" users;
+ };
+ };
+
+in
+{
+
+ ###### interface
+
+ options = {
+
+ services.geoclue2 = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable GeoClue 2 daemon, a DBus service
+ that provides location information for accessing.
+ '';
+ };
+
+ enableDemoAgent = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to use the GeoClue demo agent. This should be
+ overridden by desktop environments that provide their own
+ agent.
+ '';
+ };
+
+ enableNmea = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to fetch location from NMEA sources on local network.
+ '';
+ };
+
+ enable3G = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable 3G source.
+ '';
+ };
+
+ enableCDMA = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable CDMA source.
+ '';
+ };
+
+ enableModemGPS = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable Modem-GPS source.
+ '';
+ };
+
+ enableWifi = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable WiFi source.
+ '';
+ };
+
+ geoProviderUrl = mkOption {
+ type = types.str;
+ default = "https://location.services.mozilla.com/v1/geolocate?key=geoclue";
+ example = "https://www.googleapis.com/geolocation/v1/geolocate?key=YOUR_KEY";
+ description = ''
+ The url to the wifi GeoLocation Service.
+ '';
+ };
+
+ submitData = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to submit data to a GeoLocation Service.
+ '';
+ };
+
+ submissionUrl = mkOption {
+ type = types.str;
+ default = "https://location.services.mozilla.com/v1/submit?key=geoclue";
+ description = ''
+ The url to submit data to a GeoLocation Service.
+ '';
+ };
+
+ submissionNick = mkOption {
+ type = types.str;
+ default = "geoclue";
+ description = ''
+ A nickname to submit network data with.
+ Must be 2-32 characters long.
+ '';
+ };
+
+ appConfig = mkOption {
+ type = types.loaOf appConfigModule;
+ default = {};
+ example = literalExample ''
+ "com.github.app" = {
+ isAllowed = true;
+ isSystem = true;
+ users = [ "300" ];
+ };
+ '';
+ description = ''
+ Specify extra settings per application.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ package ];
+
+ services.dbus.packages = [ package ];
+
+ systemd.packages = [ package ];
+
+ users.users.geoclue = {
+ isSystemUser = true;
+ home = "/var/lib/geoclue";
+ group = "geoclue";
+ description = "Geoinformation service";
+ };
+
+ users.groups.geoclue = {};
+
+ systemd.tmpfiles.rules = [
+ "d /var/lib/geoclue 0755 geoclue geoclue"
+ ];
+
+ # restart geoclue service when the configuration changes
+ systemd.services.geoclue.restartTriggers = [
+ config.environment.etc."geoclue/geoclue.conf".source
+ ];
+
+ # this needs to run as a user service, since it's associated with the
+ # user who is making the requests
+ systemd.user.services = mkIf cfg.enableDemoAgent {
+ geoclue-agent = {
+ description = "Geoclue agent";
+ script = "${package}/libexec/geoclue-2.0/demos/agent";
+ # this should really be `partOf = [ "geoclue.service" ]`, but
+ # we can't be part of a system service, and the agent should
+ # be okay with the main service coming and going
+ wantedBy = [ "default.target" ];
+ };
+ };
+
+ services.geoclue2.appConfig.epiphany = {
+ isAllowed = true;
+ isSystem = false;
+ };
+
+ services.geoclue2.appConfig.firefox = {
+ isAllowed = true;
+ isSystem = false;
+ };
+
+ environment.etc."geoclue/geoclue.conf".text =
+ generators.toINI {} ({
+ agent = {
+ whitelist = concatStringsSep ";"
+ (optional cfg.enableDemoAgent "geoclue-demo-agent" ++ defaultWhitelist);
+ };
+ network-nmea = {
+ enable = cfg.enableNmea;
+ };
+ "3g" = {
+ enable = cfg.enable3G;
+ };
+ cdma = {
+ enable = cfg.enableCDMA;
+ };
+ modem-gps = {
+ enable = cfg.enableModemGPS;
+ };
+ wifi = {
+ enable = cfg.enableWifi;
+ url = cfg.geoProviderUrl;
+ submit-data = boolToString cfg.submitData;
+ submission-url = cfg.submissionUrl;
+ submission-nick = cfg.submissionNick;
+ };
+ } // mapAttrs' appConfigToINICompatible cfg.appConfig);
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/at-spi2-core.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/at-spi2-core.nix
new file mode 100644
index 00000000000..cca98c43dc7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/at-spi2-core.nix
@@ -0,0 +1,42 @@
+# at-spi2-core daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.at-spi2-core = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable at-spi2-core, a service for the Assistive Technologies
+ available on the GNOME platform.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkMerge [
+ (mkIf config.services.gnome3.at-spi2-core.enable {
+ environment.systemPackages = [ pkgs.at-spi2-core ];
+ services.dbus.packages = [ pkgs.at-spi2-core ];
+ systemd.packages = [ pkgs.at-spi2-core ];
+ })
+
+ (mkIf (!config.services.gnome3.at-spi2-core.enable) {
+ environment.variables.NO_AT_BRIDGE = "1";
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix
new file mode 100644
index 00000000000..3d2b3ed85e3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix
@@ -0,0 +1,29 @@
+# Chrome GNOME Shell native host connector.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+ options = {
+ services.gnome3.chrome-gnome-shell.enable = mkEnableOption ''
+ Chrome GNOME Shell native host connector, a DBus service
+ allowing to install GNOME Shell extensions from a web browser.
+ '';
+ };
+
+
+ ###### implementation
+ config = mkIf config.services.gnome3.chrome-gnome-shell.enable {
+ environment.etc = {
+ "chromium/native-messaging-hosts/org.gnome.chrome_gnome_shell.json".source = "${pkgs.chrome-gnome-shell}/etc/chromium/native-messaging-hosts/org.gnome.chrome_gnome_shell.json";
+ "opt/chrome/native-messaging-hosts/org.gnome.chrome_gnome_shell.json".source = "${pkgs.chrome-gnome-shell}/etc/opt/chrome/native-messaging-hosts/org.gnome.chrome_gnome_shell.json";
+ };
+
+ environment.systemPackages = [ pkgs.chrome-gnome-shell ];
+
+ services.dbus.packages = [ pkgs.chrome-gnome-shell ];
+
+ nixpkgs.config.firefox.enableGnomeExtensions = true;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/evolution-data-server.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/evolution-data-server.nix
new file mode 100644
index 00000000000..7e312a1b81e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/evolution-data-server.nix
@@ -0,0 +1,41 @@
+# Evolution Data Server daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.evolution-data-server = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Evolution Data Server, a collection of services for
+ storing addressbooks and calendars.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.gnome3.evolution-data-server.enable {
+
+ environment.systemPackages = [ pkgs.gnome3.evolution-data-server ];
+
+ services.dbus.packages = [ pkgs.gnome3.evolution-data-server ];
+
+ systemd.packages = [ pkgs.gnome3.evolution-data-server ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/glib-networking.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/glib-networking.nix
new file mode 100644
index 00000000000..fcd58509d6f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/glib-networking.nix
@@ -0,0 +1,33 @@
+# GLib Networking
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.glib-networking = {
+
+ enable = mkEnableOption "network extensions for GLib";
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.gnome3.glib-networking.enable {
+
+ services.dbus.packages = [ pkgs.glib-networking ];
+
+ systemd.packages = [ pkgs.glib-networking ];
+
+ environment.variables.GIO_EXTRA_MODULES = [ "${pkgs.glib-networking.out}/lib/gio/modules" ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-keyring.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
new file mode 100644
index 00000000000..db60445ef77
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
@@ -0,0 +1,47 @@
+# GNOME Keyring daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.gnome-keyring = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable GNOME Keyring daemon, a service designed to
+ take care of the user's security credentials,
+ such as user names and passwords.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.gnome3.gnome-keyring.enable {
+
+ environment.systemPackages = [ pkgs.gnome3.gnome-keyring ];
+
+ services.dbus.packages = [ pkgs.gnome3.gnome-keyring pkgs.gcr ];
+
+ security.pam.services.login.enableGnomeKeyring = true;
+
+ security.wrappers.gnome-keyring-daemon = {
+ source = "${pkgs.gnome3.gnome-keyring}/bin/gnome-keyring-daemon";
+ capabilities = "cap_ipc_lock=ep";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix
new file mode 100644
index 00000000000..748a025414a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix
@@ -0,0 +1,39 @@
+# GNOME Online Accounts daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.gnome-online-accounts = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable GNOME Online Accounts daemon, a service that provides
+ a single sign-on framework for the GNOME desktop.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.gnome3.gnome-online-accounts.enable {
+
+ environment.systemPackages = [ pkgs.gnome-online-accounts ];
+
+ services.dbus.packages = [ pkgs.gnome-online-accounts ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix
new file mode 100644
index 00000000000..d406bf6f5e3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix
@@ -0,0 +1,39 @@
+# GNOME Online Miners daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.gnome-online-miners = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable GNOME Online Miners, a service that
+ crawls through your online content.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.gnome3.gnome-online-miners.enable {
+
+ environment.systemPackages = [ pkgs.gnome3.gnome-online-miners ];
+
+ services.dbus.packages = [ pkgs.gnome3.gnome-online-miners ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix
new file mode 100644
index 00000000000..021f4f9534b
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix
new file mode 100644
index 00000000000..7f7adcf26ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix
@@ -0,0 +1,45 @@
+# GNOME Settings Daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.gnome3.gnome-settings-daemon;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.gnome-settings-daemon = {
+
+ enable = mkEnableOption "GNOME Settings Daemon";
+
+ # There are many forks of gnome-settings-daemon
+ package = mkOption {
+ type = types.package;
+ default = pkgs.gnome3.gnome-settings-daemon;
+ description = "Which gnome-settings-daemon package to use.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ services.udev.packages = [ cfg.package ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-user-share.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-user-share.nix
new file mode 100644
index 00000000000..f8396287770
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-user-share.nix
@@ -0,0 +1,36 @@
+# GNOME User Share daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.gnome-user-share = {
+
+ enable = mkEnableOption "GNOME User Share, a user-level file sharing service for GNOME";
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.gnome3.gnome-user-share.enable {
+
+ environment.systemPackages = [
+ pkgs.gnome3.gnome-user-share
+ ];
+
+ systemd.packages = [
+ pkgs.gnome3.gnome-user-share
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/rygel.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/rygel.nix
new file mode 100644
index 00000000000..55d5e703aa1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/rygel.nix
@@ -0,0 +1,30 @@
+# rygel service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+ options = {
+ services.gnome3.rygel = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable Rygel UPnP Mediaserver.
+
+ You will need to also allow UPnP connections in firewall, see the following <link xlink:href="https://github.com/NixOS/nixpkgs/pull/45045#issuecomment-416030795">comment</link>.
+ '';
+ type = types.bool;
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkIf config.services.gnome3.rygel.enable {
+ environment.systemPackages = [ pkgs.gnome3.rygel ];
+
+ services.dbus.packages = [ pkgs.gnome3.rygel ];
+
+ systemd.packages = [ pkgs.gnome3.rygel ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/sushi.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/sushi.nix
new file mode 100644
index 00000000000..7a4389038b2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/sushi.nix
@@ -0,0 +1,38 @@
+# GNOME Sushi daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.sushi = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Sushi, a quick previewer for nautilus.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.gnome3.sushi.enable {
+
+ environment.systemPackages = [ pkgs.gnome3.sushi ];
+
+ services.dbus.packages = [ pkgs.gnome3.sushi ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/tracker-miners.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/tracker-miners.nix
new file mode 100644
index 00000000000..b390d8368c6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/tracker-miners.nix
@@ -0,0 +1,41 @@
+# Tracker Miners daemons.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.tracker-miners = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Tracker miners, indexing services for Tracker
+ search engine and metadata storage system.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.gnome3.tracker-miners.enable {
+
+ environment.systemPackages = [ pkgs.tracker-miners ];
+
+ services.dbus.packages = [ pkgs.tracker-miners ];
+
+ systemd.packages = [ pkgs.tracker-miners ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/tracker.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/tracker.nix
new file mode 100644
index 00000000000..2e829274226
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/tracker.nix
@@ -0,0 +1,41 @@
+# Tracker daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnome3.tracker = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Tracker services, a search engine,
+ search tool and metadata storage system.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.gnome3.tracker.enable {
+
+ environment.systemPackages = [ pkgs.tracker ];
+
+ services.dbus.packages = [ pkgs.tracker ];
+
+ systemd.packages = [ pkgs.tracker ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gsignond.nix b/nixpkgs/nixos/modules/services/desktops/gsignond.nix
new file mode 100644
index 00000000000..5ab9add9f32
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gsignond.nix
@@ -0,0 +1,45 @@
+# Accounts-SSO gSignOn daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ package = pkgs.gsignond.override { plugins = config.services.gsignond.plugins; };
+in
+{
+
+ meta.maintainers = pkgs.pantheon.maintainers;
+
+ ###### interface
+
+ options = {
+
+ services.gsignond = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable gSignOn daemon, a DBus service
+ which performs user authentication on behalf of its clients.
+ '';
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = ''
+ What plugins to use with the gSignOn daemon.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkIf config.services.gsignond.enable {
+ environment.etc."gsignond.conf".source = "${package}/etc/gsignond.conf";
+ services.dbus.packages = [ package ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gvfs.nix b/nixpkgs/nixos/modules/services/desktops/gvfs.nix
new file mode 100644
index 00000000000..1d002eac41d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gvfs.nix
@@ -0,0 +1,59 @@
+# GVfs
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.gvfs;
+
+in
+
+{
+
+ # Added 2019-08-19
+ imports = [
+ (mkRenamedOptionModule
+ [ "services" "gnome3" "gvfs" "enable" ]
+ [ "services" "gvfs" "enable" ])
+ ];
+
+ ###### interface
+
+ options = {
+
+ services.gvfs = {
+
+ enable = mkEnableOption "GVfs, a userspace virtual filesystem";
+
+ # gvfs can be built with multiple configurations
+ package = mkOption {
+ type = types.package;
+ default = pkgs.gnome3.gvfs;
+ description = "Which GVfs package to use.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ services.dbus.packages = [ cfg.package ];
+
+ systemd.packages = [ cfg.package ];
+
+ services.udev.packages = [ pkgs.libmtp.bin ];
+
+ # Needed for unwrapped applications
+ environment.variables.GIO_EXTRA_MODULES = [ "${cfg.package}/lib/gio/modules" ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/pantheon/contractor.nix b/nixpkgs/nixos/modules/services/desktops/pantheon/contractor.nix
new file mode 100644
index 00000000000..2638a21df73
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/pantheon/contractor.nix
@@ -0,0 +1,41 @@
+# Contractor
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ meta.maintainers = pkgs.pantheon.maintainers;
+
+ ###### interface
+
+ options = {
+
+ services.pantheon.contractor = {
+
+ enable = mkEnableOption "contractor, a desktop-wide extension service used by pantheon";
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.pantheon.contractor.enable {
+
+ environment.systemPackages = with pkgs.pantheon; [
+ contractor
+ extra-elementary-contracts
+ ];
+
+ services.dbus.packages = [ pkgs.pantheon.contractor ];
+
+ environment.pathsToLink = [
+ "/share/contractor"
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/pantheon/files.nix b/nixpkgs/nixos/modules/services/desktops/pantheon/files.nix
new file mode 100644
index 00000000000..577aad6c298
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/pantheon/files.nix
@@ -0,0 +1,38 @@
+# pantheon files daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ meta.maintainers = pkgs.pantheon.maintainers;
+
+ ###### interface
+
+ options = {
+
+ services.pantheon.files = {
+
+ enable = mkEnableOption "pantheon files daemon";
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.pantheon.files.enable {
+
+ environment.systemPackages = [
+ pkgs.pantheon.elementary-files
+ ];
+
+ services.dbus.packages = [
+ pkgs.pantheon.elementary-files
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire.nix b/nixpkgs/nixos/modules/services/desktops/pipewire.nix
new file mode 100644
index 00000000000..13f3d61e84c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire.nix
@@ -0,0 +1,37 @@
+# pipewire service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.pipewire;
+ packages = with pkgs; [ pipewire ];
+
+in {
+ ###### interface
+ options = {
+ services.pipewire = {
+ enable = mkEnableOption "pipewire service";
+
+ socketActivation = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Automatically run pipewire when connections are made to the pipewire socket.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ environment.systemPackages = packages;
+
+ systemd.packages = packages;
+
+ systemd.user.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
+ };
+
+ meta.maintainers = with lib.maintainers; [ jtojnar ];
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/profile-sync-daemon.nix b/nixpkgs/nixos/modules/services/desktops/profile-sync-daemon.nix
new file mode 100644
index 00000000000..a8ac22ac127
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/profile-sync-daemon.nix
@@ -0,0 +1,77 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.psd;
+in {
+ options.services.psd = with types; {
+ enable = mkOption {
+ type = bool;
+ default = false;
+ description = ''
+ Whether to enable the Profile Sync daemon.
+ '';
+ };
+ resyncTimer = mkOption {
+ type = str;
+ default = "1h";
+ example = "1h 30min";
+ description = ''
+ The amount of time to wait before syncing browser profiles back to the
+ disk.
+
+ Takes a systemd.unit time span. The time unit defaults to seconds if
+ omitted.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd = {
+ user = {
+ services = {
+ psd = {
+ enable = true;
+ description = "Profile Sync daemon";
+ wants = [ "psd-resync.service" ];
+ wantedBy = [ "default.target" ];
+ path = with pkgs; [ rsync kmod gawk nettools utillinux profile-sync-daemon ];
+ unitConfig = {
+ RequiresMountsFor = [ "/home/" ];
+ };
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = "yes";
+ ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon sync";
+ ExecStop = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon unsync";
+ };
+ };
+
+ psd-resync = {
+ enable = true;
+ description = "Timed profile resync";
+ after = [ "psd.service" ];
+ wants = [ "psd-resync.timer" ];
+ partOf = [ "psd.service" ];
+ wantedBy = [ "default.target" ];
+ path = with pkgs; [ rsync kmod gawk nettools utillinux profile-sync-daemon ];
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon resync";
+ };
+ };
+ };
+
+ timers.psd-resync = {
+ description = "Timer for profile sync daemon - ${cfg.resyncTimer}";
+ partOf = [ "psd-resync.service" "psd.service" ];
+
+ timerConfig = {
+ OnUnitActiveSec = "${cfg.resyncTimer}";
+ };
+ };
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/system-config-printer.nix b/nixpkgs/nixos/modules/services/desktops/system-config-printer.nix
new file mode 100644
index 00000000000..8a80be266b2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/system-config-printer.nix
@@ -0,0 +1,38 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.system-config-printer = {
+
+ enable = mkEnableOption "system-config-printer, a service for CUPS administration used by printing interfaces";
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.system-config-printer.enable {
+
+ services.dbus.packages = [
+ pkgs.system-config-printer
+ ];
+
+ systemd.packages = [
+ pkgs.system-config-printer
+ ];
+
+ services.udev.packages = [
+ pkgs.system-config-printer
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/telepathy.nix b/nixpkgs/nixos/modules/services/desktops/telepathy.nix
new file mode 100644
index 00000000000..f5401c18098
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/telepathy.nix
@@ -0,0 +1,39 @@
+# Telepathy daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.telepathy = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Telepathy service, a communications framework
+ that enables real-time communication via pluggable protocol backends.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.telepathy.enable {
+
+ environment.systemPackages = [ pkgs.telepathy-mission-control ];
+
+ services.dbus.packages = [ pkgs.telepathy-mission-control ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/tumbler.nix b/nixpkgs/nixos/modules/services/desktops/tumbler.nix
new file mode 100644
index 00000000000..d18088d4634
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/tumbler.nix
@@ -0,0 +1,50 @@
+# Tumbler
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.tumbler;
+ tumbler = cfg.package;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.tumbler = {
+
+ enable = mkEnableOption "Tumbler, A D-Bus thumbnailer service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.xfce4-14.tumbler;
+ description = "Which tumbler package to use";
+ example = pkgs.xfce4-12.tumbler;
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [
+ tumbler
+ ];
+
+ services.dbus.packages = [
+ tumbler
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/zeitgeist.nix b/nixpkgs/nixos/modules/services/desktops/zeitgeist.nix
new file mode 100644
index 00000000000..20c82ccdd56
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/zeitgeist.nix
@@ -0,0 +1,26 @@
+# Zeitgeist
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+
+ options = {
+ services.zeitgeist = {
+ enable = mkEnableOption "zeitgeist";
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.zeitgeist.enable {
+
+ environment.systemPackages = [ pkgs.zeitgeist ];
+
+ services.dbus.packages = [ pkgs.zeitgeist ];
+
+ systemd.packages = [ pkgs.zeitgeist ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/development/bloop.nix b/nixpkgs/nixos/modules/services/development/bloop.nix
new file mode 100644
index 00000000000..226718a9e80
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/bloop.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.bloop;
+
+in {
+
+ options.services.bloop = {
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [
+ "-J-Xmx2G"
+ "-J-XX:MaxInlineLevel=20"
+ "-J-XX:+UseParallelGC"
+ ];
+ description = ''
+ Specifies additional command line argument to pass to bloop
+ java process.
+ '';
+ };
+
+ install = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to install a user service for the Bloop server.
+
+ The service must be manually started for each user with
+ "systemctl --user start bloop".
+ '';
+ };
+ };
+
+ config = mkIf (cfg.install) {
+ systemd.user.services.bloop = {
+ description = "Bloop Scala build server";
+
+ environment = {
+ PATH = mkForce "${makeBinPath [ config.programs.java.package ]}";
+ };
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = ''${pkgs.bloop}/bin/bloop server'';
+ Restart = "always";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.bloop ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/development/hoogle.nix b/nixpkgs/nixos/modules/services/development/hoogle.nix
new file mode 100644
index 00000000000..1a98f005602
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/hoogle.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.hoogle;
+
+ hoogleEnv = pkgs.buildEnv {
+ name = "hoogle";
+ paths = [ (cfg.haskellPackages.ghcWithHoogle cfg.packages) ];
+ };
+
+in {
+
+ options.services.hoogle = {
+ enable = mkEnableOption "Haskell documentation server";
+
+ port = mkOption {
+ type = types.int;
+ default = 8080;
+ description = ''
+ Port number Hoogle will be listening to.
+ '';
+ };
+
+ packages = mkOption {
+ default = hp: [];
+ defaultText = "hp: []";
+ example = "hp: with hp; [ text lens ]";
+ description = ''
+ The Haskell packages to generate documentation for.
+
+ The option value is a function that takes the package set specified in
+ the <varname>haskellPackages</varname> option as its sole parameter and
+ returns a list of packages.
+ '';
+ };
+
+ haskellPackages = mkOption {
+ description = "Which haskell package set to use.";
+ default = pkgs.haskellPackages;
+ defaultText = "pkgs.haskellPackages";
+ };
+
+ home = mkOption {
+ type = types.str;
+ description = "Url for hoogle logo";
+ default = "https://hoogle.haskell.org";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.hoogle = {
+ description = "Haskell documentation server";
+
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Restart = "always";
+ ExecStart = ''${hoogleEnv}/bin/hoogle server --local --port ${toString cfg.port} --home ${cfg.home}'';
+
+ User = "nobody";
+ Group = "nogroup";
+
+ PrivateTmp = true;
+ ProtectHome = true;
+
+ RuntimeDirectory = "hoogle";
+ WorkingDirectory = "%t/hoogle";
+ };
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/development/jupyter/default.nix b/nixpkgs/nixos/modules/services/development/jupyter/default.nix
new file mode 100644
index 00000000000..f20860af6e1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/jupyter/default.nix
@@ -0,0 +1,185 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.jupyter;
+
+ # NOTE: We don't use top-level jupyter because we don't
+ # want to pass in JUPYTER_PATH but use .environment instead,
+ # saving a rebuild.
+ package = pkgs.python3.pkgs.notebook;
+
+ kernels = (pkgs.jupyter-kernel.create {
+ definitions = if cfg.kernels != null
+ then cfg.kernels
+ else pkgs.jupyter-kernel.default;
+ });
+
+ notebookConfig = pkgs.writeText "jupyter_config.py" ''
+ ${cfg.notebookConfig}
+
+ c.NotebookApp.password = ${cfg.password}
+ '';
+
+in {
+ meta.maintainers = with maintainers; [ aborsu ];
+
+ options.services.jupyter = {
+ enable = mkEnableOption "Jupyter development server";
+
+ ip = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = ''
+ IP address Jupyter will be listening on.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8888;
+ description = ''
+ Port number Jupyter will be listening on.
+ '';
+ };
+
+ notebookDir = mkOption {
+ type = types.str;
+ default = "~/";
+ description = ''
+ Root directory for notebooks.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "jupyter";
+ description = ''
+ Name of the user used to run the jupyter service.
+ For security reason, jupyter should really not be run as root.
+ If not set (jupyter), the service will create a jupyter user with appropriate settings.
+ '';
+ example = "aborsu";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "jupyter";
+ description = ''
+ Name of the group used to run the jupyter service.
+ Use this if you want to create a group of users that are able to view the notebook directory's content.
+ '';
+ example = "users";
+ };
+
+ password = mkOption {
+ type = types.str;
+ description = ''
+ Password to use with notebook.
+ Can be generated using:
+ In [1]: from notebook.auth import passwd
+ In [2]: passwd('test')
+ Out[2]: 'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'
+ NOTE: you need to keep the single quote inside the nix string.
+ Or you can use a python oneliner:
+ "open('/path/secret_file', 'r', encoding='utf8').read().strip()"
+ It will be interpreted at the end of the notebookConfig.
+ '';
+ example = [
+ "'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'"
+ "open('/path/secret_file', 'r', encoding='utf8').read().strip()"
+ ];
+ };
+
+ notebookConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Raw jupyter config.
+ '';
+ };
+
+ kernels = mkOption {
+ type = types.nullOr (types.attrsOf(types.submodule (import ./kernel-options.nix {
+ inherit lib;
+ })));
+
+ default = null;
+ example = literalExample ''
+ {
+ python3 = let
+ env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
+ ipykernel
+ pandas
+ scikitlearn
+ ]));
+ in {
+ displayName = "Python 3 for machine learning";
+ argv = [
+ "$ {env.interpreter}"
+ "-m"
+ "ipykernel_launcher"
+ "-f"
+ "{connection_file}"
+ ];
+ language = "python";
+ logo32 = "$ {env.sitePackages}/ipykernel/resources/logo-32x32.png";
+ logo64 = "$ {env.sitePackages}/ipykernel/resources/logo-64x64.png";
+ };
+ }
+ '';
+ description = "Declarative kernel config
+
+ Kernels can be declared in any language that supports and has the required
+ dependencies to communicate with a jupyter server.
+ In python's case, it means that ipykernel package must always be included in
+ the list of packages of the targeted environment.
+ ";
+ };
+ };
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ systemd.services.jupyter = {
+ description = "Jupyter development server";
+
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ # TODO: Patch notebook so we can explicitly pass in a shell
+ path = [ pkgs.bash ]; # needed for sh in cell magic to work
+
+ environment = {
+ JUPYTER_PATH = toString kernels;
+ };
+
+ serviceConfig = {
+ Restart = "always";
+ ExecStart = ''${package}/bin/jupyter-notebook \
+ --no-browser \
+ --ip=${cfg.ip} \
+ --port=${toString cfg.port} --port-retries 0 \
+ --notebook-dir=${cfg.notebookDir} \
+ --NotebookApp.config_file=${notebookConfig}
+ '';
+ User = cfg.user;
+ Group = cfg.group;
+ WorkingDirectory = "~";
+ };
+ };
+ })
+ (mkIf (cfg.enable && (cfg.group == "jupyter")) {
+ users.groups.jupyter = {};
+ })
+ (mkIf (cfg.enable && (cfg.user == "jupyter")) {
+ users.extraUsers.jupyter = {
+ extraGroups = [ cfg.group ];
+ home = "/var/lib/jupyter";
+ createHome = true;
+ useDefaultShell = true; # needed so that the user can start a terminal.
+ };
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix b/nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix
new file mode 100644
index 00000000000..03547637449
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix
@@ -0,0 +1,60 @@
+# Options that can be used for creating a jupyter kernel.
+{lib }:
+
+with lib;
+
+{
+ options = {
+
+ displayName = mkOption {
+ type = types.str;
+ default = "";
+ example = [
+ "Python 3"
+ "Python 3 for Data Science"
+ ];
+ description = ''
+ Name that will be shown to the user.
+ '';
+ };
+
+ argv = mkOption {
+ type = types.listOf types.str;
+ example = [
+ "{customEnv.interpreter}"
+ "-m"
+ "ipykernel_launcher"
+ "-f"
+ "{connection_file}"
+ ];
+ description = ''
+ Command and arguments to start the kernel.
+ '';
+ };
+
+ language = mkOption {
+ type = types.str;
+ example = "python";
+ description = ''
+ Language of the environment. Typically the name of the binary.
+ '';
+ };
+
+ logo32 = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "{env.sitePackages}/ipykernel/resources/logo-32x32.png";
+ description = ''
+ Path to 32x32 logo png.
+ '';
+ };
+ logo64 = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "{env.sitePackages}/ipykernel/resources/logo-64x64.png";
+ description = ''
+ Path to 64x64 logo png.
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/editors/emacs.nix b/nixpkgs/nixos/modules/services/editors/emacs.nix
new file mode 100644
index 00000000000..d791b387665
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/editors/emacs.nix
@@ -0,0 +1,102 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.emacs;
+
+ editorScript = pkgs.writeScriptBin "emacseditor" ''
+ #!${pkgs.runtimeShell}
+ if [ -z "$1" ]; then
+ exec ${cfg.package}/bin/emacsclient --create-frame --alternate-editor ${cfg.package}/bin/emacs
+ else
+ exec ${cfg.package}/bin/emacsclient --alternate-editor ${cfg.package}/bin/emacs "$@"
+ fi
+ '';
+
+desktopApplicationFile = pkgs.writeTextFile {
+ name = "emacsclient.desktop";
+ destination = "/share/applications/emacsclient.desktop";
+ text = ''
+[Desktop Entry]
+Name=Emacsclient
+GenericName=Text Editor
+Comment=Edit text
+MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
+Exec=emacseditor %F
+Icon=emacs
+Type=Application
+Terminal=false
+Categories=Development;TextEditor;
+StartupWMClass=Emacs
+Keywords=Text;Editor;
+'';
+};
+
+in {
+
+ options.services.emacs = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable a user service for the Emacs daemon. Use <literal>emacsclient</literal> to connect to the
+ daemon. If <literal>true</literal>, <varname>services.emacs.install</varname> is
+ considered <literal>true</literal>, whatever its value.
+ '';
+ };
+
+ install = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to install a user service for the Emacs daemon. Once
+ the service is started, use emacsclient to connect to the
+ daemon.
+
+ The service must be manually started for each user with
+ "systemctl --user start emacs" or globally through
+ <varname>services.emacs.enable</varname>.
+ '';
+ };
+
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.emacs;
+ defaultText = "pkgs.emacs";
+ description = ''
+ emacs derivation to use.
+ '';
+ };
+
+ defaultEditor = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ When enabled, configures emacsclient to be the default editor
+ using the EDITOR environment variable.
+ '';
+ };
+ };
+
+ config = mkIf (cfg.enable || cfg.install) {
+ systemd.user.services.emacs = {
+ description = "Emacs: the extensible, self-documenting text editor";
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.bash}/bin/bash -c 'source ${config.system.build.setEnvironment}; exec ${cfg.package}/bin/emacs --daemon'";
+ ExecStop = "${cfg.package}/bin/emacsclient --eval (kill-emacs)";
+ Restart = "always";
+ };
+ } // optionalAttrs cfg.enable { wantedBy = [ "default.target" ]; };
+
+ environment.systemPackages = [ cfg.package editorScript desktopApplicationFile ];
+
+ environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "${editorScript}/bin/emacseditor");
+ };
+
+ meta.doc = ./emacs.xml;
+}
diff --git a/nixpkgs/nixos/modules/services/editors/emacs.xml b/nixpkgs/nixos/modules/services/editors/emacs.xml
new file mode 100644
index 00000000000..03483f69fa2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/editors/emacs.xml
@@ -0,0 +1,580 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="module-services-emacs">
+ <title>Emacs</title>
+<!--
+ Documentation contributors:
+ Damien Cassou @DamienCassou
+ Thomas Tuegel @ttuegel
+ Rodney Lorrimar @rvl
+ Adam Hoese @adisbladis
+ -->
+ <para>
+ <link xlink:href="https://www.gnu.org/software/emacs/">Emacs</link> is an
+ extensible, customizable, self-documenting real-time display editor — and
+ more. At its core is an interpreter for Emacs Lisp, a dialect of the Lisp
+ programming language with extensions to support text editing.
+ </para>
+ <para>
+ Emacs runs within a graphical desktop environment using the X Window System,
+ but works equally well on a text terminal. Under
+ <productname>macOS</productname>, a "Mac port" edition is available, which
+ uses Apple's native GUI frameworks.
+ </para>
+ <para>
+ <productname>Nixpkgs</productname> provides a superior environment for
+ running <application>Emacs</application>. It's simple to create custom builds
+ by overriding the default packages. Chaotic collections of Emacs Lisp code
+ and extensions can be brought under control using declarative package
+ management. <productname>NixOS</productname> even provides a
+ <command>systemd</command> user service for automatically starting the Emacs
+ daemon.
+ </para>
+ <section xml:id="module-services-emacs-installing">
+ <title>Installing <application>Emacs</application></title>
+
+ <para>
+ Emacs can be installed in the normal way for Nix (see
+ <xref linkend="sec-package-management" />). In addition, a NixOS
+ <emphasis>service</emphasis> can be enabled.
+ </para>
+
+ <section xml:id="module-services-emacs-releases">
+ <title>The Different Releases of Emacs</title>
+
+ <para>
+ <productname>Nixpkgs</productname> defines several basic Emacs packages.
+ The following are attributes belonging to the <varname>pkgs</varname> set:
+ <variablelist>
+ <varlistentry>
+ <term>
+ <varname>emacs</varname>
+ </term>
+ <term>
+ <varname>emacs25</varname>
+ </term>
+ <listitem>
+ <para>
+ The latest stable version of Emacs 25 using the
+ <link
+ xlink:href="http://www.gtk.org">GTK 2</link>
+ widget toolkit.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <varname>emacs25-nox</varname>
+ </term>
+ <listitem>
+ <para>
+ Emacs 25 built without any dependency on X11 libraries.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <varname>emacsMacport</varname>
+ </term>
+ <term>
+ <varname>emacs25Macport</varname>
+ </term>
+ <listitem>
+ <para>
+ Emacs 25 with the "Mac port" patches, providing a more native look and
+ feel under macOS.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ <para>
+ If those aren't suitable, then the following imitation Emacs editors are
+ also available in Nixpkgs:
+ <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>,
+ <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>,
+ <link xlink:href="http://yi-editor.github.io/">Yi</link>,
+ <link xlink:href="https://joe-editor.sourceforge.io/">jmacs</link>.
+ </para>
+ </section>
+
+ <section xml:id="module-services-emacs-adding-packages">
+ <title>Adding Packages to Emacs</title>
+
+ <para>
+ Emacs includes an entire ecosystem of functionality beyond text editing,
+ including a project planner, mail and news reader, debugger interface,
+ calendar, and more.
+ </para>
+
+ <para>
+ Most extensions are gotten with the Emacs packaging system
+ (<filename>package.el</filename>) from
+ <link
+ xlink:href="https://elpa.gnu.org/">Emacs Lisp Package Archive
+ (<acronym>ELPA</acronym>)</link>,
+ <link xlink:href="https://melpa.org/"><acronym>MELPA</acronym></link>,
+ <link xlink:href="https://stable.melpa.org/">MELPA Stable</link>, and
+ <link xlink:href="http://orgmode.org/elpa.html">Org ELPA</link>. Nixpkgs is
+ regularly updated to mirror all these archives.
+ </para>
+
+ <para>
+ Under NixOS, you can continue to use
+ <function>package-list-packages</function> and
+ <function>package-install</function> to install packages. You can also
+ declare the set of Emacs packages you need using the derivations from
+ Nixpkgs. The rest of this section discusses declarative installation of
+ Emacs packages through nixpkgs.
+ </para>
+
+ <para>
+ The first step to declare the list of packages you want in your Emacs
+ installation is to create a dedicated derivation. This can be done in a
+ dedicated <filename>emacs.nix</filename> file such as:
+ <example xml:id="ex-emacsNix">
+ <title>Nix expression to build Emacs with packages (<filename>emacs.nix</filename>)</title>
+<programlisting language="nix">
+/*
+This is a nix expression to build Emacs and some Emacs packages I like
+from source on any distribution where Nix is installed. This will install
+all the dependencies from the nixpkgs repository and build the binary files
+without interfering with the host distribution.
+
+To build the project, type the following from the current directory:
+
+$ nix-build emacs.nix
+
+To run the newly compiled executable:
+
+$ ./result/bin/emacs
+*/
+{ pkgs ? import &lt;nixpkgs&gt; {} }: <co xml:id="ex-emacsNix-1" />
+
+let
+ myEmacs = pkgs.emacs; <co xml:id="ex-emacsNix-2" />
+ emacsWithPackages = (pkgs.emacsPackagesGen myEmacs).emacsWithPackages; <co xml:id="ex-emacsNix-3" />
+in
+ emacsWithPackages (epkgs: (with epkgs.melpaStablePackages; [ <co xml:id="ex-emacsNix-4" />
+ magit # ; Integrate git &lt;C-x g&gt;
+ zerodark-theme # ; Nicolas' theme
+ ]) ++ (with epkgs.melpaPackages; [ <co xml:id="ex-emacsNix-5" />
+ undo-tree # ; &lt;C-x u&gt; to show the undo tree
+ zoom-frm # ; increase/decrease font size for all buffers %lt;C-x C-+&gt;
+ ]) ++ (with epkgs.elpaPackages; [ <co xml:id="ex-emacsNix-6" />
+ auctex # ; LaTeX mode
+ beacon # ; highlight my cursor when scrolling
+ nameless # ; hide current package name everywhere in elisp code
+ ]) ++ [
+ pkgs.notmuch # From main packages set <co xml:id="ex-emacsNix-7" />
+ ])
+</programlisting>
+ </example>
+ <calloutlist>
+ <callout arearefs="ex-emacsNix-1">
+ <para>
+ The first non-comment line in this file (<literal>{ pkgs ? ...
+ }</literal>) indicates that the whole file represents a function.
+ </para>
+ </callout>
+ <callout arearefs="ex-emacsNix-2">
+ <para>
+ The <varname>let</varname> expression below defines a
+ <varname>myEmacs</varname> binding pointing to the current stable
+ version of Emacs. This binding is here to separate the choice of the
+ Emacs binary from the specification of the required packages.
+ </para>
+ </callout>
+ <callout arearefs="ex-emacsNix-3">
+ <para>
+ This generates an <varname>emacsWithPackages</varname> function. It
+ takes a single argument: a function from a package set to a list of
+ packages (the packages that will be available in Emacs).
+ </para>
+ </callout>
+ <callout arearefs="ex-emacsNix-4">
+ <para>
+ The rest of the file specifies the list of packages to install. In the
+ example, two packages (<varname>magit</varname> and
+ <varname>zerodark-theme</varname>) are taken from MELPA stable.
+ </para>
+ </callout>
+ <callout arearefs="ex-emacsNix-5">
+ <para>
+ Two packages (<varname>undo-tree</varname> and
+ <varname>zoom-frm</varname>) are taken from MELPA.
+ </para>
+ </callout>
+ <callout arearefs="ex-emacsNix-6">
+ <para>
+ Three packages are taken from GNU ELPA.
+ </para>
+ </callout>
+ <callout arearefs="ex-emacsNix-7">
+ <para>
+ <varname>notmuch</varname> is taken from a nixpkgs derivation which
+ contains an Emacs mode.
+ </para>
+ </callout>
+ </calloutlist>
+ </para>
+
+ <para>
+ The result of this configuration will be an <command>emacs</command>
+ command which launches Emacs with all of your chosen packages in the
+ <varname>load-path</varname>.
+ </para>
+
+ <para>
+ You can check that it works by executing this in a terminal:
+<screen>
+<prompt>$ </prompt>nix-build emacs.nix
+<prompt>$ </prompt>./result/bin/emacs -q
+</screen>
+ and then typing <literal>M-x package-initialize</literal>. Check that you
+ can use all the packages you want in this Emacs instance. For example, try
+ switching to the zerodark theme through <literal>M-x load-theme &lt;RET&gt;
+ zerodark &lt;RET&gt; y</literal>.
+ </para>
+
+ <tip>
+ <para>
+ A few popular extensions worth checking out are: auctex, company,
+ edit-server, flycheck, helm, iedit, magit, multiple-cursors, projectile,
+ and yasnippet.
+ </para>
+ </tip>
+
+ <para>
+ The list of available packages in the various ELPA repositories can be seen
+ with the following commands:
+ <example xml:id="module-services-emacs-querying-packages">
+ <title>Querying Emacs packages</title>
+<programlisting><![CDATA[
+nix-env -f "<nixpkgs>" -qaP -A emacsPackages.elpaPackages
+nix-env -f "<nixpkgs>" -qaP -A emacsPackages.melpaPackages
+nix-env -f "<nixpkgs>" -qaP -A emacsPackages.melpaStablePackages
+nix-env -f "<nixpkgs>" -qaP -A emacsPackages.orgPackages
+]]></programlisting>
+ </example>
+ </para>
+
+ <para>
+ If you are on NixOS, you can install this particular Emacs for all users by
+ adding it to the list of system packages (see
+ <xref linkend="sec-declarative-package-mgmt" />). Simply modify your file
+ <filename>configuration.nix</filename> to make it contain:
+ <example xml:id="module-services-emacs-configuration-nix">
+ <title>Custom Emacs in <filename>configuration.nix</filename></title>
+<programlisting><![CDATA[
+{
+ environment.systemPackages = [
+ # [...]
+ (import /path/to/emacs.nix { inherit pkgs; })
+ ];
+}
+]]></programlisting>
+ </example>
+ </para>
+
+ <para>
+ In this case, the next <command>nixos-rebuild switch</command> will take
+ care of adding your <command>emacs</command> to the <varname>PATH</varname>
+ environment variable (see <xref linkend="sec-changing-config" />).
+ </para>
+
+<!-- fixme: i think the following is better done with config.nix
+https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides
+-->
+
+ <para>
+ If you are not on NixOS or want to install this particular Emacs only for
+ yourself, you can do so by adding it to your
+ <filename>~/.config/nixpkgs/config.nix</filename> (see
+ <link xlink:href="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>
+
+ <para>
+ If you want, you can tweak the Emacs package itself from your
+ <filename>emacs.nix</filename>. For example, if you want to have a
+ GTK 3-based Emacs instead of the default GTK 2-based binary and remove the
+ automatically generated <filename>emacs.desktop</filename> (useful is you
+ only use <command>emacsclient</command>), you can change your file
+ <filename>emacs.nix</filename> in this way:
+ </para>
+
+ <example xml:id="ex-emacsGtk3Nix">
+ <title>Custom Emacs build</title>
+<programlisting><![CDATA[
+{ pkgs ? import <nixpkgs> {} }:
+let
+ myEmacs = (pkgs.emacs.override {
+ # Use gtk3 instead of the default gtk2
+ withGTK3 = true;
+ withGTK2 = false;
+ }).overrideAttrs (attrs: {
+ # I don't want emacs.desktop file because I only use
+ # emacsclient.
+ postInstall = (attrs.postInstall or "") + ''
+ rm $out/share/applications/emacs.desktop
+ '';
+ });
+in [...]
+]]></programlisting>
+ </example>
+
+ <para>
+ After building this file as shown in <xref linkend="ex-emacsNix" />, you
+ will get an GTK 3-based Emacs binary pre-loaded with your favorite packages.
+ </para>
+ </section>
+ </section>
+ <section xml:id="module-services-emacs-running">
+ <title>Running Emacs as a Service</title>
+
+ <para>
+ <productname>NixOS</productname> provides an optional
+ <command>systemd</command> service which launches
+ <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html">
+ Emacs daemon </link> with the user's login session.
+ </para>
+
+ <para>
+ <emphasis>Source:</emphasis>
+ <filename>modules/services/editors/emacs.nix</filename>
+ </para>
+
+ <section xml:id="module-services-emacs-enabling">
+ <title>Enabling the Service</title>
+
+ <para>
+ To install and enable the <command>systemd</command> user service for Emacs
+ daemon, add the following to your <filename>configuration.nix</filename>:
+<programlisting>
+<xref linkend="opt-services.emacs.enable"/> = true;
+<xref linkend="opt-services.emacs.package"/> = import /home/cassou/.emacs.d { pkgs = pkgs; };
+</programlisting>
+ </para>
+
+ <para>
+ The <varname>services.emacs.package</varname> option allows a custom
+ derivation to be used, for example, one created by
+ <function>emacsWithPackages</function>.
+ </para>
+
+ <para>
+ Ensure that the Emacs server is enabled for your user's Emacs
+ configuration, either by customizing the <varname>server-mode</varname>
+ variable, or by adding <literal>(server-start)</literal> to
+ <filename>~/.emacs.d/init.el</filename>.
+ </para>
+
+ <para>
+ To start the daemon, execute the following:
+<screen>
+<prompt>$ </prompt>nixos-rebuild switch # to activate the new configuration.nix
+<prompt>$ </prompt>systemctl --user daemon-reload # to force systemd reload
+<prompt>$ </prompt>systemctl --user start emacs.service # to start the Emacs daemon
+</screen>
+ The server should now be ready to serve Emacs clients.
+ </para>
+ </section>
+
+ <section xml:id="module-services-emacs-starting-client">
+ <title>Starting the client</title>
+
+ <para>
+ Ensure that the emacs server is enabled, either by customizing the
+ <varname>server-mode</varname> variable, or by adding
+ <literal>(server-start)</literal> to <filename>~/.emacs</filename>.
+ </para>
+
+ <para>
+ To connect to the emacs daemon, run one of the following:
+<programlisting><![CDATA[
+emacsclient FILENAME
+emacsclient --create-frame # opens a new frame (window)
+emacsclient --create-frame --tty # opens a new frame on the current terminal
+]]></programlisting>
+ </para>
+ </section>
+
+ <section xml:id="module-services-emacs-editor-variable">
+ <title>Configuring the <varname>EDITOR</varname> variable</title>
+
+<!--<title><command>emacsclient</command> as the Default Editor</title>-->
+
+ <para>
+ If <xref linkend="opt-services.emacs.defaultEditor"/> is
+ <literal>true</literal>, the <varname>EDITOR</varname> variable will be set
+ to a wrapper script which launches <command>emacsclient</command>.
+ </para>
+
+ <para>
+ Any setting of <varname>EDITOR</varname> in the shell config files will
+ override <varname>services.emacs.defaultEditor</varname>. To make sure
+ <varname>EDITOR</varname> refers to the Emacs wrapper script, remove any
+ existing <varname>EDITOR</varname> assignment from
+ <filename>.profile</filename>, <filename>.bashrc</filename>,
+ <filename>.zshenv</filename> or any other shell config file.
+ </para>
+
+ <para>
+ If you have formed certain bad habits when editing files, these can be
+ corrected with a shell alias to the wrapper script:
+<programlisting>alias vi=$EDITOR</programlisting>
+ </para>
+ </section>
+
+ <section xml:id="module-services-emacs-per-user">
+ <title>Per-User Enabling of the Service</title>
+
+ <para>
+ In general, <command>systemd</command> user services are globally enabled
+ by symlinks in <filename>/etc/systemd/user</filename>. In the case where
+ Emacs daemon is not wanted for all users, it is possible to install the
+ service but not globally enable it:
+<programlisting>
+<xref linkend="opt-services.emacs.enable"/> = false;
+<xref linkend="opt-services.emacs.install"/> = true;
+</programlisting>
+ </para>
+
+ <para>
+ To enable the <command>systemd</command> user service for just the
+ currently logged in user, run:
+<programlisting>systemctl --user enable emacs</programlisting>
+ This will add the symlink
+ <filename>~/.config/systemd/user/emacs.service</filename>.
+ </para>
+ </section>
+ </section>
+ <section xml:id="module-services-emacs-configuring">
+ <title>Configuring Emacs</title>
+
+ <para>
+ The Emacs init file should be changed to load the extension packages at
+ startup:
+ <example xml:id="module-services-emacs-package-initialisation">
+ <title>Package initialization in <filename>.emacs</filename></title>
+<programlisting><![CDATA[
+(require 'package)
+
+;; optional. makes unpure packages archives unavailable
+(setq package-archives nil)
+
+(setq package-enable-at-startup nil)
+(package-initialize)
+]]></programlisting>
+ </example>
+ </para>
+
+ <para>
+ After the declarative emacs package configuration has been tested,
+ previously downloaded packages can be cleaned up by removing
+ <filename>~/.emacs.d/elpa</filename> (do make a backup first, in case you
+ forgot a package).
+ </para>
+
+<!--
+ todo: is it worth documenting customizations for
+ server-switch-hook, server-done-hook?
+ -->
+
+ <section xml:id="module-services-emacs-major-mode">
+ <title>A Major Mode for Nix Expressions</title>
+
+ <para>
+ Of interest may be <varname>melpaPackages.nix-mode</varname>, which
+ provides syntax highlighting for the Nix language. This is particularly
+ convenient if you regularly edit Nix files.
+ </para>
+ </section>
+
+ <section xml:id="module-services-emacs-man-pages">
+ <title>Accessing man pages</title>
+
+ <para>
+ You can use <function>woman</function> to get completion of all available
+ man pages. For example, type <literal>M-x woman &lt;RET&gt; nixos-rebuild
+ &lt;RET&gt;.</literal>
+ </para>
+ </section>
+
+ <section xml:id="sec-emacs-docbook-xml">
+ <title>Editing DocBook 5 XML Documents</title>
+
+ <para>
+ Emacs includes
+ <link
+ xlink:href="https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html">nXML</link>,
+ a major-mode for validating and editing XML documents. When editing DocBook
+ 5.0 documents, such as <link linkend="book-nixos-manual">this one</link>,
+ nXML needs to be configured with the relevant schema, which is not
+ included.
+ </para>
+
+ <para>
+ To install the DocBook 5.0 schemas, either add
+ <varname>pkgs.docbook5</varname> to
+ <xref linkend="opt-environment.systemPackages"/>
+ (<link
+ linkend="sec-declarative-package-mgmt">NixOS</link>), or run
+ <literal>nix-env -f '&lt;nixpkgs&gt;' -iA docbook5</literal>
+ (<link linkend="sec-ad-hoc-packages">Nix</link>).
+ </para>
+
+ <para>
+ Then customize the variable <varname>rng-schema-locating-files</varname> to
+ include <filename>~/.emacs.d/schemas.xml</filename> and put the following
+ text into that file:
+ <example xml:id="ex-emacs-docbook-xml">
+ <title>nXML Schema Configuration (<filename>~/.emacs.d/schemas.xml</filename>)</title>
+<programlisting language="xml"><![CDATA[
+<?xml version="1.0"?>
+<!--
+ To let emacs find this file, evaluate:
+ (add-to-list 'rng-schema-locating-files "~/.emacs.d/schemas.xml")
+-->
+<locatingRules xmlns="http://thaiopensource.com/ns/locating-rules/1.0">
+ <!--
+ Use this variation if pkgs.docbook5 is added to environment.systemPackages
+ -->
+ <namespace ns="http://docbook.org/ns/docbook"
+ uri="/run/current-system/sw/share/xml/docbook-5.0/rng/docbookxi.rnc"/>
+ <!--
+ Use this variation if installing schema with "nix-env -iA pkgs.docbook5".
+ <namespace ns="http://docbook.org/ns/docbook"
+ uri="../.nix-profile/share/xml/docbook-5.0/rng/docbookxi.rnc"/>
+ -->
+</locatingRules>
+]]></programlisting>
+ </example>
+ </para>
+ </section>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/editors/infinoted.nix b/nixpkgs/nixos/modules/services/editors/infinoted.nix
new file mode 100644
index 00000000000..9cc8d421270
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/editors/infinoted.nix
@@ -0,0 +1,158 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.infinoted;
+in {
+ options.services.infinoted = {
+ enable = mkEnableOption "infinoted";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.libinfinity;
+ defaultText = "pkgs.libinfinity";
+ description = ''
+ Package providing infinoted
+ '';
+ };
+
+ keyFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Private key to use for TLS
+ '';
+ };
+
+ certificateFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Server certificate to use for TLS
+ '';
+ };
+
+ certificateChain = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Chain of CA-certificates to which our `certificateFile` is relative.
+ Optional for TLS.
+ '';
+ };
+
+ securityPolicy = mkOption {
+ type = types.enum ["no-tls" "allow-tls" "require-tls"];
+ default = "require-tls";
+ description = ''
+ How strictly to enforce clients connection with TLS.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 6523;
+ description = ''
+ Port to listen on
+ '';
+ };
+
+ rootDirectory = mkOption {
+ type = types.path;
+ default = "/var/lib/infinoted/documents/";
+ description = ''
+ Root of the directory structure to serve
+ '';
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.str;
+ default = [ "note-text" "note-chat" "logging" "autosave" ];
+ description = ''
+ Plugins to enable
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ File to read server-wide password from
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = ''
+ [autosave]
+ interval=10
+ '';
+ description = ''
+ Additional configuration to append to infinoted.conf
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "infinoted";
+ description = ''
+ What to call the dedicated user under which infinoted is run
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "infinoted";
+ description = ''
+ What to call the primary group of the dedicated user under which infinoted is run
+ '';
+ };
+ };
+
+ config = mkIf (cfg.enable) {
+ users.users = optional (cfg.user == "infinoted")
+ { name = "infinoted";
+ description = "Infinoted user";
+ group = cfg.group;
+ };
+ users.groups = optional (cfg.group == "infinoted")
+ { name = "infinoted";
+ };
+
+ systemd.services.infinoted =
+ { description = "Gobby Dedicated Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ Restart = "always";
+ ExecStart = "${cfg.package.infinoted} --config-file=/var/lib/infinoted/infinoted.conf";
+ User = cfg.user;
+ Group = cfg.group;
+ PermissionsStartOnly = true;
+ };
+ preStart = ''
+ mkdir -p /var/lib/infinoted
+ install -o ${cfg.user} -g ${cfg.group} -m 0600 /dev/null /var/lib/infinoted/infinoted.conf
+ cat >>/var/lib/infinoted/infinoted.conf <<EOF
+ [infinoted]
+ ${optionalString (cfg.keyFile != null) ''key-file=${cfg.keyFile}''}
+ ${optionalString (cfg.certificateFile != null) ''certificate-file=${cfg.certificateFile}''}
+ ${optionalString (cfg.certificateChain != null) ''certificate-chain=${cfg.certificateChain}''}
+ port=${toString cfg.port}
+ security-policy=${cfg.securityPolicy}
+ root-directory=${cfg.rootDirectory}
+ plugins=${concatStringsSep ";" cfg.plugins}
+ ${optionalString (cfg.passwordFile != null) ''password=$(head -n 1 ${cfg.passwordFile})''}
+
+ ${cfg.extraConfig}
+ EOF
+
+ install -o ${cfg.user} -g ${cfg.group} -m 0750 -d ${cfg.rootDirectory}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/games/factorio.nix b/nixpkgs/nixos/modules/services/games/factorio.nix
new file mode 100644
index 00000000000..f3831156f45
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/games/factorio.nix
@@ -0,0 +1,226 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.factorio;
+ factorio = pkgs.factorio-headless;
+ name = "Factorio";
+ stateDir = "/var/lib/${cfg.stateDirName}";
+ mkSavePath = name: "${stateDir}/saves/${name}.zip";
+ configFile = pkgs.writeText "factorio.conf" ''
+ use-system-read-write-data-directories=true
+ [path]
+ read-data=${factorio}/share/factorio/data
+ write-data=${stateDir}
+ '';
+ serverSettings = {
+ name = cfg.game-name;
+ description = cfg.description;
+ visibility = {
+ public = cfg.public;
+ lan = cfg.lan;
+ };
+ username = cfg.username;
+ password = cfg.password;
+ token = cfg.token;
+ game_password = cfg.game-password;
+ require_user_verification = cfg.requireUserVerification;
+ max_upload_in_kilobytes_per_second = 0;
+ minimum_latency_in_ticks = 0;
+ ignore_player_limit_for_returning_players = false;
+ allow_commands = "admins-only";
+ autosave_interval = cfg.autosave-interval;
+ autosave_slots = 5;
+ afk_autokick_interval = 0;
+ auto_pause = true;
+ only_admins_can_pause_the_game = true;
+ autosave_only_on_server = true;
+ admins = [];
+ };
+ serverSettingsFile = pkgs.writeText "server-settings.json" (builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings));
+ modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods;
+in
+{
+ options = {
+ services.factorio = {
+ enable = mkEnableOption name;
+ port = mkOption {
+ type = types.int;
+ default = 34197;
+ description = ''
+ The port to which the service should bind.
+
+ This option will also open up the UDP port in the firewall configuration.
+ '';
+ };
+ saveName = mkOption {
+ type = types.str;
+ default = "default";
+ description = ''
+ The name of the savegame that will be used by the server.
+
+ When not present in ${stateDir}/saves, a new map with default
+ settings will be generated before starting the service.
+ '';
+ };
+ # TODO Add more individual settings as nixos-options?
+ # TODO XXX The server tries to copy a newly created config file over the old one
+ # on shutdown, but fails, because it's in the nix store. When is this needed?
+ # Can an admin set options in-game and expect to have them persisted?
+ configFile = mkOption {
+ type = types.path;
+ default = configFile;
+ defaultText = "configFile";
+ description = ''
+ The server's configuration file.
+
+ The default file generated by this module contains lines essential to
+ the server's operation. Use its contents as a basis for any
+ customizations.
+ '';
+ };
+ stateDirName = mkOption {
+ type = types.str;
+ default = "factorio";
+ description = ''
+ Name of the directory under /var/lib holding the server's data.
+
+ The configuration and map will be stored here.
+ '';
+ };
+ mods = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = ''
+ Mods the server should install and activate.
+
+ The derivations in this list must "build" the mod by simply copying
+ the .zip, named correctly, into the output directory. Eventually,
+ there will be a way to pull in the most up-to-date list of
+ derivations via nixos-channel. Until then, this is for experts only.
+ '';
+ };
+ game-name = mkOption {
+ type = types.nullOr types.str;
+ default = "Factorio Game";
+ description = ''
+ Name of the game as it will appear in the game listing.
+ '';
+ };
+ description = mkOption {
+ type = types.nullOr types.str;
+ default = "";
+ description = ''
+ Description of the game that will appear in the listing.
+ '';
+ };
+ public = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Game will be published on the official Factorio matching server.
+ '';
+ };
+ lan = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Game will be broadcast on LAN.
+ '';
+ };
+ username = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Your factorio.com login credentials. Required for games with visibility public.
+ '';
+ };
+ password = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Your factorio.com login credentials. Required for games with visibility public.
+ '';
+ };
+ token = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Authentication token. May be used instead of 'password' above.
+ '';
+ };
+ game-password = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Game password.
+ '';
+ };
+ requireUserVerification = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ When set to true, the server will only allow clients that have a valid factorio.com account.
+ '';
+ };
+ autosave-interval = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 10;
+ description = ''
+ Autosave interval in minutes.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.factorio = {
+ description = "Factorio headless server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ preStart = toString [
+ "test -e ${stateDir}/saves/${cfg.saveName}.zip"
+ "||"
+ "${factorio}/bin/factorio"
+ "--config=${cfg.configFile}"
+ "--create=${mkSavePath cfg.saveName}"
+ (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
+ ];
+
+ serviceConfig = {
+ Restart = "always";
+ KillSignal = "SIGINT";
+ DynamicUser = true;
+ StateDirectory = cfg.stateDirName;
+ UMask = "0007";
+ ExecStart = toString [
+ "${factorio}/bin/factorio"
+ "--config=${cfg.configFile}"
+ "--port=${toString cfg.port}"
+ "--start-server=${mkSavePath cfg.saveName}"
+ "--server-settings=${serverSettingsFile}"
+ (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
+ ];
+
+ # Sandboxing
+ NoNewPrivileges = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ ProtectControlGroups = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
+ RestrictRealtime = true;
+ RestrictNamespaces = true;
+ MemoryDenyWriteExecute = true;
+ };
+ };
+
+ networking.firewall.allowedUDPPorts = [ cfg.port ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/games/minecraft-server.nix b/nixpkgs/nixos/modules/services/games/minecraft-server.nix
new file mode 100644
index 00000000000..eb9288fca58
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/games/minecraft-server.nix
@@ -0,0 +1,234 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.minecraft-server;
+
+ # We don't allow eula=false anyways
+ eulaFile = builtins.toFile "eula.txt" ''
+ # eula.txt managed by NixOS Configuration
+ eula=true
+ '';
+
+ whitelistFile = pkgs.writeText "whitelist.json"
+ (builtins.toJSON
+ (mapAttrsToList (n: v: { name = n; uuid = v; }) cfg.whitelist));
+
+ cfgToString = v: if builtins.isBool v then boolToString v else toString v;
+
+ serverPropertiesFile = pkgs.writeText "server.properties" (''
+ # server.properties managed by NixOS configuration
+ '' + concatStringsSep "\n" (mapAttrsToList
+ (n: v: "${n}=${cfgToString v}") cfg.serverProperties));
+
+
+ # To be able to open the firewall, we need to read out port values in the
+ # server properties, but fall back to the defaults when those don't exist.
+ # These defaults are from https://minecraft.gamepedia.com/Server.properties#Java_Edition_3
+ defaultServerPort = 25565;
+
+ serverPort = cfg.serverProperties.server-port or defaultServerPort;
+
+ rconPort = if cfg.serverProperties.enable-rcon or false
+ then cfg.serverProperties."rcon.port" or 25575
+ else null;
+
+ queryPort = if cfg.serverProperties.enable-query or false
+ then cfg.serverProperties."query.port" or 25565
+ else null;
+
+in {
+ options = {
+ services.minecraft-server = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, start a Minecraft Server. The server
+ data will be loaded from and saved to
+ <option>services.minecraft-server.dataDir</option>.
+ '';
+ };
+
+ declarative = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use a declarative Minecraft server configuration.
+ Only if set to <literal>true</literal>, the options
+ <option>services.minecraft-server.whitelist</option> and
+ <option>services.minecraft-server.serverProperties</option> will be
+ applied.
+ '';
+ };
+
+ eula = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether you agree to
+ <link xlink:href="https://account.mojang.com/documents/minecraft_eula">
+ Mojangs EULA</link>. This option must be set to
+ <literal>true</literal> to run Minecraft server.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/minecraft";
+ description = ''
+ Directory to store Minecraft database and other state/data files.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to open ports in the firewall for the server.
+ '';
+ };
+
+ whitelist = mkOption {
+ type = let
+ minecraftUUID = types.strMatching
+ "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" // {
+ description = "Minecraft UUID";
+ };
+ in types.attrsOf minecraftUUID;
+ default = {};
+ description = ''
+ Whitelisted players, only has an effect when
+ <option>services.minecraft-server.declarative</option> is
+ <literal>true</literal> and the whitelist is enabled
+ via <option>services.minecraft-server.serverProperties</option> by
+ setting <literal>white-list</literal> to <literal>true</literal>.
+ This is a mapping from Minecraft usernames to UUIDs.
+ You can use <link xlink:href="https://mcuuid.net/"/> to get a
+ Minecraft UUID for a username.
+ '';
+ example = literalExample ''
+ {
+ username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
+ username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
+ };
+ '';
+ };
+
+ serverProperties = mkOption {
+ type = with types; attrsOf (oneOf [ bool int str ]);
+ default = {};
+ example = literalExample ''
+ {
+ server-port = 43000;
+ difficulty = 3;
+ gamemode = 1;
+ max-players = 5;
+ motd = "NixOS Minecraft server!";
+ white-list = true;
+ enable-rcon = true;
+ "rcon.password" = "hunter2";
+ }
+ '';
+ description = ''
+ Minecraft server properties for the server.properties file. Only has
+ an effect when <option>services.minecraft-server.declarative</option>
+ is set to <literal>true</literal>. See
+ <link xlink:href="https://minecraft.gamepedia.com/Server.properties#Java_Edition_3"/>
+ for documentation on these values.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.minecraft-server;
+ defaultText = "pkgs.minecraft-server";
+ example = literalExample "pkgs.minecraft-server_1_12_2";
+ description = "Version of minecraft-server to run.";
+ };
+
+ jvmOpts = mkOption {
+ type = types.separatedString " ";
+ default = "-Xmx2048M -Xms2048M";
+ # Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script
+ example = "-Xmx2048M -Xms4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
+ + "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 "
+ + "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10";
+ description = "JVM options for the Minecraft server.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users.minecraft = {
+ description = "Minecraft server service user";
+ home = cfg.dataDir;
+ createHome = true;
+ uid = config.ids.uids.minecraft;
+ };
+
+ systemd.services.minecraft-server = {
+ description = "Minecraft Server Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/minecraft-server ${cfg.jvmOpts}";
+ Restart = "always";
+ User = "minecraft";
+ WorkingDirectory = cfg.dataDir;
+ };
+
+ preStart = ''
+ ln -sf ${eulaFile} eula.txt
+ '' + (if cfg.declarative then ''
+
+ if [ -e .declarative ]; then
+
+ # Was declarative before, no need to back up anything
+ ln -sf ${whitelistFile} whitelist.json
+ cp -f ${serverPropertiesFile} server.properties
+
+ else
+
+ # Declarative for the first time, backup stateful files
+ ln -sb --suffix=.stateful ${whitelistFile} whitelist.json
+ cp -b --suffix=.stateful ${serverPropertiesFile} server.properties
+
+ # server.properties must have write permissions, because every time
+ # the server starts it first parses the file and then regenerates it..
+ chmod +w server.properties
+ echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
+ > .declarative
+
+ fi
+ '' else ''
+ if [ -e .declarative ]; then
+ rm .declarative
+ fi
+ '');
+ };
+
+ networking.firewall = mkIf cfg.openFirewall (if cfg.declarative then {
+ allowedUDPPorts = [ serverPort ];
+ allowedTCPPorts = [ serverPort ]
+ ++ optional (queryPort != null) queryPort
+ ++ optional (rconPort != null) rconPort;
+ } else {
+ allowedUDPPorts = [ defaultServerPort ];
+ allowedTCPPorts = [ defaultServerPort ];
+ });
+
+ assertions = [
+ { assertion = cfg.eula;
+ message = "You must agree to Mojangs EULA to run minecraft-server."
+ + " Read https://account.mojang.com/documents/minecraft_eula and"
+ + " set `services.minecraft-server.eula` to `true` if you agree.";
+ }
+ ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/games/minetest-server.nix b/nixpkgs/nixos/modules/services/games/minetest-server.nix
new file mode 100644
index 00000000000..98e69c6dc0e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/games/minetest-server.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.minetest-server;
+ flag = val: name: if val != null then "--${name} ${val} " else "";
+ flags = [
+ (flag cfg.gameId "gameid")
+ (flag cfg.world "world")
+ (flag cfg.configPath "config")
+ (flag cfg.logPath "logfile")
+ (flag cfg.port "port")
+ ];
+in
+{
+ options = {
+ services.minetest-server = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "If enabled, starts a Minetest Server.";
+ };
+
+ gameId = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Id of the game to use. To list available games run
+ `minetestserver --gameid list`.
+
+ If only one game exists, this option can be null.
+ '';
+ };
+
+ world = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Name of the world to use. To list available worlds run
+ `minetestserver --world list`.
+
+ If only one world exists, this option can be null.
+ '';
+ };
+
+ configPath = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Path to the config to use.
+
+ If set to null, the config of the running user will be used:
+ `~/.minetest/minetest.conf`.
+ '';
+ };
+
+ logPath = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Path to logfile for logging.
+
+ If set to null, logging will be output to stdout which means
+ all output will be catched by systemd.
+ '';
+ };
+
+ port = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Port number to bind to.
+
+ If set to null, the default 30000 will be used.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.minetest = {
+ description = "Minetest Server Service user";
+ home = "/var/lib/minetest";
+ createHome = true;
+ uid = config.ids.uids.minetest;
+ group = "minetest";
+ };
+ users.groups.minetest.gid = config.ids.gids.minetest;
+
+ systemd.services.minetest-server = {
+ description = "Minetest Server Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig.Restart = "always";
+ serviceConfig.User = "minetest";
+ serviceConfig.Group = "minetest";
+
+ script = ''
+ cd /var/lib/minetest
+
+ exec ${pkgs.minetest}/bin/minetest --server ${concatStrings flags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/games/terraria.nix b/nixpkgs/nixos/modules/services/games/terraria.nix
new file mode 100644
index 00000000000..a59b74c0b4c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/games/terraria.nix
@@ -0,0 +1,149 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.terraria;
+ worldSizeMap = { small = 1; medium = 2; large = 3; };
+ valFlag = name: val: optionalString (val != null) "-${name} \"${escape ["\\" "\""] (toString val)}\"";
+ boolFlag = name: val: optionalString val "-${name}";
+ flags = [
+ (valFlag "port" cfg.port)
+ (valFlag "maxPlayers" cfg.maxPlayers)
+ (valFlag "password" cfg.password)
+ (valFlag "motd" cfg.messageOfTheDay)
+ (valFlag "world" cfg.worldPath)
+ (valFlag "autocreate" (builtins.getAttr cfg.autoCreatedWorldSize worldSizeMap))
+ (valFlag "banlist" cfg.banListPath)
+ (boolFlag "secure" cfg.secure)
+ (boolFlag "noupnp" cfg.noUPnP)
+ ];
+ stopScript = pkgs.writeScript "terraria-stop" ''
+ #!${pkgs.runtimeShell}
+
+ if ! [ -d "/proc/$1" ]; then
+ exit 0
+ fi
+
+ ${getBin pkgs.tmux}/bin/tmux -S /var/lib/terraria/terraria.sock send-keys Enter exit Enter
+ ${getBin pkgs.coreutils}/bin/tail --pid="$1" -f /dev/null
+ '';
+in
+{
+ options = {
+ services.terraria = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, starts a Terraria server. The server can be connected to via <literal>tmux -S /var/lib/terraria/terraria.sock attach</literal>
+ for administration by users who are a part of the <literal>terraria</literal> group (use <literal>C-b d</literal> shortcut to detach again).
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 7777;
+ description = ''
+ Specifies the port to listen on.
+ '';
+ };
+
+ maxPlayers = mkOption {
+ type = types.int;
+ default = 255;
+ description = ''
+ Sets the max number of players (between 1 and 255).
+ '';
+ };
+
+ password = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Sets the server password. Leave <literal>null</literal> for no password.
+ '';
+ };
+
+ messageOfTheDay = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Set the server message of the day text.
+ '';
+ };
+
+ worldPath = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The path to the world file (<literal>.wld</literal>) which should be loaded.
+ If no world exists at this path, one will be created with the size
+ specified by <literal>autoCreatedWorldSize</literal>.
+ '';
+ };
+
+ autoCreatedWorldSize = mkOption {
+ type = types.enum [ "small" "medium" "large" ];
+ default = "medium";
+ description = ''
+ Specifies the size of the auto-created world if <literal>worldPath</literal> does not
+ point to an existing world.
+ '';
+ };
+
+ banListPath = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The path to the ban list.
+ '';
+ };
+
+ secure = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Adds additional cheat protection to the server.";
+ };
+
+ noUPnP = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Disables automatic Universal Plug and Play.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.terraria = {
+ description = "Terraria server service user";
+ home = "/var/lib/terraria";
+ createHome = true;
+ uid = config.ids.uids.terraria;
+ };
+
+ users.groups.terraria = {
+ gid = config.ids.gids.terraria;
+ members = [ "terraria" ];
+ };
+
+ systemd.services.terraria = {
+ description = "Terraria Server Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ User = "terraria";
+ Type = "forking";
+ GuessMainPID = true;
+ ExecStart = "${getBin pkgs.tmux}/bin/tmux -S /var/lib/terraria/terraria.sock new -d ${pkgs.terraria-server}/bin/TerrariaServer ${concatStringsSep " " flags}";
+ ExecStop = "${stopScript} $MAINPID";
+ };
+
+ postStart = ''
+ ${pkgs.coreutils}/bin/chmod 660 /var/lib/terraria/terraria.sock
+ ${pkgs.coreutils}/bin/chgrp terraria /var/lib/terraria/terraria.sock
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/acpid.nix b/nixpkgs/nixos/modules/services/hardware/acpid.nix
new file mode 100644
index 00000000000..4c97485d972
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/acpid.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ canonicalHandlers = {
+ powerEvent = {
+ event = "button/power.*";
+ action = config.services.acpid.powerEventCommands;
+ };
+
+ lidEvent = {
+ event = "button/lid.*";
+ action = config.services.acpid.lidEventCommands;
+ };
+
+ acEvent = {
+ event = "ac_adapter.*";
+ action = config.services.acpid.acEventCommands;
+ };
+ };
+
+ acpiConfDir = pkgs.runCommand "acpi-events" { preferLocalBuild = true; }
+ ''
+ mkdir -p $out
+ ${
+ # Generate a configuration file for each event. (You can't have
+ # multiple events in one config file...)
+ let f = name: handler:
+ ''
+ fn=$out/${name}
+ echo "event=${handler.event}" > $fn
+ echo "action=${pkgs.writeShellScriptBin "${name}.sh" handler.action }/bin/${name}.sh '%e'" >> $fn
+ '';
+ in concatStringsSep "\n" (mapAttrsToList f (canonicalHandlers // config.services.acpid.handlers))
+ }
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.acpid = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the ACPI daemon.";
+ };
+
+ logEvents = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Log all event activity.";
+ };
+
+ handlers = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ event = mkOption {
+ type = types.str;
+ example = [ "button/power.*" "button/lid.*" "ac_adapter.*" "button/mute.*" "button/volumedown.*" "cd/play.*" "cd/next.*" ];
+ description = "Event type.";
+ };
+
+ action = mkOption {
+ type = types.lines;
+ description = "Shell commands to execute when the event is triggered.";
+ };
+ };
+ });
+
+ description = ''
+ Event handlers.
+
+ <note><para>
+ Handler can be a single command.
+ </para></note>
+ '';
+ default = {};
+ example = {
+ ac-power = {
+ event = "ac_adapter/*";
+ action = ''
+ vals=($1) # space separated string to array of multiple values
+ case ''${vals[3]} in
+ 00000000)
+ echo unplugged >> /tmp/acpi.log
+ ;;
+ 00000001)
+ echo plugged in >> /tmp/acpi.log
+ ;;
+ *)
+ echo unknown >> /tmp/acpi.log
+ ;;
+ esac
+ '';
+ };
+ };
+ };
+
+ powerEventCommands = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Shell commands to execute on a button/power.* event.";
+ };
+
+ lidEventCommands = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Shell commands to execute on a button/lid.* event.";
+ };
+
+ acEventCommands = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Shell commands to execute on an ac_adapter.* event.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.acpid.enable {
+
+ systemd.services.acpid = {
+ description = "ACPI Daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "systemd-udev-settle.service" ];
+
+ path = [ pkgs.acpid ];
+
+ serviceConfig = {
+ Type = "forking";
+ };
+
+ unitConfig = {
+ ConditionVirtualization = "!systemd-nspawn";
+ ConditionPathExists = [ "/proc/acpi" ];
+ };
+
+ script = "acpid ${optionalString config.services.acpid.logEvents "--logevents"} --confdir ${acpiConfDir}";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/actkbd.nix b/nixpkgs/nixos/modules/services/hardware/actkbd.nix
new file mode 100644
index 00000000000..4168140b287
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/actkbd.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.actkbd;
+
+ configFile = pkgs.writeText "actkbd.conf" ''
+ ${concatMapStringsSep "\n"
+ ({ keys, events, attributes, command, ... }:
+ ''${concatMapStringsSep "+" toString keys}:${concatStringsSep "," events}:${concatStringsSep "," attributes}:${command}''
+ )
+ cfg.bindings}
+ ${cfg.extraConfig}
+ '';
+
+ bindingCfg = { ... }: {
+ options = {
+
+ keys = mkOption {
+ type = types.listOf types.int;
+ description = "List of keycodes to match.";
+ };
+
+ events = mkOption {
+ type = types.listOf (types.enum ["key" "rep" "rel"]);
+ default = [ "key" ];
+ description = "List of events to match.";
+ };
+
+ attributes = mkOption {
+ type = types.listOf types.str;
+ default = [ "exec" ];
+ description = "List of attributes.";
+ };
+
+ command = mkOption {
+ type = types.str;
+ default = "";
+ description = "What to run.";
+ };
+
+ };
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.actkbd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the <command>actkbd</command> key mapping daemon.
+
+ Turning this on will start an <command>actkbd</command>
+ instance for every evdev input that has at least one key
+ (which is okay even for systems with tiny memory footprint,
+ since actkbd normally uses &lt;100 bytes of memory per
+ instance).
+
+ This allows binding keys globally without the need for e.g.
+ X11.
+ '';
+ };
+
+ bindings = mkOption {
+ type = types.listOf (types.submodule bindingCfg);
+ default = [];
+ example = lib.literalExample ''
+ [ { keys = [ 113 ]; events = [ "key" ]; command = "''${pkgs.alsaUtils}/bin/amixer -q set Master toggle"; }
+ ]
+ '';
+ description = ''
+ Key bindings for <command>actkbd</command>.
+
+ See <command>actkbd</command> <filename>README</filename> for documentation.
+
+ The example shows a piece of what <option>sound.enableMediaKeys</option> does when enabled.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Literal contents to append to the end of actkbd configuration file.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.udev.packages = lib.singleton (pkgs.writeTextFile {
+ name = "actkbd-udev-rules";
+ destination = "/etc/udev/rules.d/61-actkbd.rules";
+ text = ''
+ ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ENV{ID_INPUT_KEY}=="1", TAG+="systemd", ENV{SYSTEMD_WANTS}+="actkbd@$env{DEVNAME}.service"
+ '';
+ });
+
+ systemd.services."actkbd@" = {
+ enable = true;
+ restartIfChanged = true;
+ unitConfig = {
+ Description = "actkbd on %I";
+ ConditionPathExists = "%I";
+ };
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.actkbd}/bin/actkbd -D -c ${configFile} -d %I";
+ };
+ };
+
+ # For testing
+ environment.systemPackages = [ pkgs.actkbd ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/bluetooth.nix b/nixpkgs/nixos/modules/services/hardware/bluetooth.nix
new file mode 100644
index 00000000000..c5f9d1f9b72
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/bluetooth.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.hardware.bluetooth;
+ bluez-bluetooth = cfg.package;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ hardware.bluetooth = {
+ enable = mkEnableOption "support for Bluetooth";
+
+ powerOnBoot = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to power up the default Bluetooth controller on boot.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.bluez;
+ defaultText = "pkgs.bluez";
+ example = "pkgs.bluezFull";
+ description = ''
+ Which BlueZ package to use.
+
+ <note><para>
+ Use the <literal>pkgs.bluezFull</literal> package to enable all
+ bluez plugins.
+ </para></note>
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ [General]
+ ControllerMode = bredr
+ '';
+ description = ''
+ Set additional configuration for system-wide bluetooth (/etc/bluetooth/main.conf).
+
+ NOTE: We already include [Policy], so any configuration under the Policy group should come first.
+ '';
+ };
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ bluez-bluetooth pkgs.openobex pkgs.obexftp ];
+
+ environment.etc = singleton {
+ source = pkgs.writeText "main.conf" ''
+ [Policy]
+ AutoEnable=${lib.boolToString cfg.powerOnBoot}
+
+ ${cfg.extraConfig}
+ '';
+ target = "bluetooth/main.conf";
+ };
+
+ services.udev.packages = [ bluez-bluetooth ];
+ services.dbus.packages = [ bluez-bluetooth ];
+ systemd.packages = [ bluez-bluetooth ];
+
+ systemd.services = {
+ bluetooth = {
+ wantedBy = [ "bluetooth.target" ];
+ aliases = [ "dbus-org.bluez.service" ];
+ };
+ };
+
+ systemd.user.services = {
+ obex.aliases = [ "dbus-org.bluez.obex.service" ];
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/bolt.nix b/nixpkgs/nixos/modules/services/hardware/bolt.nix
new file mode 100644
index 00000000000..32b60af0603
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/bolt.nix
@@ -0,0 +1,34 @@
+# Thunderbolt 3 device manager
+
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+{
+ options = {
+
+ services.hardware.bolt = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Bolt, a userspace daemon to enable
+ security levels for Thunderbolt 3 on GNU/Linux.
+
+ Bolt is used by GNOME 3 to handle Thunderbolt settings.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf config.services.hardware.bolt.enable {
+
+ environment.systemPackages = [ pkgs.bolt ];
+ services.udev.packages = [ pkgs.bolt ];
+ systemd.packages = [ pkgs.bolt ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/brltty.nix b/nixpkgs/nixos/modules/services/hardware/brltty.nix
new file mode 100644
index 00000000000..1266e8f81e5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/brltty.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.brltty;
+
+in {
+
+ options = {
+
+ services.brltty.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the BRLTTY daemon.";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.brltty = {
+ description = "Braille Device Support";
+ unitConfig = {
+ Documentation = "http://mielke.cc/brltty/";
+ DefaultDependencies = "no";
+ RequiresMountsFor = "${pkgs.brltty}/var/lib/brltty";
+ };
+ serviceConfig = {
+ ExecStart = "${pkgs.brltty}/bin/brltty --no-daemon";
+ Type = "notify";
+ TimeoutStartSec = 5;
+ TimeoutStopSec = 10;
+ Restart = "always";
+ RestartSec = 30;
+ Nice = -10;
+ OOMScoreAdjust = -900;
+ ProtectHome = "read-only";
+ ProtectSystem = "full";
+ SystemCallArchitectures = "native";
+ };
+ wants = [ "systemd-udev-settle.service" ];
+ after = [ "local-fs.target" "systemd-udev-settle.service" ];
+ before = [ "sysinit.target" ];
+ wantedBy = [ "sysinit.target" ];
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/freefall.nix b/nixpkgs/nixos/modules/services/hardware/freefall.nix
new file mode 100644
index 00000000000..83f1e8c84f2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/freefall.nix
@@ -0,0 +1,64 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.freefall;
+
+in {
+
+ options.services.freefall = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to protect HP/Dell laptop hard drives (not SSDs) in free fall.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.freefall;
+ defaultText = "pkgs.freefall";
+ description = ''
+ freefall derivation to use.
+ '';
+ };
+
+ devices = mkOption {
+ type = types.listOf types.str;
+ default = [ "/dev/sda" ];
+ description = ''
+ Device paths to all internal spinning hard drives.
+ '';
+ };
+
+ };
+
+ config = let
+
+ mkService = dev:
+ assert dev != "";
+ let dev' = utils.escapeSystemdPath dev; in
+ nameValuePair "freefall-${dev'}" {
+ description = "Free-fall protection for ${dev}";
+ after = [ "${dev'}.device" ];
+ wantedBy = [ "${dev'}.device" ];
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/freefall ${dev}";
+ Restart = "on-failure";
+ Type = "forking";
+ };
+ };
+
+ in mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services = builtins.listToAttrs (map mkService cfg.devices);
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/fwupd.nix b/nixpkgs/nixos/modules/services/hardware/fwupd.nix
new file mode 100644
index 00000000000..6c341bcbf24
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/fwupd.nix
@@ -0,0 +1,127 @@
+# fwupd daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.fwupd;
+ originalEtc =
+ let
+ mkEtcFile = n: nameValuePair n { source = "${cfg.package}/etc/${n}"; };
+ in listToAttrs (map mkEtcFile cfg.package.filesInstalledToEtc);
+ extraTrustedKeys =
+ let
+ mkName = p: "pki/fwupd/${baseNameOf (toString p)}";
+ mkEtcFile = p: nameValuePair (mkName p) { source = p; };
+ in listToAttrs (map mkEtcFile cfg.extraTrustedKeys);
+
+ # We cannot include the file in $out and rely on filesInstalledToEtc
+ # to install it because it would create a cyclic dependency between
+ # the outputs. We also need to enable the remote,
+ # which should not be done by default.
+ testRemote = if cfg.enableTestRemote then {
+ "fwupd/remotes.d/fwupd-tests.conf" = {
+ source = pkgs.runCommand "fwupd-tests-enabled.conf" {} ''
+ sed "s,^Enabled=false,Enabled=true," \
+ "${cfg.package.installedTests}/etc/fwupd/remotes.d/fwupd-tests.conf" > "$out"
+ '';
+ };
+ } else {};
+in {
+
+ ###### interface
+ options = {
+ services.fwupd = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable fwupd, a DBus service that allows
+ applications to update firmware.
+ '';
+ };
+
+ blacklistDevices = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ];
+ description = ''
+ Allow blacklisting specific devices by their GUID
+ '';
+ };
+
+ blacklistPlugins = mkOption {
+ type = types.listOf types.str;
+ default = [ "test" ];
+ example = [ "udev" ];
+ description = ''
+ Allow blacklisting specific plugins
+ '';
+ };
+
+ extraTrustedKeys = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ example = literalExample "[ /etc/nixos/fwupd/myfirmware.pem ]";
+ description = ''
+ Installing a public key allows firmware signed with a matching private key to be recognized as trusted, which may require less authentication to install than for untrusted files. By default trusted firmware can be upgraded (but not downgraded) without the user or administrator password. Only very few keys are installed by default.
+ '';
+ };
+
+ enableTestRemote = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable test remote. This is used by
+ <link xlink:href="https://github.com/hughsie/fwupd/blob/master/data/installed-tests/README.md">installed tests</link>.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.fwupd;
+ description = ''
+ Which fwupd package to use.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ cfg.package ];
+
+ environment.etc = {
+ "fwupd/daemon.conf" = {
+ source = pkgs.writeText "daemon.conf" ''
+ [fwupd]
+ BlacklistDevices=${lib.concatStringsSep ";" cfg.blacklistDevices}
+ BlacklistPlugins=${lib.concatStringsSep ";" cfg.blacklistPlugins}
+ '';
+ };
+ "fwupd/uefi.conf" = {
+ source = pkgs.writeText "uefi.conf" ''
+ [uefi]
+ OverrideESPMountPoint=${config.boot.loader.efi.efiSysMountPoint}
+ '';
+ };
+
+ } // originalEtc // extraTrustedKeys // testRemote;
+
+ services.dbus.packages = [ cfg.package ];
+
+ services.udev.packages = [ cfg.package ];
+
+ systemd.packages = [ cfg.package ];
+
+ systemd.tmpfiles.rules = [
+ "d /var/lib/fwupd 0755 root root -"
+ ];
+ };
+
+ meta = {
+ maintainers = pkgs.fwupd.meta.maintainers;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/illum.nix b/nixpkgs/nixos/modules/services/hardware/illum.nix
new file mode 100644
index 00000000000..ff73c99a653
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/illum.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.illum;
+in {
+
+ options = {
+
+ services.illum = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable illum, a daemon for controlling screen brightness with brightness buttons.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.illum = {
+ description = "Backlight Adjustment Service";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${pkgs.illum}/bin/illum-d";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/interception-tools.nix b/nixpkgs/nixos/modules/services/hardware/interception-tools.nix
new file mode 100644
index 00000000000..fadcb19a016
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/interception-tools.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.interception-tools;
+in {
+ options.services.interception-tools = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the interception tools service.";
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.package;
+ default = [ pkgs.interception-tools-plugins.caps2esc ];
+ description = ''
+ A list of interception tools plugins that will be made available to use
+ inside the udevmon configuration.
+ '';
+ };
+
+ udevmonConfig = mkOption {
+ type = types.either types.str types.path;
+ default = ''
+ - JOB: "intercept -g $DEVNODE | caps2esc | uinput -d $DEVNODE"
+ DEVICE:
+ EVENTS:
+ EV_KEY: [KEY_CAPSLOCK, KEY_ESC]
+ '';
+ example = ''
+ - JOB: "intercept -g $DEVNODE | y2z | x2y | uinput -d $DEVNODE"
+ DEVICE:
+ EVENTS:
+ EV_KEY: [KEY_X, KEY_Y]
+ '';
+ description = ''
+ String of udevmon YAML configuration, or path to a udevmon YAML
+ configuration file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.interception-tools = {
+ description = "Interception tools";
+ path = [ pkgs.bash pkgs.interception-tools ] ++ cfg.plugins;
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.interception-tools}/bin/udevmon -c \
+ ${if builtins.typeOf cfg.udevmonConfig == "path"
+ then cfg.udevmonConfig
+ else pkgs.writeText "udevmon.yaml" cfg.udevmonConfig}
+ '';
+ Nice = -20;
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/irqbalance.nix b/nixpkgs/nixos/modules/services/hardware/irqbalance.nix
new file mode 100644
index 00000000000..b139154432c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/irqbalance.nix
@@ -0,0 +1,30 @@
+#
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.irqbalance;
+
+in
+{
+ options.services.irqbalance.enable = mkEnableOption "irqbalance daemon";
+
+ config = mkIf cfg.enable {
+
+ systemd.services = {
+ irqbalance = {
+ description = "irqbalance daemon";
+ path = [ pkgs.irqbalance ];
+ serviceConfig =
+ { ExecStart = "${pkgs.irqbalance}/bin/irqbalance --foreground"; };
+ wantedBy = [ "multi-user.target" ];
+ };
+ };
+
+ environment.systemPackages = [ pkgs.irqbalance ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/lcd.nix b/nixpkgs/nixos/modules/services/hardware/lcd.nix
new file mode 100644
index 00000000000..d78d742cd31
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/lcd.nix
@@ -0,0 +1,172 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.hardware.lcd;
+ pkg = lib.getBin pkgs.lcdproc;
+
+ serverCfg = pkgs.writeText "lcdd.conf" ''
+ [server]
+ DriverPath=${pkg}/lib/lcdproc/
+ ReportToSyslog=false
+ Bind=${cfg.serverHost}
+ Port=${toString cfg.serverPort}
+ ${cfg.server.extraConfig}
+ '';
+
+ clientCfg = pkgs.writeText "lcdproc.conf" ''
+ [lcdproc]
+ Server=${cfg.serverHost}
+ Port=${toString cfg.serverPort}
+ ReportToSyslog=false
+ ${cfg.client.extraConfig}
+ '';
+
+ serviceCfg = {
+ DynamicUser = true;
+ Restart = "on-failure";
+ Slice = "lcd.slice";
+ };
+
+in with lib; {
+
+ meta.maintainers = with maintainers; [ peterhoeg ];
+
+ options = with types; {
+ services.hardware.lcd = {
+ serverHost = mkOption {
+ type = str;
+ default = "localhost";
+ description = "Host on which LCDd is listening.";
+ };
+
+ serverPort = mkOption {
+ type = int;
+ default = 13666;
+ description = "Port on which LCDd is listening.";
+ };
+
+ server = {
+ enable = mkOption {
+ type = bool;
+ default = false;
+ description = "Enable the LCD panel server (LCDd)";
+ };
+
+ openPorts = mkOption {
+ type = bool;
+ default = false;
+ description = "Open the ports in the firewall";
+ };
+
+ usbPermissions = mkOption {
+ type = bool;
+ default = false;
+ description = ''
+ Set group-write permissions on a USB device.
+ </para>
+ <para>
+ A USB connected LCD panel will most likely require having its
+ permissions modified for lcdd to write to it. Enabling this option
+ sets group-write permissions on the device identified by
+ <option>services.hardware.lcd.usbVid</option> and
+ <option>services.hardware.lcd.usbPid</option>. In order to find the
+ values, you can run the <command>lsusb</command> command. Example
+ output:
+ </para>
+ <para>
+ <literal>
+ Bus 005 Device 002: ID 0403:c630 Future Technology Devices International, Ltd lcd2usb interface
+ </literal>
+ </para>
+ <para>
+ In this case the vendor id is 0403 and the product id is c630.
+ '';
+ };
+
+ usbVid = mkOption {
+ type = str;
+ default = "";
+ description = "The vendor ID of the USB device to claim.";
+ };
+
+ usbPid = mkOption {
+ type = str;
+ default = "";
+ description = "The product ID of the USB device to claim.";
+ };
+
+ usbGroup = mkOption {
+ type = str;
+ default = "dialout";
+ description = "The group to use for settings permissions. This group must exist or you will have to create it.";
+ };
+
+ extraConfig = mkOption {
+ type = lines;
+ default = "";
+ description = "Additional configuration added verbatim to the server config.";
+ };
+ };
+
+ client = {
+ enable = mkOption {
+ type = bool;
+ default = false;
+ description = "Enable the LCD panel client (LCDproc)";
+ };
+
+ extraConfig = mkOption {
+ type = lines;
+ default = "";
+ description = "Additional configuration added verbatim to the client config.";
+ };
+
+ restartForever = mkOption {
+ type = bool;
+ default = true;
+ description = "Try restarting the client forever.";
+ };
+ };
+ };
+ };
+
+ config = mkIf (cfg.server.enable || cfg.client.enable) {
+ networking.firewall.allowedTCPPorts = mkIf (cfg.server.enable && cfg.server.openPorts) [ cfg.serverPort ];
+
+ services.udev.extraRules = mkIf (cfg.server.enable && cfg.server.usbPermissions) ''
+ ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="${cfg.server.usbVid}", ATTRS{idProduct}=="${cfg.server.usbPid}", MODE="660", GROUP="${cfg.server.usbGroup}"
+ '';
+
+ systemd.services = {
+ lcdd = mkIf cfg.server.enable {
+ description = "LCDproc - server";
+ wantedBy = [ "lcd.target" ];
+ serviceConfig = serviceCfg // {
+ ExecStart = "${pkg}/bin/LCDd -f -c ${serverCfg}";
+ SupplementaryGroups = cfg.server.usbGroup;
+ };
+ };
+
+ lcdproc = mkIf cfg.client.enable {
+ description = "LCDproc - client";
+ after = [ "lcdd.service" ];
+ wantedBy = [ "lcd.target" ];
+ serviceConfig = serviceCfg // {
+ ExecStart = "${pkg}/bin/lcdproc -f -c ${clientCfg}";
+ # If the server is being restarted at the same time, the client will
+ # fail as it cannot connect, so space it out a bit.
+ RestartSec = "5";
+ # Allow restarting for eternity
+ StartLimitIntervalSec = lib.mkIf cfg.client.restartForever "0";
+ StartLimitBurst = lib.mkIf cfg.client.restartForever "0";
+ };
+ };
+ };
+
+ systemd.targets.lcd = {
+ description = "LCD client/server";
+ after = [ "lcdd.service" "lcdproc.service" ];
+ wantedBy = [ "multi-user.target" ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/lirc.nix b/nixpkgs/nixos/modules/services/hardware/lirc.nix
new file mode 100644
index 00000000000..826e512c75d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/lirc.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.lirc;
+in {
+
+ ###### interface
+
+ options = {
+ services.lirc = {
+
+ enable = mkEnableOption "LIRC daemon";
+
+ options = mkOption {
+ type = types.lines;
+ example = ''
+ [lircd]
+ nodaemon = False
+ '';
+ description = "LIRC default options descriped in man:lircd(8) (<filename>lirc_options.conf</filename>)";
+ };
+
+ configs = mkOption {
+ type = types.listOf types.lines;
+ description = "Configurations for lircd to load, see man:lircd.conf(5) for details (<filename>lircd.conf</filename>)";
+ };
+
+ extraArguments = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Extra arguments to lircd.";
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ # Note: LIRC executables raises a warning, if lirc_options.conf do not exists
+ environment.etc."lirc/lirc_options.conf".text = cfg.options;
+
+ passthru.lirc.socket = "/run/lirc/lircd";
+
+ environment.systemPackages = [ pkgs.lirc ];
+
+ systemd.sockets.lircd = {
+ description = "LIRC daemon socket";
+ wantedBy = [ "sockets.target" ];
+ socketConfig = {
+ ListenStream = config.passthru.lirc.socket;
+ SocketUser = "lirc";
+ SocketMode = "0660";
+ };
+ };
+
+ systemd.services.lircd = let
+ configFile = pkgs.writeText "lircd.conf" (builtins.concatStringsSep "\n" cfg.configs);
+ in {
+ description = "LIRC daemon service";
+ after = [ "network.target" ];
+
+ unitConfig.Documentation = [ "man:lircd(8)" ];
+
+ serviceConfig = {
+ RuntimeDirectory = "lirc";
+
+ # Service runtime directory and socket share same folder.
+ # Following hacks are necessary to get everything right:
+
+ # 1. prevent socket deletion during stop and restart
+ RuntimeDirectoryPreserve = true;
+
+ # 2. fix runtime folder owner-ship, happens when socket activation
+ # creates the folder
+ PermissionsStartOnly = true;
+ ExecStartPre = [
+ "${pkgs.coreutils}/bin/chown lirc /run/lirc/"
+ ];
+
+ ExecStart = ''
+ ${pkgs.lirc}/bin/lircd --nodaemon \
+ ${escapeShellArgs cfg.extraArguments} \
+ ${configFile}
+ '';
+ User = "lirc";
+ };
+ };
+
+ users.users.lirc = {
+ uid = config.ids.uids.lirc;
+ group = "lirc";
+ description = "LIRC user for lircd";
+ };
+
+ users.groups.lirc.gid = config.ids.gids.lirc;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/nvidia-optimus.nix b/nixpkgs/nixos/modules/services/hardware/nvidia-optimus.nix
new file mode 100644
index 00000000000..d53175052c7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/nvidia-optimus.nix
@@ -0,0 +1,43 @@
+{ config, lib, ... }:
+
+let kernel = config.boot.kernelPackages; in
+
+{
+
+ ###### interface
+
+ options = {
+
+ hardware.nvidiaOptimus.disable = lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ description = ''
+ Completely disable the NVIDIA graphics card and use the
+ integrated graphics processor instead.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = lib.mkIf config.hardware.nvidiaOptimus.disable {
+ boot.blacklistedKernelModules = ["nouveau" "nvidia" "nvidiafb" "nvidia-drm"];
+ boot.kernelModules = [ "bbswitch" ];
+ boot.extraModulePackages = [ kernel.bbswitch ];
+
+ systemd.services.bbswitch = {
+ description = "Disable NVIDIA Card";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${kernel.bbswitch}/bin/discrete_vga_poweroff";
+ ExecStop = "${kernel.bbswitch}/bin/discrete_vga_poweron";
+ };
+ path = [ kernel.bbswitch ];
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/pcscd.nix b/nixpkgs/nixos/modules/services/hardware/pcscd.nix
new file mode 100644
index 00000000000..f3fc4c3cc79
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/pcscd.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfgFile = pkgs.writeText "reader.conf" config.services.pcscd.readerConfig;
+
+ pluginEnv = pkgs.buildEnv {
+ name = "pcscd-plugins";
+ paths = map (p: "${p}/pcsc/drivers") config.services.pcscd.plugins;
+ };
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.pcscd = {
+ enable = mkEnableOption "PCSC-Lite daemon";
+
+ plugins = mkOption {
+ type = types.listOf types.package;
+ default = [ pkgs.ccid ];
+ defaultText = "[ pkgs.ccid ]";
+ example = literalExample "[ pkgs.pcsc-cyberjack ]";
+ description = "Plugin packages to be used for PCSC-Lite.";
+ };
+
+ readerConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ FRIENDLYNAME "Some serial reader"
+ DEVICENAME /dev/ttyS0
+ LIBPATH /path/to/serial_reader.so
+ CHANNELID 1
+ '';
+ description = ''
+ Configuration for devices that aren't hotpluggable.
+
+ See <citerefentry><refentrytitle>reader.conf</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for valid options.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.pcscd.enable {
+
+ systemd.sockets.pcscd = {
+ description = "PCSC-Lite Socket";
+ wantedBy = [ "sockets.target" ];
+ before = [ "multi-user.target" ];
+ socketConfig.ListenStream = "/run/pcscd/pcscd.comm";
+ };
+
+ systemd.services.pcscd = {
+ description = "PCSC-Lite daemon";
+ environment.PCSCLITE_HP_DROPDIR = pluginEnv;
+ serviceConfig = {
+ ExecStart = "${getBin pkgs.pcsclite}/sbin/pcscd -f -x -c ${cfgFile}";
+ ExecReload = "${getBin pkgs.pcsclite}/sbin/pcscd -H";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/pommed.nix b/nixpkgs/nixos/modules/services/hardware/pommed.nix
new file mode 100644
index 00000000000..bf7d6a46a29
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/pommed.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.hardware.pommed;
+ defaultConf = "${pkgs.pommed_light}/etc/pommed.conf.mactel";
+in {
+
+ options = {
+
+ services.hardware.pommed = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use the pommed tool to handle Apple laptop
+ keyboard hotkeys.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The path to the <filename>pommed.conf</filename> file. Leave
+ to null to use the default config file
+ (<filename>/etc/pommed.conf.mactel</filename>). See the
+ files <filename>/etc/pommed.conf.mactel</filename> and
+ <filename>/etc/pommed.conf.pmac</filename> for examples to
+ build on.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.polkit pkgs.pommed_light ];
+
+ environment.etc."pommed.conf".source =
+ if cfg.configFile == null then defaultConf else cfg.configFile;
+
+ systemd.services.pommed = {
+ description = "Pommed Apple Hotkeys Daemon";
+ wantedBy = [ "multi-user.target" ];
+ script = "${pkgs.pommed_light}/bin/pommed -f";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/ratbagd.nix b/nixpkgs/nixos/modules/services/hardware/ratbagd.nix
new file mode 100644
index 00000000000..103e1d2315a
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/hardware/sane.nix b/nixpkgs/nixos/modules/services/hardware/sane.nix
new file mode 100644
index 00000000000..b344dfc2061
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/sane.nix
@@ -0,0 +1,162 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ pkg = if config.hardware.sane.snapshot
+ then pkgs.sane-backends-git
+ else pkgs.sane-backends;
+
+ sanedConf = pkgs.writeTextFile {
+ name = "saned.conf";
+ destination = "/etc/sane.d/saned.conf";
+ text = ''
+ localhost
+ ${config.services.saned.extraConfig}
+ '';
+ };
+
+ netConf = pkgs.writeTextFile {
+ name = "net.conf";
+ destination = "/etc/sane.d/net.conf";
+ text = ''
+ ${lib.optionalString config.services.saned.enable "localhost"}
+ ${config.hardware.sane.netConf}
+ '';
+ };
+
+ env = {
+ SANE_CONFIG_DIR = config.hardware.sane.configDir;
+ LD_LIBRARY_PATH = [ "${saneConfig}/lib/sane" ];
+ };
+
+ backends = [ pkg netConf ] ++ optional config.services.saned.enable sanedConf ++ config.hardware.sane.extraBackends;
+ saneConfig = pkgs.mkSaneConfig { paths = backends; };
+
+ enabled = config.hardware.sane.enable || config.services.saned.enable;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ hardware.sane.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable support for SANE scanners.
+
+ <note><para>
+ Users in the "scanner" group will gain access to the scanner, or the "lp" group if it's also a printer.
+ </para></note>
+ '';
+ };
+
+ hardware.sane.snapshot = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Use a development snapshot of SANE scanner drivers.";
+ };
+
+ hardware.sane.extraBackends = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ Packages providing extra SANE backends to enable.
+
+ <note><para>
+ The example contains the package for HP scanners.
+ </para></note>
+ '';
+ example = literalExample "[ pkgs.hplipWithPlugin ]";
+ };
+
+ hardware.sane.configDir = mkOption {
+ type = types.str;
+ internal = true;
+ description = "The value of SANE_CONFIG_DIR.";
+ };
+
+ hardware.sane.netConf = mkOption {
+ type = types.lines;
+ default = "";
+ example = "192.168.0.16";
+ description = ''
+ Network hosts that should be probed for remote scanners.
+ '';
+ };
+
+ services.saned.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable saned network daemon for remote connection to scanners.
+
+ saned would be runned from <literal>scanner</literal> user; to allow
+ access to hardware that doesn't have <literal>scanner</literal> group
+ you should add needed groups to this user.
+ '';
+ };
+
+ services.saned.extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "192.168.0.0/24";
+ description = ''
+ Extra saned configuration lines.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkMerge [
+ (mkIf enabled {
+ hardware.sane.configDir = mkDefault "${saneConfig}/etc/sane.d";
+
+ environment.systemPackages = backends;
+ environment.sessionVariables = env;
+ services.udev.packages = backends;
+
+ users.groups.scanner.gid = config.ids.gids.scanner;
+ })
+
+ (mkIf config.services.saned.enable {
+ networking.firewall.connectionTrackingModules = [ "sane" ];
+
+ systemd.services."saned@" = {
+ description = "Scanner Service";
+ environment = mapAttrs (name: val: toString val) env;
+ serviceConfig = {
+ User = "scanner";
+ Group = "scanner";
+ ExecStart = "${pkg}/bin/saned";
+ };
+ };
+
+ systemd.sockets.saned = {
+ description = "saned incoming socket";
+ wantedBy = [ "sockets.target" ];
+ listenStreams = [ "0.0.0.0:6566" "[::]:6566" ];
+ socketConfig = {
+ # saned needs to distinguish between IPv4 and IPv6 to open matching data sockets.
+ BindIPv6Only = "ipv6-only";
+ Accept = true;
+ MaxConnections = 1;
+ };
+ };
+
+ users.users.scanner = {
+ uid = config.ids.uids.scanner;
+ group = "scanner";
+ };
+ })
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
new file mode 100644
index 00000000000..f6ed4e25e9c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.hardware.sane.brscan4;
+
+ netDeviceList = attrValues cfg.netDevices;
+
+ etcFiles = pkgs.callPackage ./brscan4_etc_files.nix { netDevices = netDeviceList; };
+
+ netDeviceOpts = { name, ... }: {
+
+ options = {
+
+ name = mkOption {
+ type = types.str;
+ description = ''
+ The friendly name you give to the network device. If undefined,
+ the name of attribute will be used.
+ '';
+
+ example = literalExample "office1";
+ };
+
+ model = mkOption {
+ type = types.str;
+ description = ''
+ The model of the network device.
+ '';
+
+ example = literalExample "MFC-7860DW";
+ };
+
+ ip = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ The ip address of the device. If undefined, you will have to
+ provide a nodename.
+ '';
+
+ example = literalExample "192.168.1.2";
+ };
+
+ nodename = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ The node name of the device. If undefined, you will have to
+ provide an ip.
+ '';
+
+ example = literalExample "BRW0080927AFBCE";
+ };
+
+ };
+
+
+ config =
+ { name = mkDefault name;
+ };
+ };
+
+in
+
+{
+ options = {
+
+ hardware.sane.brscan4.enable =
+ mkEnableOption "Brother's brscan4 scan backend" // {
+ description = ''
+ When enabled, will automatically register the "brscan4" sane
+ backend and bring configuration files to their expected location.
+ '';
+ };
+
+ hardware.sane.brscan4.netDevices = mkOption {
+ default = {};
+ example =
+ { office1 = { model = "MFC-7860DW"; ip = "192.168.1.2"; };
+ office2 = { model = "MFC-7860DW"; nodename = "BRW0080927AFBCE"; };
+ };
+ type = with types; loaOf (submodule netDeviceOpts);
+ description = ''
+ The list of network devices that will be registered against the brscan4
+ sane backend.
+ '';
+ };
+ };
+
+ config = mkIf (config.hardware.sane.enable && cfg.enable) {
+
+ hardware.sane.extraBackends = [
+ pkgs.brscan4
+ ];
+
+ environment.etc = singleton {
+ target = "opt/brother/scanner/brscan4";
+ source = "${etcFiles}/etc/opt/brother/scanner/brscan4";
+ };
+
+ assertions = [
+ { assertion = all (x: !(null != x.ip && null != x.nodename)) netDeviceList;
+
+ message = ''
+ When describing a network device as part of the attribute list
+ `hardware.sane.brscan4.netDevices`, only one of its `ip` or `nodename`
+ attribute should be specified, not both!
+ '';
+ }
+ ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
new file mode 100644
index 00000000000..6bf31982b71
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
@@ -0,0 +1,71 @@
+{ stdenv, lib, brscan4, netDevices ? [] }:
+
+/*
+
+Testing
+-------
+
+No net devices:
+
+~~~
+nix-shell -E 'with import <nixpkgs> { }; brscan4-etc-files'
+~~~
+
+Two net devices:
+
+~~~
+nix-shell -E 'with import <nixpkgs> { }; brscan4-etc-files.override{netDevices=[{name="a"; model="MFC-7860DW"; nodename="BRW0080927AFBCE";} {name="b"; model="MFC-7860DW"; ip="192.168.1.2";}];}'
+~~~
+
+*/
+
+with lib;
+
+let
+
+ addNetDev = nd: ''
+ brsaneconfig4 -a \
+ name="${nd.name}" \
+ model="${nd.model}" \
+ ${if (hasAttr "nodename" nd && nd.nodename != null) then
+ ''nodename="${nd.nodename}"'' else
+ ''ip="${nd.ip}"''}'';
+ addAllNetDev = xs: concatStringsSep "\n" (map addNetDev xs);
+in
+
+stdenv.mkDerivation {
+
+ name = "brscan4-etc-files-0.4.3-3";
+ src = "${brscan4}/opt/brother/scanner/brscan4";
+
+ nativeBuildInputs = [ brscan4 ];
+
+ dontConfigure = true;
+
+ buildPhase = ''
+ TARGET_DIR="$out/etc/opt/brother/scanner/brscan4"
+ mkdir -p "$TARGET_DIR"
+ cp -rp "./models4" "$TARGET_DIR"
+ cp -rp "./Brsane4.ini" "$TARGET_DIR"
+ cp -rp "./brsanenetdevice4.cfg" "$TARGET_DIR"
+
+ export BRSANENETDEVICE4_CFG_FILENAME="$TARGET_DIR/brsanenetdevice4.cfg"
+
+ printf '${addAllNetDev netDevices}\n'
+
+ ${addAllNetDev netDevices}
+ '';
+
+ installPhase = ":";
+
+ dontStrip = true;
+ dontPatchELF = true;
+
+ meta = {
+ description = "Brother brscan4 sane backend driver etc files";
+ homepage = http://www.brother.com;
+ platforms = stdenv.lib.platforms.linux;
+ license = stdenv.lib.licenses.unfree;
+ maintainers = with stdenv.lib.maintainers; [ jraygauthier ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix
new file mode 100644
index 00000000000..d71a17f5ea6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options = {
+
+ hardware.sane.dsseries.enable =
+ mkEnableOption "Brother DSSeries scan backend" // {
+ description = ''
+ When enabled, will automatically register the "dsseries" SANE backend.
+
+ This supports the Brother DSmobile scanner series, including the
+ DS-620, DS-720D, DS-820W, and DS-920DW scanners.
+ '';
+ };
+ };
+
+ config = mkIf (config.hardware.sane.enable && config.hardware.sane.dsseries.enable) {
+
+ hardware.sane.extraBackends = [ pkgs.dsseries ];
+ services.udev.packages = [ pkgs.dsseries ];
+ boot.kernelModules = [ "sg" ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/tcsd.nix b/nixpkgs/nixos/modules/services/hardware/tcsd.nix
new file mode 100644
index 00000000000..3876280ee6b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/tcsd.nix
@@ -0,0 +1,151 @@
+# tcsd daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+
+ cfg = config.services.tcsd;
+
+ tcsdConf = pkgs.writeText "tcsd.conf" ''
+ port = 30003
+ num_threads = 10
+ system_ps_file = ${cfg.stateDir}/system.data
+ # This is the log of each individual measurement done by the system.
+ # By re-calculating the PCR registers based on this information, even
+ # finer details about the measured environment can be inferred than
+ # what is available directly from the PCR registers.
+ firmware_log_file = /sys/kernel/security/tpm0/binary_bios_measurements
+ kernel_log_file = /sys/kernel/security/ima/binary_runtime_measurements
+ firmware_pcrs = ${cfg.firmwarePCRs}
+ kernel_pcrs = ${cfg.kernelPCRs}
+ platform_cred = ${cfg.platformCred}
+ conformance_cred = ${cfg.conformanceCred}
+ endorsement_cred = ${cfg.endorsementCred}
+ #remote_ops = create_key,random
+ #host_platform_class = server_12
+ #all_platform_classes = pc_11,pc_12,mobile_12
+ '';
+
+in
+{
+
+ ###### interface
+
+ options = {
+
+ services.tcsd = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable tcsd, a Trusted Computing management service
+ that provides TCG Software Stack (TSS). The tcsd daemon is
+ the only portal to the Trusted Platform Module (TPM), a hardware
+ chip on the motherboard.
+ '';
+ };
+
+ user = mkOption {
+ default = "tss";
+ type = types.str;
+ description = "User account under which tcsd runs.";
+ };
+
+ group = mkOption {
+ default = "tss";
+ type = types.str;
+ description = "Group account under which tcsd runs.";
+ };
+
+ stateDir = mkOption {
+ default = "/var/lib/tpm";
+ type = types.path;
+ description = ''
+ The location of the system persistent storage file.
+ The system persistent storage file holds keys and data across
+ restarts of the TCSD and system reboots.
+ '';
+ };
+
+ firmwarePCRs = mkOption {
+ default = "0,1,2,3,4,5,6,7";
+ type = types.str;
+ description = "PCR indices used in the TPM for firmware measurements.";
+ };
+
+ kernelPCRs = mkOption {
+ default = "8,9,10,11,12";
+ type = types.str;
+ description = "PCR indices used in the TPM for kernel measurements.";
+ };
+
+ platformCred = mkOption {
+ default = "${cfg.stateDir}/platform.cert";
+ type = types.path;
+ description = ''
+ Path to the platform credential for your TPM. Your TPM
+ manufacturer may have provided you with a set of credentials
+ (certificates) that should be used when creating identities
+ using your TPM. When a user of your TPM makes an identity,
+ this credential will be encrypted as part of that process.
+ See the 1.1b TPM Main specification section 9.3 for information
+ on this process. '';
+ };
+
+ conformanceCred = mkOption {
+ default = "${cfg.stateDir}/conformance.cert";
+ type = types.path;
+ description = ''
+ Path to the conformance credential for your TPM.
+ See also the platformCred option'';
+ };
+
+ endorsementCred = mkOption {
+ default = "${cfg.stateDir}/endorsement.cert";
+ type = types.path;
+ description = ''
+ Path to the endorsement credential for your TPM.
+ See also the platformCred option'';
+ };
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.trousers ];
+
+# system.activationScripts.tcsd =
+# ''
+# chown ${cfg.user}:${cfg.group} ${tcsdConf}
+# '';
+
+ systemd.services.tcsd = {
+ description = "TCSD";
+ after = [ "systemd-udev-settle.service" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.trousers ];
+ preStart =
+ ''
+ mkdir -m 0700 -p ${cfg.stateDir}
+ chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+ '';
+ serviceConfig.ExecStart = "${pkgs.trousers}/sbin/tcsd -f -c ${tcsdConf}";
+ };
+
+ users.users = optionalAttrs (cfg.user == "tss") (singleton
+ { name = "tss";
+ group = "tss";
+ uid = config.ids.uids.tss;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "tss") (singleton
+ { name = "tss";
+ gid = config.ids.gids.tss;
+ });
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/thermald.nix b/nixpkgs/nixos/modules/services/hardware/thermald.nix
new file mode 100644
index 00000000000..69577bbe018
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/thermald.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.thermald;
+in {
+ ###### interface
+ options = {
+ services.thermald = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable thermald, the temperature management daemon.
+ '';
+ };
+
+ debug = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable debug logging.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "the thermald manual configuration file.";
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.dbus.packages = [ pkgs.thermald ];
+
+ systemd.services.thermald = {
+ description = "Thermal Daemon Service";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.thermald}/sbin/thermald \
+ --no-daemon \
+ ${optionalString cfg.debug "--loglevel=debug"} \
+ ${optionalString (cfg.configFile != null) "--config-file ${cfg.configFile}"} \
+ --dbus-enable
+ '';
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/thinkfan.nix b/nixpkgs/nixos/modules/services/hardware/thinkfan.nix
new file mode 100644
index 00000000000..7c105e99ca5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/thinkfan.nix
@@ -0,0 +1,152 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.thinkfan;
+ configFile = pkgs.writeText "thinkfan.conf" ''
+ # ATTENTION: There is only very basic sanity checking on the configuration.
+ # That means you can set your temperature limits as insane as you like. You
+ # can do anything stupid, e.g. turn off your fan when your CPU reaches 70°C.
+ #
+ # That's why this program is called THINKfan: You gotta think for yourself.
+ #
+ ######################################################################
+ #
+ # IBM/Lenovo Thinkpads (thinkpad_acpi, /proc/acpi/ibm)
+ # ====================================================
+ #
+ # IMPORTANT:
+ #
+ # To keep your HD from overheating, you have to specify a correction value for
+ # the sensor that has the HD's temperature. You need to do this because
+ # thinkfan uses only the highest temperature it can find in the system, and
+ # that'll most likely never be your HD, as most HDs are already out of spec
+ # when they reach 55 °C.
+ # Correction values are applied from left to right in the same order as the
+ # temperatures are read from the file.
+ #
+ # For example:
+ # tp_thermal /proc/acpi/ibm/thermal (0, 0, 10)
+ # will add a fixed value of 10 °C the 3rd value read from that file. Check out
+ # http://www.thinkwiki.org/wiki/Thermal_Sensors to find out how much you may
+ # want to add to certain temperatures.
+
+ ${cfg.fan}
+ ${cfg.sensors}
+
+ # Syntax:
+ # (LEVEL, LOW, HIGH)
+ # LEVEL is the fan level to use (0-7 with thinkpad_acpi)
+ # LOW is the temperature at which to step down to the previous level
+ # HIGH is the temperature at which to step up to the next level
+ # All numbers are integers.
+ #
+
+ ${cfg.levels}
+ '';
+
+ thinkfan = pkgs.thinkfan.override { smartSupport = cfg.smartSupport; };
+
+in {
+
+ options = {
+
+ services.thinkfan = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable thinkfan, fan controller for IBM/Lenovo ThinkPads.
+ '';
+ };
+
+ smartSupport = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to build thinkfan with SMART support to read temperatures
+ directly from hard disks.
+ '';
+ };
+
+ sensors = mkOption {
+ type = types.lines;
+ default = ''
+ tp_thermal /proc/acpi/ibm/thermal (0,0,10)
+ '';
+ description =''
+ thinkfan can read temperatures from three possible sources:
+
+ /proc/acpi/ibm/thermal
+ Which is provided by the thinkpad_acpi kernel
+ module (keyword tp_thermal)
+
+ /sys/class/hwmon/*/temp*_input
+ Which may be provided by any hwmon drivers (keyword
+ hwmon)
+
+ S.M.A.R.T. (requires smartSupport to be enabled)
+ Which reads the temperature directly from the hard
+ disk using libatasmart (keyword atasmart)
+
+ Multiple sensors may be added, in which case they will be
+ numbered in their order of appearance.
+ '';
+ };
+
+ fan = mkOption {
+ type = types.str;
+ default = "tp_fan /proc/acpi/ibm/fan";
+ description =''
+ Specifies the fan we want to use.
+ On anything other than a Thinkpad you'll probably
+ use some PWM control file in /sys/class/hwmon.
+ A sysfs fan would be specified like this:
+ pwm_fan /sys/class/hwmon/hwmon2/device/pwm1
+ '';
+ };
+
+ levels = mkOption {
+ type = types.lines;
+ default = ''
+ (0, 0, 55)
+ (1, 48, 60)
+ (2, 50, 61)
+ (3, 52, 63)
+ (6, 56, 65)
+ (7, 60, 85)
+ (127, 80, 32767)
+ '';
+ description = ''
+ (LEVEL, LOW, HIGH)
+ LEVEL is the fan level to use (0-7 with thinkpad_acpi).
+ LOW is the temperature at which to step down to the previous level.
+ HIGH is the temperature at which to step up to the next level.
+ All numbers are integers.
+ '';
+ };
+
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ thinkfan ];
+
+ systemd.services.thinkfan = {
+ description = "Thinkfan";
+ after = [ "basic.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ thinkfan ];
+ serviceConfig.ExecStart = "${thinkfan}/bin/thinkfan -n -c ${configFile}";
+ };
+
+ boot.extraModprobeConfig = "options thinkpad_acpi experimental=1 fan_control=1";
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/throttled.nix b/nixpkgs/nixos/modules/services/hardware/throttled.nix
new file mode 100644
index 00000000000..7617c4492d7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/throttled.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.throttled;
+in {
+ options = {
+ services.throttled = {
+ enable = mkEnableOption "fix for Intel CPU throttling";
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = "Alternative configuration";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.packages = [ pkgs.throttled ];
+ # The upstream package has this in Install, but that's not enough, see the NixOS manual
+ systemd.services.lenovo_fix.wantedBy = [ "multi-user.target" ];
+
+ environment.etc."lenovo_fix.conf".source =
+ if cfg.extraConfig != ""
+ then pkgs.writeText "lenovo_fix.conf" cfg.extraConfig
+ else "${pkgs.throttled}/etc/lenovo_fix.conf";
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/tlp.nix b/nixpkgs/nixos/modules/services/hardware/tlp.nix
new file mode 100644
index 00000000000..4f8af797828
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/tlp.nix
@@ -0,0 +1,120 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+cfg = config.services.tlp;
+
+enableRDW = config.networking.networkmanager.enable;
+
+tlp = pkgs.tlp.override {
+ inherit enableRDW;
+};
+
+# XXX: We can't use writeTextFile + readFile here because it triggers
+# TLP build to get the .drv (even on --dry-run).
+confFile = pkgs.runCommand "tlp"
+ { config = cfg.extraConfig;
+ passAsFile = [ "config" ];
+ preferLocalBuild = true;
+ }
+ ''
+ cat ${tlp}/etc/default/tlp > $out
+ cat $configPath >> $out
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.tlp = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the TLP daemon.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional configuration variables for TLP";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ powerManagement.scsiLinkPolicy = null;
+ powerManagement.cpuFreqGovernor = null;
+ powerManagement.cpufreq.max = null;
+ powerManagement.cpufreq.min = null;
+
+ systemd.sockets.systemd-rfkill.enable = false;
+
+ systemd.services = {
+ "systemd-rfkill@".enable = false;
+ systemd-rfkill.enable = false;
+
+ tlp = {
+ description = "TLP system startup/shutdown";
+
+ after = [ "multi-user.target" ];
+ wantedBy = [ "multi-user.target" ];
+ before = [ "shutdown.target" ];
+ restartTriggers = [ confFile ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${tlp}/bin/tlp init start";
+ ExecStop = "${tlp}/bin/tlp init stop";
+ };
+ };
+
+ tlp-sleep = {
+ description = "TLP suspend/resume";
+
+ wantedBy = [ "sleep.target" ];
+ before = [ "sleep.target" ];
+
+ unitConfig = {
+ StopWhenUnneeded = true;
+ };
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${tlp}/bin/tlp suspend";
+ ExecStop = "${tlp}/bin/tlp resume";
+ };
+ };
+ };
+
+ services.udev.packages = [ tlp ];
+
+ environment.etc = [{ source = confFile;
+ target = "default/tlp";
+ }
+ ] ++ optional enableRDW {
+ source = "${tlp}/etc/NetworkManager/dispatcher.d/99tlp-rdw-nm";
+ target = "NetworkManager/dispatcher.d/99tlp-rdw-nm";
+ };
+
+ environment.systemPackages = [ tlp ];
+
+ boot.kernelModules = [ "msr" ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/trezord.nix b/nixpkgs/nixos/modules/services/hardware/trezord.nix
new file mode 100644
index 00000000000..62824ed7350
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/trezord.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.trezord;
+in {
+
+ ### docs
+
+ meta = {
+ doc = ./trezord.xml;
+ };
+
+ ### interface
+
+ options = {
+ services.trezord = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable Trezor bridge daemon, for use with Trezor hardware bitcoin wallets.
+ '';
+ };
+
+ emulator.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable Trezor emulator support.
+ '';
+ };
+
+ emulator.port = mkOption {
+ type = types.port;
+ default = 21324;
+ description = ''
+ Listening port for the Trezor emulator.
+ '';
+ };
+ };
+ };
+
+ ### implementation
+
+ config = mkIf cfg.enable {
+ services.udev.packages = lib.singleton (pkgs.writeTextFile {
+ name = "trezord-udev-rules";
+ destination = "/etc/udev/rules.d/51-trezor.rules";
+ text = ''
+ # TREZOR v1 (One)
+ SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
+ KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", GROUP="trezord", TAG+="uaccess"
+
+ # TREZOR v2 (T)
+ SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
+ SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
+ KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="trezord", TAG+="uaccess"
+ '';
+ });
+
+ systemd.services.trezord = {
+ description = "TREZOR Bridge";
+ after = [ "systemd-udev-settle.service" "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [];
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${pkgs.trezord}/bin/trezord-go ${optionalString cfg.emulator.enable "-e ${builtins.toString cfg.emulator.port}"}";
+ User = "trezord";
+ };
+ };
+
+ users.users.trezord = {
+ group = "trezord";
+ description = "Trezor bridge daemon user";
+ };
+
+ users.groups.trezord = {};
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/hardware/trezord.xml b/nixpkgs/nixos/modules/services/hardware/trezord.xml
new file mode 100644
index 00000000000..972d409d9d0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/trezord.xml
@@ -0,0 +1,26 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="trezor">
+ <title>Trezor</title>
+ <para>
+ Trezor is an open-source cryptocurrency hardware wallet and security token
+ allowing secure storage of private keys.
+ </para>
+ <para>
+ It offers advanced features such U2F two-factor authorization, SSH login
+ through
+ <link xlink:href="https://wiki.trezor.io/Apps:SSH_agent">Trezor SSH agent</link>,
+ <link xlink:href="https://wiki.trezor.io/GPG">GPG</link> and a
+ <link xlink:href="https://wiki.trezor.io/Trezor_Password_Manager">password manager</link>.
+ For more information, guides and documentation, see <link xlink:href="https://wiki.trezor.io"/>.
+ </para>
+ <para>
+ To enable Trezor support, add the following to your <filename>configuration.nix</filename>:
+<programlisting>
+<xref linkend="opt-services.trezord.enable"/> = true;
+</programlisting>
+ This will add all necessary udev rules and start Trezor Bridge.
+ </para>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/hardware/triggerhappy.nix b/nixpkgs/nixos/modules/services/hardware/triggerhappy.nix
new file mode 100644
index 00000000000..f9f5234bdc3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/triggerhappy.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.triggerhappy;
+
+ socket = "/run/thd.socket";
+
+ configFile = pkgs.writeText "triggerhappy.conf" ''
+ ${concatMapStringsSep "\n"
+ ({ keys, event, cmd, ... }:
+ ''${concatMapStringsSep "+" (x: "KEY_" + x) keys} ${toString { press = 1; hold = 2; release = 0; }.${event}} ${cmd}''
+ )
+ cfg.bindings}
+ ${cfg.extraConfig}
+ '';
+
+ bindingCfg = { ... }: {
+ options = {
+
+ keys = mkOption {
+ type = types.listOf types.str;
+ description = "List of keys to match. Key names as defined in linux/input-event-codes.h";
+ };
+
+ event = mkOption {
+ type = types.enum ["press" "hold" "release"];
+ default = "press";
+ description = "Event to match.";
+ };
+
+ cmd = mkOption {
+ type = types.str;
+ description = "What to run.";
+ };
+
+ };
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.triggerhappy = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the <command>triggerhappy</command> hotkey daemon.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "nobody";
+ example = "root";
+ description = ''
+ User account under which <command>triggerhappy</command> runs.
+ '';
+ };
+
+ bindings = mkOption {
+ type = types.listOf (types.submodule bindingCfg);
+ default = [];
+ example = lib.literalExample ''
+ [ { keys = ["PLAYPAUSE"]; cmd = "''${pkgs.mpc_cli}/bin/mpc -q toggle"; } ]
+ '';
+ description = ''
+ Key bindings for <command>triggerhappy</command>.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Literal contents to append to the end of <command>triggerhappy</command> configuration file.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.sockets.triggerhappy = {
+ description = "Triggerhappy Socket";
+ wantedBy = [ "sockets.target" ];
+ socketConfig.ListenDatagram = socket;
+ };
+
+ systemd.services.triggerhappy = {
+ wantedBy = [ "multi-user.target" ];
+ description = "Global hotkey daemon";
+ serviceConfig = {
+ ExecStart = "${pkgs.triggerhappy}/bin/thd ${optionalString (cfg.user != "root") "--user ${cfg.user}"} --socket ${socket} --triggers ${configFile} --deviceglob /dev/input/event*";
+ };
+ };
+
+ services.udev.packages = lib.singleton (pkgs.writeTextFile {
+ name = "triggerhappy-udev-rules";
+ destination = "/etc/udev/rules.d/61-triggerhappy.rules";
+ text = ''
+ ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ATTRS{name}!="triggerhappy", \
+ RUN+="${pkgs.triggerhappy}/bin/th-cmd --socket ${socket} --passfd --udev"
+ '';
+ });
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/u2f.nix b/nixpkgs/nixos/modules/services/hardware/u2f.nix
new file mode 100644
index 00000000000..bb4b2f05f89
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/u2f.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.hardware.u2f;
+in {
+ options = {
+ hardware.u2f = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable U2F hardware support.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.udev.packages = [ pkgs.libu2f-host ];
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/hardware/udev.nix b/nixpkgs/nixos/modules/services/hardware/udev.nix
new file mode 100644
index 00000000000..83ab93bd7cf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/udev.nix
@@ -0,0 +1,318 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ udev = config.systemd.package;
+
+ cfg = config.services.udev;
+
+ extraUdevRules = pkgs.writeTextFile {
+ name = "extra-udev-rules";
+ text = cfg.extraRules;
+ destination = "/etc/udev/rules.d/99-local.rules";
+ };
+
+ extraHwdbFile = pkgs.writeTextFile {
+ name = "extra-hwdb-file";
+ text = cfg.extraHwdb;
+ destination = "/etc/udev/hwdb.d/99-local.hwdb";
+ };
+
+ nixosRules = ''
+ # Miscellaneous devices.
+ KERNEL=="kvm", MODE="0666"
+ KERNEL=="kqemu", MODE="0666"
+
+ # Needed for gpm.
+ SUBSYSTEM=="input", KERNEL=="mice", TAG+="systemd"
+ '';
+
+ # Perform substitutions in all udev rules files.
+ udevRules = pkgs.runCommand "udev-rules"
+ { preferLocalBuild = true;
+ allowSubstitutes = false;
+ packages = unique (map toString cfg.packages);
+ }
+ ''
+ mkdir -p $out
+ shopt -s nullglob
+ set +o pipefail
+
+ # Set a reasonable $PATH for programs called by udev rules.
+ echo 'ENV{PATH}="${udevPath}/bin:${udevPath}/sbin"' > $out/00-path.rules
+
+ # Add the udev rules from other packages.
+ for i in $packages; do
+ echo "Adding rules for package $i"
+ for j in $i/{etc,lib}/udev/rules.d/*; do
+ echo "Copying $j to $out/$(basename $j)"
+ cat $j > $out/$(basename $j)
+ done
+ done
+
+ # Fix some paths in the standard udev rules. Hacky.
+ for i in $out/*.rules; do
+ substituteInPlace $i \
+ --replace \"/sbin/modprobe \"${pkgs.kmod}/bin/modprobe \
+ --replace \"/sbin/mdadm \"${pkgs.mdadm}/sbin/mdadm \
+ --replace \"/sbin/blkid \"${pkgs.utillinux}/sbin/blkid \
+ --replace \"/bin/mount \"${pkgs.utillinux}/bin/mount \
+ --replace /usr/bin/readlink ${pkgs.coreutils}/bin/readlink \
+ --replace /usr/bin/basename ${pkgs.coreutils}/bin/basename
+ done
+
+ echo -n "Checking that all programs called by relative paths in udev rules exist in ${udev}/lib/udev... "
+ import_progs=$(grep 'IMPORT{program}="[^/$]' $out/* |
+ sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
+ run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="[^/$]' |
+ sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
+ for i in $import_progs $run_progs; do
+ if [[ ! -x ${udev}/lib/udev/$i && ! $i =~ socket:.* ]]; then
+ echo "FAIL"
+ echo "$i is called in udev rules but not installed by udev"
+ exit 1
+ fi
+ done
+ echo "OK"
+
+ echo -n "Checking that all programs called by absolute paths in udev rules exist... "
+ import_progs=$(grep 'IMPORT{program}="\/' $out/* |
+ sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
+ run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="/' |
+ sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
+ for i in $import_progs $run_progs; do
+ if [[ ! -x $i ]]; then
+ echo "FAIL"
+ echo "$i is called in udev rules but is not executable or does not exist"
+ exit 1
+ fi
+ done
+ echo "OK"
+
+ filesToFixup="$(for i in "$out"/*; do
+ grep -l '\B\(/usr\)\?/s\?bin' "$i" || :
+ done)"
+
+ if [ -n "$filesToFixup" ]; then
+ echo "Consider fixing the following udev rules:"
+ echo "$filesToFixup" | while read localFile; do
+ remoteFile="origin unknown"
+ for i in ${toString cfg.packages}; do
+ for j in "$i"/*/udev/rules.d/*; do
+ [ -e "$out/$(basename "$j")" ] || continue
+ [ "$(basename "$j")" = "$(basename "$localFile")" ] || continue
+ remoteFile="originally from $j"
+ break 2
+ done
+ done
+ refs="$(
+ grep -o '\B\(/usr\)\?/s\?bin/[^ "]\+' "$localFile" \
+ | sed -e ':r;N;''${s/\n/ and /;br};s/\n/, /g;br'
+ )"
+ echo "$localFile ($remoteFile) contains references to $refs."
+ done
+ exit 1
+ fi
+
+ # If auto-configuration is disabled, then remove
+ # udev's 80-drivers.rules file, which contains rules for
+ # automatically calling modprobe.
+ ${optionalString (!config.boot.hardwareScan) ''
+ ln -s /dev/null $out/80-drivers.rules
+ ''}
+ ''; # */
+
+ hwdbBin = pkgs.runCommand "hwdb.bin"
+ { preferLocalBuild = true;
+ allowSubstitutes = false;
+ packages = unique (map toString ([udev] ++ cfg.packages));
+ }
+ ''
+ mkdir -p etc/udev/hwdb.d
+ for i in $packages; do
+ echo "Adding hwdb files for package $i"
+ for j in $i/{etc,lib}/udev/hwdb.d/*; do
+ ln -s $j etc/udev/hwdb.d/$(basename $j)
+ done
+ done
+
+ echo "Generating hwdb database..."
+ # hwdb --update doesn't return error code even on errors!
+ res="$(${pkgs.buildPackages.udev}/bin/udevadm hwdb --update --root=$(pwd) 2>&1)"
+ echo "$res"
+ [ -z "$(echo "$res" | egrep '^Error')" ]
+ mv etc/udev/hwdb.bin $out
+ '';
+
+ # Udev has a 512-character limit for ENV{PATH}, so create a symlink
+ # tree to work around this.
+ udevPath = pkgs.buildEnv {
+ name = "udev-path";
+ paths = cfg.path;
+ pathsToLink = [ "/bin" "/sbin" ];
+ ignoreCollisions = true;
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ boot.hardwareScan = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to try to load kernel modules for all detected hardware.
+ Usually this does a good job of providing you with the modules
+ you need, but sometimes it can crash the system or cause other
+ nasty effects.
+ '';
+ };
+
+ services.udev = {
+
+ packages = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ List of packages containing <command>udev</command> rules.
+ All files found in
+ <filename><replaceable>pkg</replaceable>/etc/udev/rules.d</filename> and
+ <filename><replaceable>pkg</replaceable>/lib/udev/rules.d</filename>
+ will be included.
+ '';
+ apply = map getBin;
+ };
+
+ path = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ Packages added to the <envar>PATH</envar> environment variable when
+ executing programs from Udev rules.
+ '';
+ };
+
+ extraRules = mkOption {
+ default = "";
+ example = ''
+ KERNEL=="eth*", ATTR{address}=="00:1D:60:B9:6D:4F", NAME="my_fast_network_card"
+ '';
+ type = types.lines;
+ description = ''
+ Additional <command>udev</command> rules. They'll be written
+ into file <filename>99-local.rules</filename>. Thus they are
+ read and applied after all other rules.
+ '';
+ };
+
+ extraHwdb = mkOption {
+ default = "";
+ example = ''
+ evdev:input:b0003v05AFp8277*
+ KEYBOARD_KEY_70039=leftalt
+ KEYBOARD_KEY_700e2=leftctrl
+ '';
+ type = types.lines;
+ description = ''
+ Additional <command>hwdb</command> files. They'll be written
+ into file <filename>10-local.hwdb</filename>. Thus they are
+ read before all other files.
+ '';
+ };
+
+ };
+
+ hardware.firmware = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = ''
+ List of packages containing firmware files. Such files
+ will be loaded automatically if the kernel asks for them
+ (i.e., when it has detected specific hardware that requires
+ firmware to function). If multiple packages contain firmware
+ files with the same name, the first package in the list takes
+ precedence. Note that you must rebuild your system if you add
+ files to any of these directories.
+ '';
+ apply = list: pkgs.buildEnv {
+ name = "firmware";
+ paths = list;
+ pathsToLink = [ "/lib/firmware" ];
+ ignoreCollisions = true;
+ };
+ };
+
+ networking.usePredictableInterfaceNames = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to assign <link
+ xlink:href='http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames'>predictable
+ names to network interfaces</link>. If enabled, interfaces
+ are assigned names that contain topology information
+ (e.g. <literal>wlp3s0</literal>) and thus should be stable
+ across reboots. If disabled, names depend on the order in
+ which interfaces are discovered by the kernel, which may
+ change randomly across reboots; for instance, you may find
+ <literal>eth0</literal> and <literal>eth1</literal> flipping
+ unpredictably.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf (!config.boot.isContainer) {
+
+ services.udev.extraRules = nixosRules;
+
+ services.udev.packages = [ extraUdevRules extraHwdbFile ];
+
+ services.udev.path = [ pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.utillinux udev ];
+
+ boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
+
+ environment.etc =
+ [ { source = udevRules;
+ target = "udev/rules.d";
+ }
+ { source = hwdbBin;
+ target = "udev/hwdb.bin";
+ }
+ ];
+
+ system.requiredKernelConfig = with config.lib.kernelConfig; [
+ (isEnabled "UNIX")
+ (isYes "INOTIFY_USER")
+ (isYes "NET")
+ ];
+
+ boot.extraModprobeConfig = "options firmware_class path=${config.hardware.firmware}/lib/firmware";
+
+ system.activationScripts.udevd =
+ ''
+ # The deprecated hotplug uevent helper is not used anymore
+ if [ -e /proc/sys/kernel/hotplug ]; then
+ echo "" > /proc/sys/kernel/hotplug
+ fi
+
+ # Allow the kernel to find our firmware.
+ if [ -e /sys/module/firmware_class/parameters/path ]; then
+ echo -n "${config.hardware.firmware}/lib/firmware" > /sys/module/firmware_class/parameters/path
+ fi
+ '';
+
+ systemd.services.systemd-udevd =
+ { restartTriggers = cfg.packages;
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/udisks2.nix b/nixpkgs/nixos/modules/services/hardware/udisks2.nix
new file mode 100644
index 00000000000..ed8703be921
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/udisks2.nix
@@ -0,0 +1,47 @@
+# Udisks daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.udisks2 = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable Udisks, a DBus service that allows
+ applications to query and manipulate storage devices.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.udisks2.enable {
+
+ environment.systemPackages = [ pkgs.udisks2 ];
+
+ services.dbus.packages = [ pkgs.udisks2 ];
+
+ system.activationScripts.udisks2 =
+ ''
+ mkdir -m 0755 -p /var/lib/udisks2
+ '';
+
+ services.udev.packages = [ pkgs.udisks2 ];
+
+ systemd.packages = [ pkgs.udisks2 ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/undervolt.nix b/nixpkgs/nixos/modules/services/hardware/undervolt.nix
new file mode 100644
index 00000000000..e5ef0601de3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/undervolt.nix
@@ -0,0 +1,134 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.undervolt;
+in {
+ options.services.undervolt = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to undervolt intel cpus.
+ '';
+ };
+
+ verbose = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable verbose logging.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.undervolt;
+ defaultText = "pkgs.undervolt";
+ description = ''
+ undervolt derivation to use.
+ '';
+ };
+
+ coreOffset = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The amount of voltage to offset the CPU cores by. Accepts a floating point number.
+ '';
+ };
+
+ gpuOffset = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The amount of voltage to offset the GPU by. Accepts a floating point number.
+ '';
+ };
+
+ uncoreOffset = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The amount of voltage to offset uncore by. Accepts a floating point number.
+ '';
+ };
+
+ analogioOffset = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The amount of voltage to offset analogio by. Accepts a floating point number.
+ '';
+ };
+
+ temp = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The temperature target. Accepts a floating point number.
+ '';
+ };
+
+ tempAc = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The temperature target on AC power. Accepts a floating point number.
+ '';
+ };
+
+ tempBat = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The temperature target on battery power. Accepts a floating point number.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ boot.kernelModules = [ "msr" ];
+
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services.undervolt = {
+ path = [ pkgs.undervolt ];
+
+ description = "Intel Undervolting Service";
+ serviceConfig = {
+ Type = "oneshot";
+ Restart = "no";
+
+ # `core` and `cache` are both intentionally set to `cfg.coreOffset` as according to the undervolt docs:
+ #
+ # Core or Cache offsets have no effect. It is not possible to set different offsets for
+ # CPU Core and Cache. The CPU will take the smaller of the two offsets, and apply that to
+ # both CPU and Cache. A warning message will be displayed if you attempt to set different offsets.
+ ExecStart = ''
+ ${pkgs.undervolt}/bin/undervolt \
+ ${optionalString cfg.verbose "--verbose"} \
+ ${optionalString (cfg.coreOffset != null) "--core ${cfg.coreOffset}"} \
+ ${optionalString (cfg.coreOffset != null) "--cache ${cfg.coreOffset}"} \
+ ${optionalString (cfg.gpuOffset != null) "--gpu ${cfg.gpuOffset}"} \
+ ${optionalString (cfg.uncoreOffset != null) "--uncore ${cfg.uncoreOffset}"} \
+ ${optionalString (cfg.analogioOffset != null) "--analogio ${cfg.analogioOffset}"} \
+ ${optionalString (cfg.temp != null) "--temp ${cfg.temp}"} \
+ ${optionalString (cfg.tempAc != null) "--temp-ac ${cfg.tempAc}"} \
+ ${optionalString (cfg.tempBat != null) "--temp-bat ${cfg.tempBat}"}
+ '';
+ };
+ };
+
+ systemd.timers.undervolt = {
+ description = "Undervolt timer to ensure voltage settings are always applied";
+ partOf = [ "undervolt.service" ];
+ wantedBy = [ "multi-user.target" ];
+ timerConfig = {
+ OnBootSec = "2min";
+ OnUnitActiveSec = "30";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/upower.nix b/nixpkgs/nixos/modules/services/hardware/upower.nix
new file mode 100644
index 00000000000..5e7ac7a6e65
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/upower.nix
@@ -0,0 +1,59 @@
+# Upower daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.upower;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.upower = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Upower, a DBus service that provides power
+ management support to applications.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.upower;
+ defaultText = "pkgs.upower";
+ example = lib.literalExample "pkgs.upower";
+ description = ''
+ Which upower package to use.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ services.dbus.packages = [ cfg.package ];
+
+ services.udev.packages = [ cfg.package ];
+
+ systemd.packages = [ cfg.package ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/usbmuxd.nix b/nixpkgs/nixos/modules/services/hardware/usbmuxd.nix
new file mode 100644
index 00000000000..93ced0b9f04
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/usbmuxd.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ defaultUserGroup = "usbmux";
+ apple = "05ac";
+
+ cfg = config.services.usbmuxd;
+
+in
+
+{
+ options.services.usbmuxd = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the usbmuxd ("USB multiplexing daemon") service. This daemon is
+ in charge of multiplexing connections over USB to an iOS device. This is
+ needed for transferring data from and to iOS devices (see ifuse). Also
+ this may enable plug-n-play tethering for iPhones.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = defaultUserGroup;
+ description = ''
+ The user usbmuxd should use to run after startup.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = defaultUserGroup;
+ description = ''
+ The group usbmuxd should use to run after startup.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users = optional (cfg.user == defaultUserGroup) {
+ name = cfg.user;
+ description = "usbmuxd user";
+ group = cfg.group;
+ };
+
+ users.groups = optional (cfg.group == defaultUserGroup) {
+ name = cfg.group;
+ };
+
+ # Give usbmuxd permission for Apple devices
+ services.udev.extraRules = ''
+ SUBSYSTEM=="usb", ATTR{idVendor}=="${apple}", GROUP="${cfg.group}"
+ '';
+
+ systemd.services.usbmuxd = {
+ description = "usbmuxd";
+ wantedBy = [ "multi-user.target" ];
+ unitConfig.Documentation = "man:usbmuxd(8)";
+ serviceConfig = {
+ # Trigger the udev rule manually. This doesn't require replugging the
+ # device when first enabling the option to get it to work
+ ExecStartPre = "${pkgs.udev}/bin/udevadm trigger -s usb -a idVendor=${apple}";
+ ExecStart = "${pkgs.usbmuxd}/bin/usbmuxd -U ${cfg.user} -f";
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/vdr.nix b/nixpkgs/nixos/modules/services/hardware/vdr.nix
new file mode 100644
index 00000000000..6e246f70f51
--- /dev/null
+++ b/nixpkgs/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 "VDR. Please put config into ${libDir}";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.vdr;
+ defaultText = "pkgs.vdr";
+ example = literalExample "pkgs.wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }";
+ description = "Package to use.";
+ };
+
+ videoDir = mkOption {
+ type = types.path;
+ default = "/srv/vdr/video";
+ description = "Recording directory";
+ };
+
+ extraArguments = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Additional command line arguments to pass to VDR.";
+ };
+
+ enableLirc = mkEnableOption "LIRC";
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable (mkMerge [{
+ systemd.tmpfiles.rules = [
+ "d ${cfg.videoDir} 0755 vdr vdr -"
+ "Z ${cfg.videoDir} - vdr vdr -"
+ ];
+
+ systemd.services.vdr = {
+ description = "VDR";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${cfg.package}/bin/vdr \
+ --video="${cfg.videoDir}" \
+ --config="${libDir}" \
+ ${escapeShellArgs cfg.extraArguments}
+ '';
+ User = "vdr";
+ CacheDirectory = "vdr";
+ StateDirectory = "vdr";
+ Restart = "on-failure";
+ };
+ };
+
+ users.users.vdr = {
+ group = "vdr";
+ home = libDir;
+ };
+
+ users.groups.vdr = {};
+ }
+
+ (mkIf cfg.enableLirc {
+ services.lirc.enable = true;
+ users.users.vdr.extraGroups = [ "lirc" ];
+ services.vdr.extraArguments = [
+ "--lirc=${config.passthru.lirc.socket}"
+ ];
+ })]);
+}
diff --git a/nixpkgs/nixos/modules/services/logging/SystemdJournal2Gelf.nix b/nixpkgs/nixos/modules/services/logging/SystemdJournal2Gelf.nix
new file mode 100644
index 00000000000..f26aef7262b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/SystemdJournal2Gelf.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.SystemdJournal2Gelf;
+in
+
+{ options = {
+ services.SystemdJournal2Gelf = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable SystemdJournal2Gelf.
+ '';
+ };
+
+ graylogServer = mkOption {
+ type = types.str;
+ example = "graylog2.example.com:11201";
+ description = ''
+ Host and port of your graylog2 input. This should be a GELF
+ UDP input.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.separatedString " ";
+ default = "";
+ description = ''
+ Any extra flags to pass to SystemdJournal2Gelf. Note that
+ these are basically <literal>journalctl</literal> flags.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.systemd-journal2gelf;
+ description = ''
+ SystemdJournal2Gelf package to use.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.SystemdJournal2Gelf = {
+ description = "SystemdJournal2Gelf";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/SystemdJournal2Gelf ${cfg.graylogServer} --follow ${cfg.extraOptions}";
+ Restart = "on-failure";
+ RestartSec = "30";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/awstats.nix b/nixpkgs/nixos/modules/services/logging/awstats.nix
new file mode 100644
index 00000000000..a92ff3bee49
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/awstats.nix
@@ -0,0 +1,117 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.awstats;
+ httpd = config.services.httpd;
+ package = pkgs.awstats;
+in
+
+{
+ options.services.awstats = {
+ enable = mkOption {
+ type = types.bool;
+ default = cfg.service.enable;
+ description = ''
+ Enable the awstats program (but not service).
+ Currently only simple httpd (Apache) configs are supported,
+ and awstats plugins may not work correctly.
+ '';
+ };
+ vardir = mkOption {
+ type = types.path;
+ default = "/var/lib/awstats";
+ description = "The directory where variable awstats data will be stored.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra configuration to be appendend to awstats.conf.";
+ };
+
+ updateAt = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "hourly";
+ description = ''
+ Specification of the time at which awstats will get updated.
+ (in the format described by <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>)
+ '';
+ };
+
+ service = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''Enable the awstats web service. This switches on httpd.'';
+ };
+ urlPrefix = mkOption {
+ type = types.str;
+ default = "/awstats";
+ description = "The URL prefix under which the awstats service appears.";
+ };
+ };
+ };
+
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ package.bin ];
+ /* TODO:
+ - heed config.services.httpd.logPerVirtualHost, etc.
+ - Can't AllowToUpdateStatsFromBrowser, as CGI scripts don't have permission
+ to read the logs, and our httpd config apparently doesn't an option for that.
+ */
+ environment.etc."awstats/awstats.conf".source = pkgs.runCommand "awstats.conf"
+ { preferLocalBuild = true; }
+ ( let
+ logFormat =
+ if httpd.logFormat == "combined" then "1" else
+ if httpd.logFormat == "common" then "4" else
+ throw "awstats service doesn't support Apache log format `${httpd.logFormat}`";
+ in
+ ''
+ sed \
+ -e 's|^\(DirData\)=.*$|\1="${cfg.vardir}"|' \
+ -e 's|^\(DirIcons\)=.*$|\1="icons"|' \
+ -e 's|^\(CreateDirDataIfNotExists\)=.*$|\1=1|' \
+ -e 's|^\(SiteDomain\)=.*$|\1="${httpd.hostName}"|' \
+ -e 's|^\(LogFile\)=.*$|\1="${httpd.logDir}/access_log"|' \
+ -e 's|^\(LogFormat\)=.*$|\1=${logFormat}|' \
+ < '${package.out}/wwwroot/cgi-bin/awstats.model.conf' > "$out"
+ echo '${cfg.extraConfig}' >> "$out"
+ '');
+
+ systemd.tmpfiles.rules = optionals cfg.service.enable [
+ "d '${cfg.vardir}' - ${httpd.user} ${httpd.group} - -"
+ "Z '${cfg.vardir}' - ${httpd.user} ${httpd.group} - -"
+ ];
+
+ # The httpd sub-service showing awstats.
+ services.httpd = optionalAttrs cfg.service.enable {
+ enable = true;
+ extraConfig = ''
+ Alias ${cfg.service.urlPrefix}/classes "${package.out}/wwwroot/classes/"
+ Alias ${cfg.service.urlPrefix}/css "${package.out}/wwwroot/css/"
+ Alias ${cfg.service.urlPrefix}/icons "${package.out}/wwwroot/icon/"
+ ScriptAlias ${cfg.service.urlPrefix}/ "${package.out}/wwwroot/cgi-bin/"
+
+ <Directory "${package.out}/wwwroot">
+ Options None
+ Require all granted
+ </Directory>
+ '';
+ };
+
+ systemd.services.awstats-update = mkIf (cfg.updateAt != null) {
+ description = "awstats log collector";
+ script = "exec '${package.bin}/bin/awstats' -update -config=awstats.conf";
+ startAt = cfg.updateAt;
+ };
+ };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/logging/fluentd.nix b/nixpkgs/nixos/modules/services/logging/fluentd.nix
new file mode 100644
index 00000000000..95825705d9d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/fluentd.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.fluentd;
+
+ pluginArgs = concatStringsSep " " (map (x: "-p ${x}") cfg.plugins);
+in {
+ ###### interface
+
+ options = {
+
+ services.fluentd = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable fluentd.";
+ };
+
+ config = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Fluentd config.";
+ };
+
+ package = mkOption {
+ type = types.path;
+ default = pkgs.fluentd;
+ defaultText = "pkgs.fluentd";
+ description = "The fluentd package to use.";
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ A list of plugin paths to pass into fluentd. It will make plugins defined in ruby files
+ there available in your config.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.fluentd = with pkgs; {
+ description = "Fluentd Daemon";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/fluentd -c ${pkgs.writeText "fluentd.conf" cfg.config} ${pluginArgs}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/graylog.nix b/nixpkgs/nixos/modules/services/logging/graylog.nix
new file mode 100644
index 00000000000..a889a44d4b2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/graylog.nix
@@ -0,0 +1,170 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.graylog;
+
+ confFile = pkgs.writeText "graylog.conf" ''
+ is_master = ${boolToString cfg.isMaster}
+ node_id_file = ${cfg.nodeIdFile}
+ password_secret = ${cfg.passwordSecret}
+ root_username = ${cfg.rootUsername}
+ root_password_sha2 = ${cfg.rootPasswordSha2}
+ elasticsearch_hosts = ${concatStringsSep "," cfg.elasticsearchHosts}
+ message_journal_dir = ${cfg.messageJournalDir}
+ mongodb_uri = ${cfg.mongodbUri}
+ plugin_dir = /var/lib/graylog/plugins
+
+ ${cfg.extraConfig}
+ '';
+
+ glPlugins = pkgs.buildEnv {
+ name = "graylog-plugins";
+ paths = cfg.plugins;
+ };
+
+in
+
+{
+ ###### interface
+
+ options = {
+
+ services.graylog = {
+
+ enable = mkEnableOption "Graylog";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.graylog;
+ defaultText = "pkgs.graylog";
+ example = literalExample "pkgs.graylog";
+ description = "Graylog package to use.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "graylog";
+ example = literalExample "graylog";
+ description = "User account under which graylog runs";
+ };
+
+ isMaster = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether this is the master instance of your Graylog cluster";
+ };
+
+ nodeIdFile = mkOption {
+ type = types.str;
+ default = "/var/lib/graylog/server/node-id";
+ description = "Path of the file containing the graylog node-id";
+ };
+
+ passwordSecret = mkOption {
+ type = types.str;
+ description = ''
+ You MUST set a secret to secure/pepper the stored user passwords here. Use at least 64 characters.
+ Generate one by using for example: pwgen -N 1 -s 96
+ '';
+ };
+
+ rootUsername = mkOption {
+ type = types.str;
+ default = "admin";
+ description = "Name of the default administrator user";
+ };
+
+ rootPasswordSha2 = mkOption {
+ type = types.str;
+ example = "e3c652f0ba0b4801205814f8b6bc49672c4c74e25b497770bb89b22cdeb4e952";
+ description = ''
+ You MUST specify a hash password for the root user (which you only need to initially set up the
+ system and in case you lose connectivity to your authentication backend)
+ This password cannot be changed using the API or via the web interface. If you need to change it,
+ modify it here.
+ Create one by using for example: echo -n yourpassword | shasum -a 256
+ and use the resulting hash value as string for the option
+ '';
+ };
+
+ elasticsearchHosts = mkOption {
+ type = types.listOf types.str;
+ example = literalExample ''[ "http://node1:9200" "http://user:password@node2:19200" ]'';
+ description = "List of valid URIs of the http ports of your elastic nodes. If one or more of your elasticsearch hosts require authentication, include the credentials in each node URI that requires authentication";
+ };
+
+ messageJournalDir = mkOption {
+ type = types.str;
+ default = "/var/lib/graylog/data/journal";
+ description = "The directory which will be used to store the message journal. The directory must be exclusively used by Graylog and must not contain any other files than the ones created by Graylog itself";
+ };
+
+ mongodbUri = mkOption {
+ type = types.str;
+ default = "mongodb://localhost/graylog";
+ description = "MongoDB connection string. See http://docs.mongodb.org/manual/reference/connection-string/ for details";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Any other configuration options you might want to add";
+ };
+
+ plugins = mkOption {
+ description = "Extra graylog plugins";
+ default = [ ];
+ type = types.listOf types.package;
+ };
+
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = mkIf (cfg.user == "graylog") {
+ graylog = {
+ uid = config.ids.uids.graylog;
+ description = "Graylog server daemon user";
+ };
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.messageJournalDir}' - ${cfg.user} - - -"
+ ];
+
+ systemd.services.graylog = with pkgs; {
+ description = "Graylog Server";
+ wantedBy = [ "multi-user.target" ];
+ environment = {
+ JAVA_HOME = jre;
+ GRAYLOG_CONF = "${confFile}";
+ };
+ path = [ pkgs.jre_headless pkgs.which pkgs.procps ];
+ preStart = ''
+ rm -rf /var/lib/graylog/plugins || true
+ mkdir -p /var/lib/graylog/plugins -m 755
+
+ mkdir -p "$(dirname ${cfg.nodeIdFile})"
+ chown -R ${cfg.user} "$(dirname ${cfg.nodeIdFile})"
+
+ for declarativeplugin in `ls ${glPlugins}/bin/`; do
+ ln -sf ${glPlugins}/bin/$declarativeplugin /var/lib/graylog/plugins/$declarativeplugin
+ done
+ for includedplugin in `ls ${cfg.package}/plugin/`; do
+ ln -s ${cfg.package}/plugin/$includedplugin /var/lib/graylog/plugins/$includedplugin || true
+ done
+ '';
+ serviceConfig = {
+ User="${cfg.user}";
+ StateDirectory = "graylog";
+ ExecStart = "${cfg.package}/bin/graylogctl run";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/heartbeat.nix b/nixpkgs/nixos/modules/services/logging/heartbeat.nix
new file mode 100644
index 00000000000..56fb4deabda
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/heartbeat.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.heartbeat;
+
+ heartbeatYml = pkgs.writeText "heartbeat.yml" ''
+ name: ${cfg.name}
+ tags: ${builtins.toJSON cfg.tags}
+
+ ${cfg.extraConfig}
+ '';
+
+in
+{
+ options = {
+
+ services.heartbeat = {
+
+ enable = mkEnableOption "heartbeat";
+
+ name = mkOption {
+ type = types.str;
+ default = "heartbeat";
+ description = "Name of the beat";
+ };
+
+ tags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Tags to place on the shipped log messages";
+ };
+
+ stateDir = mkOption {
+ type = types.str;
+ default = "/var/lib/heartbeat";
+ description = "The state directory. heartbeat's own logs and other data are stored here.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = ''
+ heartbeat.monitors:
+ - type: http
+ urls: ["http://localhost:9200"]
+ schedule: '@every 10s'
+ '';
+ description = "Any other configuration options you want to add";
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.stateDir}' - nobody nogroup - -"
+ ];
+
+ systemd.services.heartbeat = with pkgs; {
+ description = "heartbeat log shipper";
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -p "${cfg.stateDir}"/{data,logs}
+ '';
+ serviceConfig = {
+ User = "nobody";
+ AmbientCapabilities = "cap_net_raw";
+ ExecStart = "${pkgs.heartbeat}/bin/heartbeat -c \"${heartbeatYml}\" -path.data \"${cfg.stateDir}/data\" -path.logs \"${cfg.stateDir}/logs\"";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/journalbeat.nix b/nixpkgs/nixos/modules/services/logging/journalbeat.nix
new file mode 100644
index 00000000000..89f53b1b245
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/journalbeat.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.journalbeat;
+
+ lt6 = builtins.compareVersions cfg.package.version "6" < 0;
+
+ journalbeatYml = pkgs.writeText "journalbeat.yml" ''
+ name: ${cfg.name}
+ tags: ${builtins.toJSON cfg.tags}
+
+ ${optionalString lt6 "journalbeat.cursor_state_file: /var/lib/${cfg.stateDir}/cursor-state"}
+
+ ${cfg.extraConfig}
+ '';
+
+in
+{
+ options = {
+
+ services.journalbeat = {
+
+ enable = mkEnableOption "journalbeat";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.journalbeat;
+ defaultText = "pkgs.journalbeat";
+ example = literalExample "pkgs.journalbeat7";
+ description = ''
+ The journalbeat package to use
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "journalbeat";
+ description = "Name of the beat";
+ };
+
+ tags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Tags to place on the shipped log messages";
+ };
+
+ stateDir = mkOption {
+ type = types.str;
+ default = "journalbeat";
+ description = ''
+ Directory below <literal>/var/lib/</literal> to store journalbeat's
+ own logs and other data. This directory will be created automatically
+ using systemd's StateDirectory mechanism.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = optionalString lt6 ''
+ journalbeat:
+ seek_position: cursor
+ cursor_seek_fallback: tail
+ write_cursor_state: true
+ cursor_flush_period: 5s
+ clean_field_names: true
+ convert_to_numbers: false
+ move_metadata_to_field: journal
+ default_type: journal
+ '';
+ description = "Any other configuration options you want to add";
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ {
+ assertion = !hasPrefix "/" cfg.stateDir;
+ message =
+ "The option services.journalbeat.stateDir shouldn't be an absolute directory." +
+ " It should be a directory relative to /var/lib/.";
+ }
+ ];
+
+ systemd.services.journalbeat = {
+ description = "Journalbeat log shipper";
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -p ${cfg.stateDir}/data
+ mkdir -p ${cfg.stateDir}/logs
+ '';
+ serviceConfig = {
+ StateDirectory = cfg.stateDir;
+ ExecStart = ''
+ ${cfg.package}/bin/journalbeat \
+ -c ${journalbeatYml} \
+ -path.data /var/lib/${cfg.stateDir}/data \
+ -path.logs /var/lib/${cfg.stateDir}/logs'';
+ Restart = "always";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/journaldriver.nix b/nixpkgs/nixos/modules/services/logging/journaldriver.nix
new file mode 100644
index 00000000000..9bd581e9ec0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/journaldriver.nix
@@ -0,0 +1,112 @@
+# This module implements a systemd service for running journaldriver,
+# a log forwarding agent that sends logs from journald to Stackdriver
+# Logging.
+#
+# It can be enabled without extra configuration when running on GCP.
+# On machines hosted elsewhere, the other configuration options need
+# to be set.
+#
+# For further information please consult the documentation in the
+# upstream repository at: https://github.com/tazjin/journaldriver/
+
+{ config, lib, pkgs, ...}:
+
+with lib; let cfg = config.services.journaldriver;
+in {
+ options.services.journaldriver = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable journaldriver to forward journald logs to
+ Stackdriver Logging.
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.str;
+ default = "info";
+ description = ''
+ Log level at which journaldriver logs its own output.
+ '';
+ };
+
+ logName = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Configures the name of the target log in Stackdriver Logging.
+ This option can be set to, for example, the hostname of a
+ machine to improve the user experience in the logging
+ overview.
+ '';
+ };
+
+ googleCloudProject = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Configures the name of the Google Cloud project to which to
+ forward journald logs.
+
+ This option is required on non-GCP machines, but should not be
+ set on GCP instances.
+ '';
+ };
+
+ logStream = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Configures the name of the Stackdriver Logging log stream into
+ which to write journald entries.
+
+ This option is required on non-GCP machines, but should not be
+ set on GCP instances.
+ '';
+ };
+
+ applicationCredentials = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ Path to the service account private key (in JSON-format) used
+ to forward log entries to Stackdriver Logging on non-GCP
+ instances.
+
+ This option is required on non-GCP machines, but should not be
+ set on GCP instances.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.journaldriver = {
+ description = "Stackdriver Logging journal forwarder";
+ script = "${pkgs.journaldriver}/bin/journaldriver";
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Restart = "always";
+ DynamicUser = true;
+
+ # This directive lets systemd automatically configure
+ # permissions on /var/lib/journaldriver, the directory in
+ # which journaldriver persists its cursor state.
+ StateDirectory = "journaldriver";
+
+ # This group is required for accessing journald.
+ SupplementaryGroups = "systemd-journal";
+ };
+
+ environment = {
+ RUST_LOG = cfg.logLevel;
+ LOG_NAME = cfg.logName;
+ LOG_STREAM = cfg.logStream;
+ GOOGLE_CLOUD_PROJECT = cfg.googleCloudProject;
+ GOOGLE_APPLICATION_CREDENTIALS = cfg.applicationCredentials;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/journalwatch.nix b/nixpkgs/nixos/modules/services/logging/journalwatch.nix
new file mode 100644
index 00000000000..576c646c0f5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/journalwatch.nix
@@ -0,0 +1,264 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+ cfg = config.services.journalwatch;
+ user = "journalwatch";
+ # for journal access
+ group = "systemd-journal";
+ dataDir = "/var/lib/${user}";
+
+ journalwatchConfig = pkgs.writeText "config" (''
+ # (File Generated by NixOS journalwatch module.)
+ [DEFAULT]
+ mail_binary = ${cfg.mailBinary}
+ priority = ${toString cfg.priority}
+ mail_from = ${cfg.mailFrom}
+ ''
+ + optionalString (cfg.mailTo != null) ''
+ mail_to = ${cfg.mailTo}
+ ''
+ + cfg.extraConfig);
+
+ journalwatchPatterns = pkgs.writeText "patterns" ''
+ # (File Generated by NixOS journalwatch module.)
+
+ ${mkPatterns cfg.filterBlocks}
+ '';
+
+ # empty line at the end needed to to separate the blocks
+ mkPatterns = filterBlocks: concatStringsSep "\n" (map (block: ''
+ ${block.match}
+ ${block.filters}
+
+ '') filterBlocks);
+
+ # can't use joinSymlinks directly, because when we point $XDG_CONFIG_HOME
+ # to the /nix/store path, we still need the subdirectory "journalwatch" inside that
+ # to match journalwatch's expectations
+ journalwatchConfigDir = pkgs.runCommand "journalwatch-config"
+ { preferLocalBuild = true; allowSubstitutes = false; }
+ ''
+ mkdir -p $out/journalwatch
+ ln -sf ${journalwatchConfig} $out/journalwatch/config
+ ln -sf ${journalwatchPatterns} $out/journalwatch/patterns
+ '';
+
+
+in {
+ options = {
+ services.journalwatch = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, periodically check the journal with journalwatch and report the results by mail.
+ '';
+ };
+
+ priority = mkOption {
+ type = types.int;
+ default = 6;
+ description = ''
+ Lowest priority of message to be considered.
+ A value between 7 ("debug"), and 0 ("emerg"). Defaults to 6 ("info").
+ If you don't care about anything with "info" priority, you can reduce
+ this to e.g. 5 ("notice") to considerably reduce the amount of
+ messages without needing many <option>filterBlocks</option>.
+ '';
+ };
+
+ # HACK: this is a workaround for journalwatch's usage of socket.getfqdn() which always returns localhost if
+ # there's an alias for the localhost on a separate line in /etc/hosts, or take for ages if it's not present and
+ # then return something right-ish in the direction of /etc/hostname. Just bypass it completely.
+ mailFrom = mkOption {
+ type = types.str;
+ default = "journalwatch@${config.networking.hostName}";
+ description = ''
+ Mail address to send journalwatch reports from.
+ '';
+ };
+
+ mailTo = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Mail address to send journalwatch reports to.
+ '';
+ };
+
+ mailBinary = mkOption {
+ type = types.path;
+ default = "/run/wrappers/bin/sendmail";
+ description = ''
+ Sendmail-compatible binary to be used to send the messages.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Extra lines to be added verbatim to the journalwatch/config configuration file.
+ You can add any commandline argument to the config, without the '--'.
+ See <literal>journalwatch --help</literal> for all arguments and their description.
+ '';
+ };
+
+ filterBlocks = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ match = mkOption {
+ type = types.str;
+ example = "SYSLOG_IDENTIFIER = systemd";
+ description = ''
+ Syntax: <literal>field = value</literal>
+ Specifies the log entry <literal>field</literal> this block should apply to.
+ If the <literal>field</literal> of a message matches this <literal>value</literal>,
+ this patternBlock's <option>filters</option> are applied.
+ If <literal>value</literal> starts and ends with a slash, it is interpreted as
+ an extended python regular expression, if not, it's an exact match.
+ The journal fields are explained in systemd.journal-fields(7).
+ '';
+ };
+
+ filters = mkOption {
+ type = types.str;
+ example = ''
+ (Stopped|Stopping|Starting|Started) .*
+ (Reached target|Stopped target) .*
+ '';
+ description = ''
+ The filters to apply on all messages which satisfy <option>match</option>.
+ Any of those messages that match any specified filter will be removed from journalwatch's output.
+ Each filter is an extended Python regular expression.
+ You can specify multiple filters and separate them by newlines.
+ Lines starting with '#' are comments. Inline-comments are not permitted.
+ '';
+ };
+ };
+ });
+
+ example = [
+ # examples taken from upstream
+ {
+ match = "_SYSTEMD_UNIT = systemd-logind.service";
+ filters = ''
+ New session [a-z]?\d+ of user \w+\.
+ Removed session [a-z]?\d+\.
+ '';
+ }
+
+ {
+ match = "SYSLOG_IDENTIFIER = /(CROND|crond)/";
+ filters = ''
+ pam_unix\(crond:session\): session (opened|closed) for user \w+
+ \(\w+\) CMD .*
+ '';
+ }
+ ];
+
+ # another example from upstream.
+ # very useful on priority = 6, and required as journalwatch throws an error when no pattern is defined at all.
+ default = [
+ {
+ match = "SYSLOG_IDENTIFIER = systemd";
+ filters = ''
+ (Stopped|Stopping|Starting|Started) .*
+ (Created slice|Removed slice) user-\d*\.slice\.
+ Received SIGRTMIN\+24 from PID .*
+ (Reached target|Stopped target) .*
+ Startup finished in \d*ms\.
+ '';
+ }
+ ];
+
+
+ description = ''
+ filterBlocks can be defined to blacklist journal messages which are not errors.
+ Each block matches on a log entry field, and the filters in that block then are matched
+ against all messages with a matching log entry field.
+
+ All messages whose PRIORITY is at least 6 (INFO) are processed by journalwatch.
+ If you don't specify any filterBlocks, PRIORITY is reduced to 5 (NOTICE) by default.
+
+ All regular expressions are extended Python regular expressions, for details
+ see: http://doc.pyschools.com/html/regex.html
+ '';
+ };
+
+ interval = mkOption {
+ type = types.str;
+ default = "hourly";
+ description = ''
+ How often to run journalwatch.
+
+ The format is described in systemd.time(7).
+ '';
+ };
+ accuracy = mkOption {
+ type = types.str;
+ default = "10min";
+ description = ''
+ The time window around the interval in which the journalwatch run will be scheduled.
+
+ The format is described in systemd.time(7).
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users.${user} = {
+ isSystemUser = true;
+ home = dataDir;
+ group = group;
+ };
+
+ systemd.tmpfiles.rules = [
+ # present since NixOS 19.09: remove old stateful symlink join directory,
+ # which has been replaced with the journalwatchConfigDir store path
+ "R ${dataDir}/config"
+ ];
+
+ systemd.services.journalwatch = {
+
+ environment = {
+ # journalwatch stores the last processed timpestamp here
+ # the share subdirectory is historic now that config home lives in /nix/store,
+ # but moving this in a backwards-compatible way is much more work than what's justified
+ # for cleaning that up.
+ XDG_DATA_HOME = "${dataDir}/share";
+ XDG_CONFIG_HOME = journalwatchConfigDir;
+ };
+ serviceConfig = {
+ User = user;
+ Group = group;
+ Type = "oneshot";
+ # requires a relative directory name to create beneath /var/lib
+ StateDirectory = user;
+ StateDirectoryMode = 0750;
+ ExecStart = "${pkgs.python3Packages.journalwatch}/bin/journalwatch mail";
+ # lowest CPU and IO priority, but both still in best-effort class to prevent starvation
+ Nice=19;
+ IOSchedulingPriority=7;
+ };
+ };
+
+ systemd.timers.journalwatch = {
+ description = "Periodic journalwatch run";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = cfg.interval;
+ AccuracySec = cfg.accuracy;
+ Persistent = true;
+ };
+ };
+
+ };
+
+ meta = {
+ maintainers = with lib.maintainers; [ florianjacob ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/klogd.nix b/nixpkgs/nixos/modules/services/logging/klogd.nix
new file mode 100644
index 00000000000..2d1f515da92
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/klogd.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+
+ options = {
+
+ services.klogd.enable = mkOption {
+ type = types.bool;
+ default = versionOlder (getVersion config.boot.kernelPackages.kernel) "3.5";
+ description = ''
+ Whether to enable klogd, the kernel log message processing
+ daemon. Since systemd handles logging of kernel messages on
+ Linux 3.5 and later, this is only useful if you're running an
+ older kernel.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.klogd.enable {
+ systemd.services.klogd = {
+ description = "Kernel Log Daemon";
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.sysklogd ];
+ unitConfig.ConditionVirtualization = "!systemd-nspawn";
+ script =
+ "klogd -c 1 -2 -n " +
+ "-k $(dirname $(readlink -f /run/booted-system/kernel))/System.map";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/logcheck.nix b/nixpkgs/nixos/modules/services/logging/logcheck.nix
new file mode 100644
index 00000000000..6d8be5b926d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/logcheck.nix
@@ -0,0 +1,237 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.logcheck;
+
+ defaultRules = pkgs.runCommand "logcheck-default-rules" { preferLocalBuild = true; } ''
+ cp -prd ${pkgs.logcheck}/etc/logcheck $out
+ chmod u+w $out
+ rm -r $out/logcheck.*
+ '';
+
+ rulesDir = pkgs.symlinkJoin
+ { name = "logcheck-rules-dir";
+ paths = ([ defaultRules ] ++ cfg.extraRulesDirs);
+ };
+
+ configFile = pkgs.writeText "logcheck.conf" cfg.config;
+
+ logFiles = pkgs.writeText "logcheck.logfiles" cfg.files;
+
+ flags = "-r ${rulesDir} -c ${configFile} -L ${logFiles} -${levelFlag} -m ${cfg.mailTo}";
+
+ levelFlag = getAttrFromPath [cfg.level]
+ { paranoid = "p";
+ server = "s";
+ workstation = "w";
+ };
+
+ cronJob = ''
+ @reboot logcheck env PATH=/run/wrappers/bin:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck -R ${flags}
+ 2 ${cfg.timeOfDay} * * * logcheck env PATH=/run/wrappers/bin:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck ${flags}
+ '';
+
+ writeIgnoreRule = name: {level, regex, ...}:
+ pkgs.writeTextFile
+ { inherit name;
+ destination = "/ignore.d.${level}/${name}";
+ text = ''
+ ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ ${regex}
+ '';
+ };
+
+ writeIgnoreCronRule = name: {level, user, regex, cmdline, ...}:
+ let escapeRegex = escape (stringToCharacters "\\[]{}()^$?*+|.");
+ cmdline_ = builtins.unsafeDiscardStringContext cmdline;
+ re = if regex != "" then regex else if cmdline_ == "" then ".*" else escapeRegex cmdline_;
+ in writeIgnoreRule "cron-${name}" {
+ inherit level;
+ regex = ''
+ (/usr/bin/)?cron\[[0-9]+\]: \(${user}\) CMD \(${re}\)$
+ '';
+ };
+
+ levelOption = mkOption {
+ default = "server";
+ type = types.enum [ "workstation" "server" "paranoid" ];
+ description = ''
+ Set the logcheck level.
+ '';
+ };
+
+ ignoreOptions = {
+ options = {
+ level = levelOption;
+
+ regex = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Regex specifying which log lines to ignore.
+ '';
+ };
+ };
+ };
+
+ ignoreCronOptions = {
+ options = {
+ user = mkOption {
+ default = "root";
+ type = types.str;
+ description = ''
+ User that runs the cronjob.
+ '';
+ };
+
+ cmdline = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Command line for the cron job. Will be turned into a regex for the logcheck ignore rule.
+ '';
+ };
+
+ timeArgs = mkOption {
+ default = null;
+ type = types.nullOr (types.str);
+ example = "02 06 * * *";
+ description = ''
+ "min hr dom mon dow" crontab time args, to auto-create a cronjob too.
+ Leave at null to not do this and just add a logcheck ignore rule.
+ '';
+ };
+ };
+ };
+
+in
+{
+ options = {
+ services.logcheck = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable the logcheck cron job.
+ '';
+ };
+
+ user = mkOption {
+ default = "logcheck";
+ type = types.str;
+ description = ''
+ Username for the logcheck user.
+ '';
+ };
+
+ timeOfDay = mkOption {
+ default = "*";
+ example = "6";
+ type = types.str;
+ description = ''
+ Time of day to run logcheck. A logcheck will be scheduled at xx:02 each day.
+ Leave default (*) to run every hour. Of course when nothing special was logged,
+ logcheck will be silent.
+ '';
+ };
+
+ mailTo = mkOption {
+ default = "root";
+ example = "you@domain.com";
+ type = types.str;
+ description = ''
+ Email address to send reports to.
+ '';
+ };
+
+ level = mkOption {
+ default = "server";
+ type = types.str;
+ description = ''
+ Set the logcheck level. Either "workstation", "server", or "paranoid".
+ '';
+ };
+
+ config = mkOption {
+ default = "FQDN=1";
+ type = types.lines;
+ description = ''
+ Config options that you would like in logcheck.conf.
+ '';
+ };
+
+ files = mkOption {
+ default = [ "/var/log/messages" ];
+ type = types.listOf types.path;
+ example = [ "/var/log/messages" "/var/log/mail" ];
+ description = ''
+ Which log files to check.
+ '';
+ };
+
+ extraRulesDirs = mkOption {
+ default = [];
+ example = "/etc/logcheck";
+ type = types.listOf types.path;
+ description = ''
+ Directories with extra rules.
+ '';
+ };
+
+ ignore = mkOption {
+ default = {};
+ description = ''
+ This option defines extra ignore rules.
+ '';
+ type = with types; attrsOf (submodule ignoreOptions);
+ };
+
+ ignoreCron = mkOption {
+ default = {};
+ description = ''
+ This option defines extra ignore rules for cronjobs.
+ '';
+ type = with types; attrsOf (submodule ignoreCronOptions);
+ };
+
+ extraGroups = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ example = [ "postdrop" "mongodb" ];
+ description = ''
+ Extra groups for the logcheck user, for example to be able to use sendmail,
+ or to access certain log files.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.logcheck.extraRulesDirs =
+ mapAttrsToList writeIgnoreRule cfg.ignore
+ ++ mapAttrsToList writeIgnoreCronRule cfg.ignoreCron;
+
+ users.users = optionalAttrs (cfg.user == "logcheck") (singleton
+ { name = "logcheck";
+ uid = config.ids.uids.logcheck;
+ shell = "/bin/sh";
+ description = "Logcheck user account";
+ extraGroups = cfg.extraGroups;
+ });
+
+ system.activationScripts.logcheck = ''
+ mkdir -m 700 -p /var/{lib,lock}/logcheck
+ chown ${cfg.user} /var/{lib,lock}/logcheck
+ '';
+
+ services.cron.systemCronJobs =
+ let withTime = name: {timeArgs, ...}: timeArgs != null;
+ mkCron = name: {user, cmdline, timeArgs, ...}: ''
+ ${timeArgs} ${user} ${cmdline}
+ '';
+ in mapAttrsToList mkCron (filterAttrs withTime cfg.ignoreCron)
+ ++ [ cronJob ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/logrotate.nix b/nixpkgs/nixos/modules/services/logging/logrotate.nix
new file mode 100644
index 00000000000..fdd9f0f3e5c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/logrotate.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.logrotate;
+
+ configFile = pkgs.writeText "logrotate.conf"
+ cfg.config;
+
+in
+{
+ options = {
+ services.logrotate = {
+ enable = mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Enable the logrotate cron job
+ '';
+ };
+
+ config = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ The contents of the logrotate config file
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.logrotate = {
+ description = "Logrotate Service";
+ wantedBy = [ "multi-user.target" ];
+ startAt = "*-*-* *:05:00";
+
+ serviceConfig.Restart = "no";
+ serviceConfig.User = "root";
+ script = ''
+ exec ${pkgs.logrotate}/sbin/logrotate ${configFile}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/logstash.nix b/nixpkgs/nixos/modules/services/logging/logstash.nix
new file mode 100644
index 00000000000..4943e8d7db3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/logstash.nix
@@ -0,0 +1,180 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.logstash;
+ pluginPath = lib.concatStringsSep ":" cfg.plugins;
+ havePluginPath = lib.length cfg.plugins > 0;
+ ops = lib.optionalString;
+ verbosityFlag = "--log.level " + cfg.logLevel;
+
+ pluginsPath = "--path.plugins ${pluginPath}";
+
+ logstashConf = pkgs.writeText "logstash.conf" ''
+ input {
+ ${cfg.inputConfig}
+ }
+
+ filter {
+ ${cfg.filterConfig}
+ }
+
+ output {
+ ${cfg.outputConfig}
+ }
+ '';
+
+ logstashSettingsYml = pkgs.writeText "logstash.yml" cfg.extraSettings;
+
+ logstashSettingsDir = pkgs.runCommand "logstash-settings" {
+ inherit logstashSettingsYml;
+ preferLocalBuild = true;
+ } ''
+ mkdir -p $out
+ ln -s $logstashSettingsYml $out/logstash.yml
+ '';
+in
+
+{
+ ###### interface
+
+ options = {
+
+ services.logstash = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable logstash.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.logstash;
+ defaultText = "pkgs.logstash";
+ example = literalExample "pkgs.logstash";
+ description = "Logstash package to use.";
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.path;
+ default = [ ];
+ example = literalExample "[ pkgs.logstash-contrib ]";
+ description = "The paths to find other logstash plugins in.";
+ };
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/logstash";
+ description = ''
+ A path to directory writable by logstash that it uses to store data.
+ Plugins will also have access to this path.
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.enum [ "debug" "info" "warn" "error" "fatal" ];
+ default = "warn";
+ description = "Logging verbosity level.";
+ };
+
+ filterWorkers = mkOption {
+ type = types.int;
+ default = 1;
+ description = "The quantity of filter workers to run.";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Address on which to start webserver.";
+ };
+
+ port = mkOption {
+ type = types.str;
+ default = "9292";
+ description = "Port on which to start webserver.";
+ };
+
+ inputConfig = mkOption {
+ type = types.lines;
+ default = ''generator { }'';
+ description = "Logstash input configuration.";
+ example = ''
+ # Read from journal
+ pipe {
+ command => "''${pkgs.systemd}/bin/journalctl -f -o json"
+ type => "syslog" codec => json {}
+ }
+ '';
+ };
+
+ filterConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "logstash filter configuration.";
+ example = ''
+ if [type] == "syslog" {
+ # Keep only relevant systemd fields
+ # http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
+ prune {
+ whitelist_names => [
+ "type", "@timestamp", "@version",
+ "MESSAGE", "PRIORITY", "SYSLOG_FACILITY"
+ ]
+ }
+ }
+ '';
+ };
+
+ outputConfig = mkOption {
+ type = types.lines;
+ default = ''stdout { codec => rubydebug }'';
+ description = "Logstash output configuration.";
+ example = ''
+ redis { host => ["localhost"] data_type => "list" key => "logstash" codec => json }
+ elasticsearch { }
+ '';
+ };
+
+ extraSettings = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra Logstash settings in YAML format.";
+ example = ''
+ pipeline:
+ batch:
+ size: 125
+ delay: 5
+ '';
+ };
+
+
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.logstash = with pkgs; {
+ description = "Logstash Daemon";
+ wantedBy = [ "multi-user.target" ];
+ environment = { JAVA_HOME = jre; };
+ path = [ pkgs.bash ];
+ serviceConfig = {
+ ExecStartPre = ''${pkgs.coreutils}/bin/mkdir -p "${cfg.dataDir}" ; ${pkgs.coreutils}/bin/chmod 700 "${cfg.dataDir}"'';
+ ExecStart = concatStringsSep " " (filter (s: stringLength s != 0) [
+ "${cfg.package}/bin/logstash"
+ "-w ${toString cfg.filterWorkers}"
+ (ops havePluginPath pluginsPath)
+ "${verbosityFlag}"
+ "-f ${logstashConf}"
+ "--path.settings ${logstashSettingsDir}"
+ "--path.data ${cfg.dataDir}"
+ ]);
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/rsyslogd.nix b/nixpkgs/nixos/modules/services/logging/rsyslogd.nix
new file mode 100644
index 00000000000..b924d94e0b0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/rsyslogd.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.rsyslogd;
+
+ syslogConf = pkgs.writeText "syslog.conf" ''
+ $ModLoad imuxsock
+ $SystemLogSocketName /run/systemd/journal/syslog
+ $WorkDirectory /var/spool/rsyslog
+
+ ${cfg.defaultConfig}
+ ${cfg.extraConfig}
+ '';
+
+ defaultConf = ''
+ # "local1" is used for dhcpd messages.
+ local1.* -/var/log/dhcpd
+
+ mail.* -/var/log/mail
+
+ *.=warning;*.=err -/var/log/warn
+ *.crit /var/log/warn
+
+ *.*;mail.none;local1.none -/var/log/messages
+ '';
+
+in
+
+{
+ ###### interface
+
+ options = {
+
+ services.rsyslogd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable syslogd. Note that systemd also logs
+ syslog messages, so you normally don't need to run syslogd.
+ '';
+ };
+
+ defaultConfig = mkOption {
+ type = types.lines;
+ default = defaultConf;
+ description = ''
+ The default <filename>syslog.conf</filename> file configures a
+ fairly standard setup of log files, which can be extended by
+ means of <varname>extraConfig</varname>.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "news.* -/var/log/news";
+ description = ''
+ Additional text appended to <filename>syslog.conf</filename>,
+ i.e. the contents of <varname>defaultConfig</varname>.
+ '';
+ };
+
+ extraParams = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "-m 0" ];
+ description = ''
+ Additional parameters passed to <command>rsyslogd</command>.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.rsyslog ];
+
+ systemd.services.syslog =
+ { description = "Syslog Daemon";
+
+ requires = [ "syslog.socket" ];
+
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig =
+ { ExecStart = "${pkgs.rsyslog}/sbin/rsyslogd ${toString cfg.extraParams} -f ${syslogConf} -n";
+ ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/spool/rsyslog";
+ # Prevent syslogd output looping back through journald.
+ StandardOutput = "null";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/logging/syslog-ng.nix b/nixpkgs/nixos/modules/services/logging/syslog-ng.nix
new file mode 100644
index 00000000000..65e103ac2ba
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/syslog-ng.nix
@@ -0,0 +1,97 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.syslog-ng;
+
+ syslogngConfig = pkgs.writeText "syslog-ng.conf" ''
+ ${cfg.configHeader}
+ ${cfg.extraConfig}
+ '';
+
+ ctrlSocket = "/run/syslog-ng/syslog-ng.ctl";
+ pidFile = "/run/syslog-ng/syslog-ng.pid";
+ persistFile = "/var/syslog-ng/syslog-ng.persist";
+
+ syslogngOptions = [
+ "--foreground"
+ "--module-path=${concatStringsSep ":" (["${cfg.package}/lib/syslog-ng"] ++ cfg.extraModulePaths)}"
+ "--cfgfile=${syslogngConfig}"
+ "--control=${ctrlSocket}"
+ "--persist-file=${persistFile}"
+ "--pidfile=${pidFile}"
+ ];
+
+in {
+
+ options = {
+
+ services.syslog-ng = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the syslog-ng daemon.
+ '';
+ };
+ package = mkOption {
+ type = types.package;
+ default = pkgs.syslogng;
+ defaultText = "pkgs.syslogng";
+ description = ''
+ The package providing syslog-ng binaries.
+ '';
+ };
+ extraModulePaths = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = literalExample ''
+ [ "''${pkgs.syslogng_incubator}/lib/syslog-ng" ]
+ '';
+ description = ''
+ A list of paths that should be included in syslog-ng's
+ <literal>--module-path</literal> option. They should usually
+ end in <literal>/lib/syslog-ng</literal>
+ '';
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Configuration added to the end of <literal>syslog-ng.conf</literal>.
+ '';
+ };
+ configHeader = mkOption {
+ type = types.lines;
+ default = ''
+ @version: 3.6
+ @include "scl.conf"
+ '';
+ description = ''
+ The very first lines of the configuration file. Should usually contain
+ the syslog-ng version header.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.syslog-ng = {
+ description = "syslog-ng daemon";
+ preStart = "mkdir -p /{var,run}/syslog-ng";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "multi-user.target" ]; # makes sure hostname etc is set
+ serviceConfig = {
+ Type = "notify";
+ PIDFile = pidFile;
+ StandardOutput = "null";
+ Restart = "on-failure";
+ ExecStart = "${cfg.package}/sbin/syslog-ng ${concatStringsSep " " syslogngOptions}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/logging/syslogd.nix b/nixpkgs/nixos/modules/services/logging/syslogd.nix
new file mode 100644
index 00000000000..fe0b0490811
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/syslogd.nix
@@ -0,0 +1,130 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.syslogd;
+
+ syslogConf = pkgs.writeText "syslog.conf" ''
+ ${if (cfg.tty != "") then "kern.warning;*.err;authpriv.none /dev/${cfg.tty}" else ""}
+ ${cfg.defaultConfig}
+ ${cfg.extraConfig}
+ '';
+
+ defaultConf = ''
+ # Send emergency messages to all users.
+ *.emerg *
+
+ # "local1" is used for dhcpd messages.
+ local1.* -/var/log/dhcpd
+
+ mail.* -/var/log/mail
+
+ *.=warning;*.=err -/var/log/warn
+ *.crit /var/log/warn
+
+ *.*;mail.none;local1.none -/var/log/messages
+ '';
+
+in
+
+{
+ ###### interface
+
+ options = {
+
+ services.syslogd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable syslogd. Note that systemd also logs
+ syslog messages, so you normally don't need to run syslogd.
+ '';
+ };
+
+ tty = mkOption {
+ type = types.str;
+ default = "tty10";
+ description = ''
+ The tty device on which syslogd will print important log
+ messages. Leave this option blank to disable tty logging.
+ '';
+ };
+
+ defaultConfig = mkOption {
+ type = types.lines;
+ default = defaultConf;
+ description = ''
+ The default <filename>syslog.conf</filename> file configures a
+ fairly standard setup of log files, which can be extended by
+ means of <varname>extraConfig</varname>.
+ '';
+ };
+
+ enableNetworkInput = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Accept logging through UDP. Option -r of syslogd(8).
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "news.* -/var/log/news";
+ description = ''
+ Additional text appended to <filename>syslog.conf</filename>,
+ i.e. the contents of <varname>defaultConfig</varname>.
+ '';
+ };
+
+ extraParams = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "-m 0" ];
+ description = ''
+ Additional parameters passed to <command>syslogd</command>.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions =
+ [ { assertion = !config.services.rsyslogd.enable;
+ message = "rsyslogd conflicts with syslogd";
+ }
+ ];
+
+ environment.systemPackages = [ pkgs.sysklogd ];
+
+ services.syslogd.extraParams = optional cfg.enableNetworkInput "-r";
+
+ # FIXME: restarting syslog seems to break journal logging.
+ systemd.services.syslog =
+ { description = "Syslog Daemon";
+
+ requires = [ "syslog.socket" ];
+
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig =
+ { ExecStart = "${pkgs.sysklogd}/sbin/syslogd ${toString cfg.extraParams} -f ${syslogConf} -n";
+ # Prevent syslogd output looping back through journald.
+ StandardOutput = "null";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/clamsmtp.nix b/nixpkgs/nixos/modules/services/mail/clamsmtp.nix
new file mode 100644
index 00000000000..fc1267c5d28
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/clamsmtp.nix
@@ -0,0 +1,181 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.clamsmtp;
+ clamdSocket = "/run/clamav/clamd.ctl"; # See services/security/clamav.nix
+in
+{
+ ##### interface
+ options = {
+ services.clamsmtp = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable clamsmtp.";
+ };
+
+ instances = mkOption {
+ description = "Instances of clamsmtp to run.";
+ type = types.listOf (types.submodule { options = {
+ action = mkOption {
+ type = types.enum [ "bounce" "drop" "pass" ];
+ default = "drop";
+ description =
+ ''
+ Action to take when a virus is detected.
+
+ Note that viruses often spoof sender addresses, so bouncing is
+ in most cases not a good idea.
+ '';
+ };
+
+ header = mkOption {
+ type = types.str;
+ default = "";
+ example = "X-Virus-Scanned: ClamAV using ClamSMTP";
+ description =
+ ''
+ A header to add to scanned messages. See clamsmtpd.conf(5) for
+ more details. Empty means no header.
+ '';
+ };
+
+ keepAlives = mkOption {
+ type = types.int;
+ default = 0;
+ description =
+ ''
+ Number of seconds to wait between each NOOP sent to the sending
+ server. 0 to disable.
+
+ This is meant for slow servers where the sending MTA times out
+ waiting for clamd to scan the file.
+ '';
+ };
+
+ listen = mkOption {
+ type = types.str;
+ example = "127.0.0.1:10025";
+ description =
+ ''
+ Address to wait for incoming SMTP connections on. See
+ clamsmtpd.conf(5) for more details.
+ '';
+ };
+
+ quarantine = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Whether to quarantine files that contain viruses by leaving them
+ in the temporary directory.
+ '';
+ };
+
+ maxConnections = mkOption {
+ type = types.int;
+ default = 64;
+ description = "Maximum number of connections to accept at once.";
+ };
+
+ outAddress = mkOption {
+ type = types.str;
+ description =
+ ''
+ Address of the SMTP server to send email to once it has been
+ scanned.
+ '';
+ };
+
+ tempDirectory = mkOption {
+ type = types.str;
+ default = "/tmp";
+ description =
+ ''
+ Temporary directory that needs to be accessible to both clamd
+ and clamsmtpd.
+ '';
+ };
+
+ timeout = mkOption {
+ type = types.int;
+ default = 180;
+ description = "Time-out for network connections.";
+ };
+
+ transparentProxy = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable clamsmtp's transparent proxy support.";
+ };
+
+ virusAction = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description =
+ ''
+ Command to run when a virus is found. Please see VIRUS ACTION in
+ clamsmtpd(8) for a discussion of this option and its safe use.
+ '';
+ };
+
+ xClient = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Send the XCLIENT command to the receiving server, for forwarding
+ client addresses and connection information if the receiving
+ server supports this feature.
+ '';
+ };
+ };});
+ };
+ };
+ };
+
+ ##### implementation
+ config = let
+ configfile = conf: pkgs.writeText "clamsmtpd.conf"
+ ''
+ Action: ${conf.action}
+ ClamAddress: ${clamdSocket}
+ Header: ${conf.header}
+ KeepAlives: ${toString conf.keepAlives}
+ Listen: ${conf.listen}
+ Quarantine: ${if conf.quarantine then "on" else "off"}
+ MaxConnections: ${toString conf.maxConnections}
+ OutAddress: ${conf.outAddress}
+ TempDirectory: ${conf.tempDirectory}
+ TimeOut: ${toString conf.timeout}
+ TransparentProxy: ${if conf.transparentProxy then "on" else "off"}
+ User: clamav
+ ${optionalString (conf.virusAction != null) "VirusAction: ${conf.virusAction}"}
+ XClient: ${if conf.xClient then "on" else "off"}
+ '';
+ in
+ mkIf cfg.enable {
+ assertions = [
+ { assertion = config.services.clamav.daemon.enable;
+ message = "clamsmtp requires clamav to be enabled";
+ }
+ ];
+
+ systemd.services = listToAttrs (imap1 (i: conf:
+ nameValuePair "clamsmtp-${toString i}" {
+ description = "ClamSMTP instance ${toString i}";
+ wantedBy = [ "multi-user.target" ];
+ script = "exec ${pkgs.clamsmtp}/bin/clamsmtpd -f ${configfile conf}";
+ after = [ "clamav-daemon.service" ];
+ requires = [ "clamav-daemon.service" ];
+ serviceConfig.Type = "forking";
+ serviceConfig.PrivateTmp = "yes";
+ unitConfig.JoinsNamespaceOf = "clamav-daemon.service";
+ }
+ ) cfg.instances);
+ };
+
+ meta.maintainers = with lib.maintainers; [ ekleog ];
+}
diff --git a/nixpkgs/nixos/modules/services/mail/davmail.nix b/nixpkgs/nixos/modules/services/mail/davmail.nix
new file mode 100644
index 00000000000..374a3dd75c1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/davmail.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.davmail;
+
+ configType = with types;
+ oneOf [ (attrsOf configType) str int bool ] // {
+ description = "davmail config type (str, int, bool or attribute set thereof)";
+ };
+
+ toStr = val: if isBool val then boolToString val else toString val;
+
+ linesForAttrs = attrs: concatMap (name: let value = attrs.${name}; in
+ if isAttrs value
+ then map (line: name + "." + line) (linesForAttrs value)
+ else [ "${name}=${toStr value}" ]
+ ) (attrNames attrs);
+
+ configFile = pkgs.writeText "davmail.properties" (concatStringsSep "\n" (linesForAttrs cfg.config));
+
+in
+
+ {
+ options.services.davmail = {
+ enable = mkEnableOption "davmail, an MS Exchange gateway";
+
+ url = mkOption {
+ type = types.str;
+ description = "Outlook Web Access URL to access the exchange server, i.e. the base webmail URL.";
+ example = "https://outlook.office365.com/EWS/Exchange.asmx";
+ };
+
+ config = mkOption {
+ type = configType;
+ default = {};
+ description = ''
+ Davmail configuration. Refer to
+ <link xlink:href="http://davmail.sourceforge.net/serversetup.html"/>
+ and <link xlink:href="http://davmail.sourceforge.net/advanced.html"/>
+ for details on supported values.
+ '';
+ example = literalExample ''
+ {
+ davmail.allowRemote = true;
+ davmail.imapPort = 55555;
+ davmail.bindAddress = "10.0.1.2";
+ davmail.smtpSaveInSent = true;
+ davmail.folderSizeLimit = 10;
+ davmail.caldavAutoSchedule = false;
+ log4j.logger.rootLogger = "DEBUG";
+ }
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ services.davmail.config = {
+ davmail = mapAttrs (name: mkDefault) {
+ server = true;
+ disableUpdateCheck = true;
+ logFilePath = "/var/log/davmail/davmail.log";
+ logFileSize = "1MB";
+ mode = "auto";
+ url = cfg.url;
+ caldavPort = 1080;
+ imapPort = 1143;
+ ldapPort = 1389;
+ popPort = 1110;
+ smtpPort = 1025;
+ };
+ log4j = {
+ logger.davmail = mkDefault "WARN";
+ logger.httpclient.wire = mkDefault "WARN";
+ logger.org.apache.commons.httpclient = mkDefault "WARN";
+ rootLogger = mkDefault "WARN";
+ };
+ };
+
+ systemd.services.davmail = {
+ description = "DavMail POP/IMAP/SMTP Exchange Gateway";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${pkgs.davmail}/bin/davmail ${configFile}";
+ Restart = "on-failure";
+ DynamicUser = "yes";
+ LogsDirectory = "davmail";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.davmail ];
+ };
+ }
diff --git a/nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix b/nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix
new file mode 100644
index 00000000000..f4ac9e47007
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix
@@ -0,0 +1,120 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.dkimproxy-out;
+ keydir = "/var/lib/dkimproxy-out";
+ privkey = "${keydir}/private.key";
+ pubkey = "${keydir}/public.key";
+in
+{
+ ##### interface
+ options = {
+ services.dkimproxy-out = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Whether to enable dkimproxy_out.
+
+ Note that a key will be auto-generated, and can be found in
+ ${keydir}.
+ '';
+ };
+
+ listen = mkOption {
+ type = types.str;
+ example = "127.0.0.1:10027";
+ description = "Address:port DKIMproxy should listen on.";
+ };
+
+ relay = mkOption {
+ type = types.str;
+ example = "127.0.0.1:10028";
+ description = "Address:port DKIMproxy should forward mail to.";
+ };
+
+ domains = mkOption {
+ type = with types; listOf str;
+ example = [ "example.org" "example.com" ];
+ description = "List of domains DKIMproxy can sign for.";
+ };
+
+ selector = mkOption {
+ type = types.str;
+ example = "selector1";
+ description =
+ ''
+ The selector to use for DKIM key identification.
+
+ For example, if 'selector1' is used here, then for each domain
+ 'example.org' given in `domain`, 'selector1._domainkey.example.org'
+ should contain the TXT record indicating the public key is the one
+ in ${pubkey}: "v=DKIM1; t=s; p=[THE PUBLIC KEY]".
+ '';
+ };
+
+ keySize = mkOption {
+ type = types.int;
+ default = 2048;
+ description =
+ ''
+ Size of the RSA key to use to sign outgoing emails. Note that the
+ maximum mandatorily verified as per RFC6376 is 2048.
+ '';
+ };
+
+ # TODO: allow signature for other schemes than dkim(c=relaxed/relaxed)?
+ # This being the scheme used by gmail, maybe nothing more is needed for
+ # reasonable use.
+ };
+ };
+
+ ##### implementation
+ config = let
+ configfile = pkgs.writeText "dkimproxy_out.conf"
+ ''
+ listen ${cfg.listen}
+ relay ${cfg.relay}
+
+ domain ${concatStringsSep "," cfg.domains}
+ selector ${cfg.selector}
+
+ signature dkim(c=relaxed/relaxed)
+
+ keyfile ${privkey}
+ '';
+ in
+ mkIf cfg.enable {
+ users.groups.dkimproxy-out = {};
+ users.users.dkimproxy-out = {
+ description = "DKIMproxy_out daemon";
+ group = "dkimproxy-out";
+ isSystemUser = true;
+ };
+
+ systemd.services.dkimproxy-out = {
+ description = "DKIMproxy_out";
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ if [ ! -d "${keydir}" ]; then
+ mkdir -p "${keydir}"
+ chmod 0700 "${keydir}"
+ ${pkgs.openssl}/bin/openssl genrsa -out "${privkey}" ${toString cfg.keySize}
+ ${pkgs.openssl}/bin/openssl rsa -in "${privkey}" -pubout -out "${pubkey}"
+ chown -R dkimproxy-out:dkimproxy-out "${keydir}"
+ fi
+ '';
+ script = ''
+ exec ${pkgs.dkimproxy}/bin/dkimproxy.out --conf_file=${configfile}
+ '';
+ serviceConfig = {
+ User = "dkimproxy-out";
+ PermissionsStartOnly = true;
+ };
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ ekleog ];
+}
diff --git a/nixpkgs/nixos/modules/services/mail/dovecot.nix b/nixpkgs/nixos/modules/services/mail/dovecot.nix
new file mode 100644
index 00000000000..cdbb776454b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/dovecot.nix
@@ -0,0 +1,400 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dovecot2;
+ dovecotPkg = pkgs.dovecot;
+
+ baseDir = "/run/dovecot2";
+ stateDir = "/var/lib/dovecot";
+
+ dovecotConf = concatStrings [
+ ''
+ base_dir = ${baseDir}
+ protocols = ${concatStringsSep " " cfg.protocols}
+ sendmail_path = /run/wrappers/bin/sendmail
+ ''
+
+ (if cfg.sslServerCert == null then ''
+ ssl = no
+ disable_plaintext_auth = no
+ '' else ''
+ ssl_cert = <${cfg.sslServerCert}
+ ssl_key = <${cfg.sslServerKey}
+ ${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)}
+ ssl_dh = <${config.security.dhparams.params.dovecot2.path}
+ disable_plaintext_auth = yes
+ '')
+
+ ''
+ default_internal_user = ${cfg.user}
+ default_internal_group = ${cfg.group}
+ ${optionalString (cfg.mailUser != null) "mail_uid = ${cfg.mailUser}"}
+ ${optionalString (cfg.mailGroup != null) "mail_gid = ${cfg.mailGroup}"}
+
+ mail_location = ${cfg.mailLocation}
+
+ maildir_copy_with_hardlinks = yes
+ pop3_uidl_format = %08Xv%08Xu
+
+ auth_mechanisms = plain login
+
+ service auth {
+ user = root
+ }
+ ''
+
+ (optionalString cfg.enablePAM ''
+ userdb {
+ driver = passwd
+ }
+
+ passdb {
+ driver = pam
+ args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2
+ }
+ '')
+
+ (optionalString (cfg.sieveScripts != {}) ''
+ plugin {
+ ${concatStringsSep "\n" (mapAttrsToList (to: from: "sieve_${to} = ${stateDir}/sieve/${to}") cfg.sieveScripts)}
+ }
+ '')
+
+ (optionalString (cfg.mailboxes != []) ''
+ protocol imap {
+ namespace inbox {
+ inbox=yes
+ ${concatStringsSep "\n" (map mailboxConfig cfg.mailboxes)}
+ }
+ }
+ '')
+
+ (optionalString cfg.enableQuota ''
+ mail_plugins = $mail_plugins quota
+ service quota-status {
+ executable = ${dovecotPkg}/libexec/dovecot/quota-status -p postfix
+ inet_listener {
+ port = ${cfg.quotaPort}
+ }
+ client_limit = 1
+ }
+
+ protocol imap {
+ mail_plugins = $mail_plugins imap_quota
+ }
+
+ plugin {
+ quota_rule = *:storage=${cfg.quotaGlobalPerUser}
+ quota = maildir:User quota # per virtual mail user quota # BUG/FIXME broken, we couldn't get this working
+ quota_status_success = DUNNO
+ quota_status_nouser = DUNNO
+ quota_status_overquota = "552 5.2.2 Mailbox is full"
+ quota_grace = 10%%
+ }
+ '')
+
+ cfg.extraConfig
+ ];
+
+ modulesDir = pkgs.symlinkJoin {
+ name = "dovecot-modules";
+ paths = map (pkg: "${pkg}/lib/dovecot") ([ dovecotPkg ] ++ map (module: module.override { dovecot = dovecotPkg; }) cfg.modules);
+ };
+
+ mailboxConfig = mailbox: ''
+ mailbox "${mailbox.name}" {
+ auto = ${toString mailbox.auto}
+ '' + optionalString (mailbox.specialUse != null) ''
+ special_use = \${toString mailbox.specialUse}
+ '' + "}";
+
+ mailboxes = { ... }: {
+ options = {
+ name = mkOption {
+ type = types.strMatching ''[^"]+'';
+ example = "Spam";
+ description = "The name of the mailbox.";
+ };
+ auto = mkOption {
+ type = types.enum [ "no" "create" "subscribe" ];
+ default = "no";
+ example = "subscribe";
+ description = "Whether to automatically create or create and subscribe to the mailbox or not.";
+ };
+ specialUse = mkOption {
+ type = types.nullOr (types.enum [ "All" "Archive" "Drafts" "Flagged" "Junk" "Sent" "Trash" ]);
+ default = null;
+ example = "Junk";
+ description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid.";
+ };
+ };
+ };
+in
+{
+
+ options.services.dovecot2 = {
+ enable = mkEnableOption "Dovecot 2.x POP3/IMAP server";
+
+ enablePop3 = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Start the POP3 listener (when Dovecot is enabled).";
+ };
+
+ enableImap = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Start the IMAP listener (when Dovecot is enabled).";
+ };
+
+ enableLmtp = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Start the LMTP listener (when Dovecot is enabled).";
+ };
+
+ protocols = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = "Additional listeners to start when Dovecot is enabled.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "dovecot2";
+ description = "Dovecot user name.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "dovecot2";
+ description = "Dovecot group name.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "mail_debug = yes";
+ description = "Additional entries to put verbatim into Dovecot's config file.";
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Config file used for the whole dovecot configuration.";
+ apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf;
+ };
+
+ mailLocation = mkOption {
+ type = types.str;
+ default = "maildir:/var/spool/mail/%u"; /* Same as inbox, as postfix */
+ example = "maildir:~/mail:INBOX=/var/spool/mail/%u";
+ description = ''
+ Location that dovecot will use for mail folders. Dovecot mail_location option.
+ '';
+ };
+
+ mailUser = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Default user to store mail for virtual users.";
+ };
+
+ mailGroup = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Default group to store mail for virtual users.";
+ };
+
+ createMailUser = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''Whether to automatically create the user
+ given in <option>services.dovecot.user</option> and the group
+ given in <option>services.dovecot.group</option>.'';
+ };
+
+ modules = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "[ pkgs.dovecot_pigeonhole ]";
+ description = ''
+ Symlinks the contents of lib/dovecot of every given package into
+ /etc/dovecot/modules. This will make the given modules available
+ if a dovecot package with the module_dir patch applied is being used.
+ '';
+ };
+
+ sslCACert = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Path to the server's CA certificate key.";
+ };
+
+ sslServerCert = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Path to the server's public key.";
+ };
+
+ sslServerKey = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Path to the server's private key.";
+ };
+
+ enablePAM = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to create a own Dovecot PAM service and configure PAM user logins.";
+ };
+
+ sieveScripts = mkOption {
+ type = types.attrsOf types.path;
+ default = {};
+ description = "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc.";
+ };
+
+ showPAMFailure = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Show the PAM failure message on authentication error (useful for OTPW).";
+ };
+
+ mailboxes = mkOption {
+ type = types.listOf (types.submodule mailboxes);
+ default = [];
+ example = [ { name = "Spam"; specialUse = "Junk"; auto = "create"; } ];
+ description = "Configure mailboxes and auto create or subscribe them.";
+ };
+
+ enableQuota = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = "Whether to enable the dovecot quota service.";
+ };
+
+ quotaPort = mkOption {
+ type = types.str;
+ default = "12340";
+ description = ''
+ The Port the dovecot quota service binds to.
+ If using postfix, add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config.
+ '';
+ };
+ quotaGlobalPerUser = mkOption {
+ type = types.str;
+ default = "100G";
+ example = "10G";
+ description = "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %.";
+ };
+
+ };
+
+
+ config = mkIf cfg.enable {
+ security.pam.services.dovecot2 = mkIf cfg.enablePAM {};
+
+ security.dhparams = mkIf (cfg.sslServerCert != null) {
+ enable = true;
+ params.dovecot2 = {};
+ };
+ services.dovecot2.protocols =
+ optional cfg.enableImap "imap"
+ ++ optional cfg.enablePop3 "pop3"
+ ++ optional cfg.enableLmtp "lmtp";
+
+ users.users = [
+ { name = "dovenull";
+ uid = config.ids.uids.dovenull2;
+ description = "Dovecot user for untrusted logins";
+ group = "dovenull";
+ }
+ ] ++ optional (cfg.user == "dovecot2")
+ { name = "dovecot2";
+ uid = config.ids.uids.dovecot2;
+ description = "Dovecot user";
+ group = cfg.group;
+ }
+ ++ optional (cfg.createMailUser && cfg.mailUser != null)
+ ({ name = cfg.mailUser;
+ description = "Virtual Mail User";
+ } // optionalAttrs (cfg.mailGroup != null) {
+ group = cfg.mailGroup;
+ });
+
+ users.groups = optional (cfg.group == "dovecot2")
+ { name = "dovecot2";
+ gid = config.ids.gids.dovecot2;
+ }
+ ++ optional (cfg.createMailUser && cfg.mailGroup != null)
+ { name = cfg.mailGroup;
+ }
+ ++ singleton
+ { name = "dovenull";
+ gid = config.ids.gids.dovenull2;
+ };
+
+ environment.etc."dovecot/modules".source = modulesDir;
+ environment.etc."dovecot/dovecot.conf".source = cfg.configFile;
+
+ systemd.services.dovecot2 = {
+ description = "Dovecot IMAP/POP3 server";
+
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ cfg.configFile ];
+
+ serviceConfig = {
+ ExecStart = "${dovecotPkg}/sbin/dovecot -F";
+ ExecReload = "${dovecotPkg}/sbin/doveadm reload";
+ Restart = "on-failure";
+ RestartSec = "1s";
+ StartLimitInterval = "1min";
+ RuntimeDirectory = [ "dovecot2" ];
+ };
+
+ # When copying sieve scripts preserve the original time stamp
+ # (should be 0) so that the compiled sieve script is newer than
+ # the source file and Dovecot won't try to compile it.
+ preStart = ''
+ rm -rf ${stateDir}/sieve
+ '' + optionalString (cfg.sieveScripts != {}) ''
+ mkdir -p ${stateDir}/sieve
+ ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
+ if [ -d '${from}' ]; then
+ mkdir '${stateDir}/sieve/${to}'
+ cp -p "${from}/"*.sieve '${stateDir}/sieve/${to}'
+ else
+ cp -p '${from}' '${stateDir}/sieve/${to}'
+ fi
+ ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}'
+ '') cfg.sieveScripts)}
+ chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve'
+ '';
+ };
+
+ environment.systemPackages = [ dovecotPkg ];
+
+ assertions = [
+ { assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != [];
+ message = "dovecot needs at least one of the IMAP or POP3 listeners enabled";
+ }
+ { assertion = (cfg.sslServerCert == null) == (cfg.sslServerKey == null)
+ && (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null));
+ message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto";
+ }
+ { assertion = cfg.showPAMFailure -> cfg.enablePAM;
+ message = "dovecot is configured with showPAMFailure while enablePAM is disabled";
+ }
+ { assertion = cfg.sieveScripts != {} -> (cfg.mailUser != null && cfg.mailGroup != null);
+ message = "dovecot requires mailUser and mailGroup to be set when sieveScripts is set";
+ }
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/dspam.nix b/nixpkgs/nixos/modules/services/mail/dspam.nix
new file mode 100644
index 00000000000..72b8c4c08b9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/dspam.nix
@@ -0,0 +1,150 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.dspam;
+
+ dspam = pkgs.dspam;
+
+ defaultSock = "/run/dspam/dspam.sock";
+
+ cfgfile = pkgs.writeText "dspam.conf" ''
+ Home /var/lib/dspam
+ StorageDriver ${dspam}/lib/dspam/lib${cfg.storageDriver}_drv.so
+
+ Trust root
+ Trust ${cfg.user}
+ SystemLog on
+ UserLog on
+
+ ${optionalString (cfg.domainSocket != null) ''
+ ServerDomainSocketPath "${cfg.domainSocket}"
+ ClientHost "${cfg.domainSocket}"
+ ''}
+
+ ${cfg.extraConfig}
+ '';
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.dspam = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the dspam spam filter.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "dspam";
+ description = "User for the dspam daemon.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "dspam";
+ description = "Group for the dspam daemon.";
+ };
+
+ storageDriver = mkOption {
+ type = types.str;
+ default = "hash";
+ description = "Storage driver backend to use for dspam.";
+ };
+
+ domainSocket = mkOption {
+ type = types.nullOr types.path;
+ default = defaultSock;
+ description = "Path to local domain socket which is used for communication with the daemon. Set to null to disable UNIX socket.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional dspam configuration.";
+ };
+
+ maintenanceInterval = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "If set, maintenance script will be run at specified (in systemd.timer format) interval";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ users.users = optionalAttrs (cfg.user == "dspam") (singleton
+ { name = "dspam";
+ group = cfg.group;
+ uid = config.ids.uids.dspam;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "dspam") (singleton
+ { name = "dspam";
+ gid = config.ids.gids.dspam;
+ });
+
+ environment.systemPackages = [ dspam ];
+
+ environment.etc."dspam/dspam.conf".source = cfgfile;
+
+ systemd.services.dspam = {
+ description = "dspam spam filtering daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "postgresql.service" ];
+ restartTriggers = [ cfgfile ];
+
+ serviceConfig = {
+ ExecStart = "${dspam}/bin/dspam --daemon --nofork";
+ User = cfg.user;
+ Group = cfg.group;
+ RuntimeDirectory = optional (cfg.domainSocket == defaultSock) "dspam";
+ RuntimeDirectoryMode = optional (cfg.domainSocket == defaultSock) "0750";
+ StateDirectory = "dspam";
+ StateDirectoryMode = "0750";
+ LogsDirectory = "dspam";
+ LogsDirectoryMode = "0750";
+ # DSPAM segfaults on just about every error
+ Restart = "on-abort";
+ RestartSec = "1s";
+ };
+ };
+ }
+
+ (mkIf (cfg.maintenanceInterval != null) {
+ systemd.timers.dspam-maintenance = {
+ description = "Timer for dspam maintenance script";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = cfg.maintenanceInterval;
+ Unit = "dspam-maintenance.service";
+ };
+ };
+
+ systemd.services.dspam-maintenance = {
+ description = "dspam maintenance script";
+ restartTriggers = [ cfgfile ];
+
+ serviceConfig = {
+ ExecStart = "${dspam}/bin/dspam_maintenance --verbose";
+ Type = "oneshot";
+ User = cfg.user;
+ Group = cfg.group;
+ };
+ };
+ })
+ ]);
+}
diff --git a/nixpkgs/nixos/modules/services/mail/exim.nix b/nixpkgs/nixos/modules/services/mail/exim.nix
new file mode 100644
index 00000000000..47812dd1e40
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/exim.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkIf mkOption singleton types;
+ inherit (pkgs) coreutils;
+ cfg = config.services.exim;
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.exim = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Exim mail transfer agent.";
+ };
+
+ config = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Verbatim Exim configuration. This should not contain exim_user,
+ exim_group, exim_path, or spool_directory.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "exim";
+ description = ''
+ User to use when no root privileges are required.
+ In particular, this applies when receiving messages and when doing
+ remote deliveries. (Local deliveries run as various non-root users,
+ typically as the owner of a local mailbox.) Specifying this value
+ as root is not supported.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "exim";
+ description = ''
+ Group to use when no root privileges are required.
+ '';
+ };
+
+ spoolDir = mkOption {
+ type = types.path;
+ default = "/var/spool/exim";
+ description = ''
+ Location of the spool directory of exim.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.exim;
+ defaultText = "pkgs.exim";
+ description = ''
+ The Exim derivation to use.
+ This can be used to enable features such as LDAP or PAM support.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment = {
+ etc."exim.conf".text = ''
+ exim_user = ${cfg.user}
+ exim_group = ${cfg.group}
+ exim_path = /run/wrappers/bin/exim
+ spool_directory = ${cfg.spoolDir}
+ ${cfg.config}
+ '';
+ systemPackages = [ cfg.package ];
+ };
+
+ users.users = singleton {
+ name = cfg.user;
+ description = "Exim mail transfer agent user";
+ uid = config.ids.uids.exim;
+ group = cfg.group;
+ };
+
+ users.groups = singleton {
+ name = cfg.group;
+ gid = config.ids.gids.exim;
+ };
+
+ security.wrappers.exim.source = "${cfg.package}/bin/exim";
+
+ systemd.services.exim = {
+ description = "Exim Mail Daemon";
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ config.environment.etc."exim.conf".source ];
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/exim -bdf -q30m";
+ ExecReload = "${coreutils}/bin/kill -HUP $MAINPID";
+ };
+ preStart = ''
+ if ! test -d ${cfg.spoolDir}; then
+ ${coreutils}/bin/mkdir -p ${cfg.spoolDir}
+ ${coreutils}/bin/chown ${cfg.user}:${cfg.group} ${cfg.spoolDir}
+ fi
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/freepops.nix b/nixpkgs/nixos/modules/services/mail/freepops.nix
new file mode 100644
index 00000000000..5b729ca50a5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/freepops.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.mail.freepopsd;
+in
+
+{
+ options = {
+ services.mail.freepopsd = {
+ enable = mkOption {
+ default = false;
+ type = with types; bool;
+ description = ''
+ Enables Freepops, a POP3 webmail wrapper.
+ '';
+ };
+
+ port = mkOption {
+ default = 2000;
+ type = with types; uniq int;
+ description = ''
+ Port on which the pop server will listen.
+ '';
+ };
+
+ threads = mkOption {
+ default = 5;
+ type = with types; uniq int;
+ description = ''
+ Max simultaneous connections.
+ '';
+ };
+
+ bind = mkOption {
+ default = "0.0.0.0";
+ type = types.str;
+ description = ''
+ Bind over an IPv4 address instead of any.
+ '';
+ };
+
+ logFile = mkOption {
+ default = "/var/log/freepopsd";
+ example = "syslog";
+ type = types.str;
+ description = ''
+ Filename of the log file or syslog to rely on the logging daemon.
+ '';
+ };
+
+ suid = {
+ user = mkOption {
+ default = "nobody";
+ type = types.str;
+ description = ''
+ User name under which freepopsd will be after binding the port.
+ '';
+ };
+
+ group = mkOption {
+ default = "nogroup";
+ type = types.str;
+ description = ''
+ Group under which freepopsd will be after binding the port.
+ '';
+ };
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.freepopsd = {
+ description = "Freepopsd (webmail over POP3)";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ script = ''
+ ${pkgs.freepops}/bin/freepopsd \
+ -p ${toString cfg.port} \
+ -t ${toString cfg.threads} \
+ -b ${cfg.bind} \
+ -vv -l ${cfg.logFile} \
+ -s ${cfg.suid.user}.${cfg.suid.group}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/mail.nix b/nixpkgs/nixos/modules/services/mail/mail.nix
new file mode 100644
index 00000000000..fed313e4738
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mail.nix
@@ -0,0 +1,33 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.mail = {
+
+ sendmailSetuidWrapper = mkOption {
+ default = null;
+ internal = true;
+ description = ''
+ Configuration for the sendmail setuid wapper.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf (config.services.mail.sendmailSetuidWrapper != null) {
+
+ security.wrappers.sendmail = config.services.mail.sendmailSetuidWrapper;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/mailcatcher.nix b/nixpkgs/nixos/modules/services/mail/mailcatcher.nix
new file mode 100644
index 00000000000..f5b4508b335
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mailcatcher.nix
@@ -0,0 +1,61 @@
+{ config, pkgs, lib, ... }:
+
+let
+ cfg = config.services.mailcatcher;
+
+ inherit (lib) mkEnableOption mkIf mkOption types optionalString;
+in
+{
+ # interface
+
+ options = {
+
+ services.mailcatcher = {
+ enable = mkEnableOption "MailCatcher";
+
+ http.ip = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "The ip address of the http server.";
+ };
+
+ http.port = mkOption {
+ type = types.port;
+ default = 1080;
+ description = "The port address of the http server.";
+ };
+
+ smtp.ip = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "The ip address of the smtp server.";
+ };
+
+ smtp.port = mkOption {
+ type = types.port;
+ default = 1025;
+ description = "The port address of the smtp server.";
+ };
+ };
+
+ };
+
+ # implementation
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.mailcatcher ];
+
+ systemd.services.mailcatcher = {
+ description = "MailCatcher Service";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ DynamicUser = true;
+ Restart = "always";
+ ExecStart = "${pkgs.mailcatcher}/bin/mailcatcher --foreground --no-quit --http-ip ${cfg.http.ip} --http-port ${toString cfg.http.port} --smtp-ip ${cfg.smtp.ip} --smtp-port ${toString cfg.smtp.port}";
+ AmbientCapabilities = optionalString (cfg.http.port < 1024 || cfg.smtp.port < 1024) "cap_net_bind_service";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/mailhog.nix b/nixpkgs/nixos/modules/services/mail/mailhog.nix
new file mode 100644
index 00000000000..b78f4c8e0e6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mailhog.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.mailhog;
+in {
+ ###### interface
+
+ options = {
+
+ services.mailhog = {
+ enable = mkEnableOption "MailHog";
+ user = mkOption {
+ type = types.str;
+ default = "mailhog";
+ description = "User account under which mailhog runs.";
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users.mailhog = {
+ name = cfg.user;
+ description = "MailHog service user";
+ };
+
+ systemd.services.mailhog = {
+ description = "MailHog service";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${pkgs.mailhog}/bin/MailHog";
+ User = cfg.user;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/mailman.nix b/nixpkgs/nixos/modules/services/mail/mailman.nix
new file mode 100644
index 00000000000..e917209f3d1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mailman.nix
@@ -0,0 +1,297 @@
+{ config, pkgs, lib, ... }: # mailman.nix
+
+with lib;
+
+let
+
+ cfg = config.services.mailman;
+
+ mailmanPyEnv = pkgs.python3.withPackages (ps: with ps; [mailman mailman-hyperkitty]);
+
+ mailmanExe = with pkgs; stdenv.mkDerivation {
+ name = "mailman-" + python3Packages.mailman.version;
+ buildInputs = [makeWrapper];
+ unpackPhase = ":";
+ installPhase = ''
+ mkdir -p $out/bin
+ makeWrapper ${mailmanPyEnv}/bin/mailman $out/bin/mailman \
+ --set MAILMAN_CONFIG_FILE /etc/mailman.cfg
+ '';
+ };
+
+ mailmanWeb = pkgs.python3Packages.mailman-web.override {
+ serverEMail = cfg.siteOwner;
+ archiverKey = cfg.hyperkittyApiKey;
+ allowedHosts = cfg.webHosts;
+ };
+
+ mailmanWebPyEnv = pkgs.python3.withPackages (x: with x; [mailman-web]);
+
+ mailmanWebExe = with pkgs; stdenv.mkDerivation {
+ inherit (mailmanWeb) name;
+ buildInputs = [makeWrapper];
+ unpackPhase = ":";
+ installPhase = ''
+ mkdir -p $out/bin
+ makeWrapper ${mailmanWebPyEnv}/bin/django-admin $out/bin/mailman-web \
+ --set DJANGO_SETTINGS_MODULE settings
+ '';
+ };
+
+ mailmanCfg = ''
+ [mailman]
+ site_owner: ${cfg.siteOwner}
+ layout: fhs
+
+ [paths.fhs]
+ bin_dir: ${pkgs.python3Packages.mailman}/bin
+ var_dir: /var/lib/mailman
+ queue_dir: $var_dir/queue
+ template_dir: $var_dir/templates
+ log_dir: $var_dir/log
+ lock_dir: $var_dir/lock
+ etc_dir: /etc
+ ext_dir: $etc_dir/mailman.d
+ pid_file: /run/mailman/master.pid
+ '' + optionalString (cfg.hyperkittyApiKey != null) ''
+ [archiver.hyperkitty]
+ class: mailman_hyperkitty.Archiver
+ enable: yes
+ configuration: ${pkgs.writeText "mailman-hyperkitty.cfg" mailmanHyperkittyCfg}
+ '';
+
+ mailmanHyperkittyCfg = ''
+ [general]
+ # This is your HyperKitty installation, preferably on the localhost. This
+ # address will be used by Mailman to forward incoming emails to HyperKitty
+ # for archiving. It does not need to be publicly available, in fact it's
+ # better if it is not.
+ base_url: ${cfg.hyperkittyBaseUrl}
+
+ # Shared API key, must be the identical to the value in HyperKitty's
+ # settings.
+ api_key: ${cfg.hyperkittyApiKey}
+ '';
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.mailman = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable Mailman on this host. Requires an active Postfix installation.";
+ };
+
+ siteOwner = mkOption {
+ type = types.str;
+ default = "postmaster@example.org";
+ description = ''
+ Certain messages that must be delivered to a human, but which can't
+ be delivered to a list owner (e.g. a bounce from a list owner), will
+ be sent to this address. It should point to a human.
+ '';
+ };
+
+ webRoot = mkOption {
+ type = types.path;
+ default = "${mailmanWeb}/${pkgs.python3.sitePackages}";
+ defaultText = "pkgs.python3Packages.mailman-web";
+ description = ''
+ The web root for the Hyperkity + Postorius apps provided by Mailman.
+ This variable can be set, of course, but it mainly exists so that site
+ admins can refer to it in their own hand-written httpd configuration files.
+ '';
+ };
+
+ webHosts = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ The list of hostnames and/or IP addresses from which the Mailman Web
+ UI will accept requests. By default, "localhost" and "127.0.0.1" are
+ enabled. All additional names under which your web server accepts
+ requests for the UI must be listed here or incoming requests will be
+ rejected.
+ '';
+ };
+
+ hyperkittyBaseUrl = mkOption {
+ type = types.str;
+ default = "http://localhost/hyperkitty/";
+ description = ''
+ Where can Mailman connect to Hyperkitty's internal API, preferably on
+ localhost?
+ '';
+ };
+
+ hyperkittyApiKey = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The shared secret used to authenticate Mailman's internal
+ communication with Hyperkitty. Must be set to enable support for the
+ Hyperkitty archiver. Note that this secret is going to be visible to
+ all local users in the Nix store.
+ '';
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.enable -> config.services.postfix.enable;
+ message = "Mailman requires Postfix";
+ }
+ ];
+
+ users.users.mailman = { description = "GNU Mailman"; isSystemUser = true; };
+
+ environment = {
+ systemPackages = [ mailmanExe mailmanWebExe pkgs.sassc ];
+ etc."mailman.cfg".text = mailmanCfg;
+ };
+
+ services.postfix = {
+ relayDomains = [ "hash:/var/lib/mailman/data/postfix_domains" ];
+ recipientDelimiter = "+"; # bake recipient addresses in mail envelopes via VERP
+ config = {
+ transport_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ];
+ local_recipient_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ];
+ owner_request_special = "no"; # Mailman handles -owner addresses on its own
+ };
+ };
+
+ systemd.services.mailman = {
+ description = "GNU Mailman Master Process";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${mailmanExe}/bin/mailman start";
+ ExecStop = "${mailmanExe}/bin/mailman stop";
+ User = "mailman";
+ Type = "forking";
+ StateDirectory = "mailman";
+ StateDirectoryMode = "0700";
+ RuntimeDirectory = "mailman";
+ PIDFile = "/run/mailman/master.pid";
+ };
+ };
+
+ systemd.services.mailman-web = {
+ description = "Init Postorius DB";
+ before = [ "httpd.service" ];
+ requiredBy = [ "httpd.service" ];
+ script = ''
+ ${mailmanWebExe}/bin/mailman-web migrate
+ rm -rf static
+ ${mailmanWebExe}/bin/mailman-web collectstatic
+ ${mailmanWebExe}/bin/mailman-web compress
+ '';
+ serviceConfig = {
+ User = config.services.httpd.user;
+ Type = "oneshot";
+ StateDirectory = "mailman-web";
+ StateDirectoryMode = "0700";
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
+ };
+
+ systemd.services.mailman-daily = {
+ description = "Trigger daily Mailman events";
+ startAt = "daily";
+ serviceConfig = {
+ ExecStart = "${mailmanExe}/bin/mailman digests --send";
+ User = "mailman";
+ };
+ };
+
+ systemd.services.hyperkitty = {
+ enable = cfg.hyperkittyApiKey != null;
+ description = "GNU Hyperkitty QCluster Process";
+ after = [ "network.target" ];
+ wantedBy = [ "mailman.service" "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${mailmanWebExe}/bin/mailman-web qcluster";
+ User = config.services.httpd.user;
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
+ };
+
+ systemd.services.hyperkitty-minutely = {
+ enable = cfg.hyperkittyApiKey != null;
+ description = "Trigger minutely Hyperkitty events";
+ startAt = "minutely";
+ serviceConfig = {
+ ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs minutely";
+ User = config.services.httpd.user;
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
+ };
+
+ systemd.services.hyperkitty-quarter-hourly = {
+ enable = cfg.hyperkittyApiKey != null;
+ description = "Trigger quarter-hourly Hyperkitty events";
+ startAt = "*:00/15";
+ serviceConfig = {
+ ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs quarter_hourly";
+ User = config.services.httpd.user;
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
+ };
+
+ systemd.services.hyperkitty-hourly = {
+ enable = cfg.hyperkittyApiKey != null;
+ description = "Trigger hourly Hyperkitty events";
+ startAt = "hourly";
+ serviceConfig = {
+ ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs hourly";
+ User = config.services.httpd.user;
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
+ };
+
+ systemd.services.hyperkitty-daily = {
+ enable = cfg.hyperkittyApiKey != null;
+ description = "Trigger daily Hyperkitty events";
+ startAt = "daily";
+ serviceConfig = {
+ ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs daily";
+ User = config.services.httpd.user;
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
+ };
+
+ systemd.services.hyperkitty-weekly = {
+ enable = cfg.hyperkittyApiKey != null;
+ description = "Trigger weekly Hyperkitty events";
+ startAt = "weekly";
+ serviceConfig = {
+ ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs weekly";
+ User = config.services.httpd.user;
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
+ };
+
+ systemd.services.hyperkitty-yearly = {
+ enable = cfg.hyperkittyApiKey != null;
+ description = "Trigger yearly Hyperkitty events";
+ startAt = "yearly";
+ serviceConfig = {
+ ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs yearly";
+ User = config.services.httpd.user;
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/mlmmj.nix b/nixpkgs/nixos/modules/services/mail/mlmmj.nix
new file mode 100644
index 00000000000..7ae00f3e501
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mlmmj.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ concatMapLines = f: l: lib.concatStringsSep "\n" (map f l);
+
+ cfg = config.services.mlmmj;
+ stateDir = "/var/lib/mlmmj";
+ spoolDir = "/var/spool/mlmmj";
+ listDir = domain: list: "${spoolDir}/${domain}/${list}";
+ listCtl = domain: list: "${listDir domain list}/control";
+ transport = domain: list: "${domain}--${list}@local.list.mlmmj mlmmj:${domain}/${list}";
+ virtual = domain: list: "${list}@${domain} ${domain}--${list}@local.list.mlmmj";
+ alias = domain: list: "${list}: \"|${pkgs.mlmmj}/bin/mlmmj-receive -L ${listDir domain list}/\"";
+ subjectPrefix = list: "[${list}]";
+ listAddress = domain: list: "${list}@${domain}";
+ customHeaders = domain: list: [ "List-Id: ${list}" "Reply-To: ${list}@${domain}" ];
+ footer = domain: list: "To unsubscribe send a mail to ${list}+unsubscribe@${domain}";
+ createList = d: l:
+ let ctlDir = listCtl d l; in
+ ''
+ for DIR in incoming queue queue/discarded archive text subconf unsubconf \
+ bounce control moderation subscribers.d digesters.d requeue \
+ nomailsubs.d
+ do
+ mkdir -p '${listDir d l}'/"$DIR"
+ done
+ ${pkgs.coreutils}/bin/mkdir -p ${ctlDir}
+ echo ${listAddress d l} > '${ctlDir}/listaddress'
+ [ ! -e ${ctlDir}/customheaders ] && \
+ echo "${lib.concatStringsSep "\n" (customHeaders d l)}" > '${ctlDir}/customheaders'
+ [ ! -e ${ctlDir}/footer ] && \
+ echo ${footer d l} > '${ctlDir}/footer'
+ [ ! -e ${ctlDir}/prefix ] && \
+ echo ${subjectPrefix l} > '${ctlDir}/prefix'
+ '';
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.mlmmj = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable mlmmj";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "mlmmj";
+ description = "mailinglist local user";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "mlmmj";
+ description = "mailinglist local group";
+ };
+
+ listDomain = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Set the mailing list domain";
+ };
+
+ mailLists = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "The collection of hosted maillists";
+ };
+
+ maintInterval = mkOption {
+ type = types.str;
+ default = "20min";
+ description = ''
+ Time interval between mlmmj-maintd runs, see
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry> for format information.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton {
+ name = cfg.user;
+ description = "mlmmj user";
+ home = stateDir;
+ createHome = true;
+ uid = config.ids.uids.mlmmj;
+ group = cfg.group;
+ useDefaultShell = true;
+ };
+
+ users.groups = singleton {
+ name = cfg.group;
+ gid = config.ids.gids.mlmmj;
+ };
+
+ services.postfix = {
+ enable = true;
+ recipientDelimiter= "+";
+ extraMasterConf = ''
+ mlmmj unix - n n - - pipe flags=ORhu user=mlmmj argv=${pkgs.mlmmj}/bin/mlmmj-receive -F -L ${spoolDir}/$nexthop
+ '';
+
+ extraAliases = concatMapLines (alias cfg.listDomain) cfg.mailLists;
+
+ extraConfig = ''
+ transport_maps = hash:${stateDir}/transports
+ virtual_alias_maps = hash:${stateDir}/virtuals
+ propagate_unmatched_extensions = virtual
+ '';
+ };
+
+ environment.systemPackages = [ pkgs.mlmmj ];
+
+ system.activationScripts.mlmmj = ''
+ ${pkgs.coreutils}/bin/mkdir -p ${stateDir} ${spoolDir}/${cfg.listDomain}
+ ${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} ${spoolDir}
+ ${concatMapLines (createList cfg.listDomain) cfg.mailLists}
+ echo "${concatMapLines (virtual cfg.listDomain) cfg.mailLists}" > ${stateDir}/virtuals
+ echo "${concatMapLines (transport cfg.listDomain) cfg.mailLists}" > ${stateDir}/transports
+ ${pkgs.postfix}/bin/postmap ${stateDir}/virtuals
+ ${pkgs.postfix}/bin/postmap ${stateDir}/transports
+ '';
+
+ systemd.services.mlmmj-maintd = {
+ description = "mlmmj maintenance daemon";
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${pkgs.mlmmj}/bin/mlmmj-maintd -F -d ${spoolDir}/${cfg.listDomain}";
+ };
+ };
+
+ systemd.timers.mlmmj-maintd = {
+ description = "mlmmj maintenance timer";
+ timerConfig.OnUnitActiveSec = cfg.maintInterval;
+ wantedBy = [ "timers.target" ];
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/nullmailer.nix b/nixpkgs/nixos/modules/services/mail/nullmailer.nix
new file mode 100644
index 00000000000..2c2910e0aa9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/nullmailer.nix
@@ -0,0 +1,246 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ options = {
+
+ services.nullmailer = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable nullmailer daemon.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "nullmailer";
+ description = ''
+ User to use to run nullmailer-send.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "nullmailer";
+ description = ''
+ Group to use to run nullmailer-send.
+ '';
+ };
+
+ setSendmail = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to set the system sendmail to nullmailer's.";
+ };
+
+ remotesFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Path to the <code>remotes</code> control file. This file contains a
+ list of remote servers to which to send each message.
+
+ See <code>man 8 nullmailer-send</code> for syntax and available
+ options.
+ '';
+ };
+
+ config = {
+ adminaddr = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ If set, all recipients to users at either "localhost" (the literal string)
+ or the canonical host name (from the me control attribute) are remapped to this address.
+ This is provided to allow local daemons to be able to send email to
+ "somebody@localhost" and have it go somewhere sensible instead of being bounced
+ by your relay host. To send to multiple addresses,
+ put them all on one line separated by a comma.
+ '';
+ };
+
+ allmailfrom = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ If set, content will override the envelope sender on all messages.
+ '';
+ };
+
+ defaultdomain = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The content of this attribute is appended to any host name that
+ does not contain a period (except localhost), including defaulthost
+ and idhost. Defaults to the value of the me attribute, if it exists,
+ otherwise the literal name defauldomain.
+ '';
+ };
+
+ defaulthost = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The content of this attribute is appended to any address that
+ is missing a host name. Defaults to the value of the me control
+ attribute, if it exists, otherwise the literal name defaulthost.
+ '';
+ };
+
+ doublebounceto = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ If the original sender was empty (the original message was a
+ delivery status or disposition notification), the double bounce
+ is sent to the address in this attribute.
+ '';
+ };
+
+ helohost = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Sets the environment variable $HELOHOST which is used by the
+ SMTP protocol module to set the parameter given to the HELO command.
+ Defaults to the value of the me configuration attribute.
+ '';
+ };
+
+ idhost = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The content of this attribute is used when building the message-id
+ string for the message. Defaults to the canonicalized value of defaulthost.
+ '';
+ };
+
+ maxpause = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The maximum time to pause between successive queue runs, in seconds.
+ Defaults to 24 hours (86400).
+ '';
+ };
+
+ me = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The fully-qualifiled host name of the computer running nullmailer.
+ Defaults to the literal name me.
+ '';
+ };
+
+ pausetime = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The minimum time to pause between successive queue runs when there
+ are messages in the queue, in seconds. Defaults to 1 minute (60).
+ Each time this timeout is reached, the timeout is doubled to a
+ maximum of maxpause. After new messages are injected, the timeout
+ is reset. If this is set to 0, nullmailer-send will exit
+ immediately after going through the queue once (one-shot mode).
+ '';
+ };
+
+ remotes = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ A list of remote servers to which to send each message. Each line
+ contains a remote host name or address followed by an optional
+ protocol string, separated by white space.
+
+ See <code>man 8 nullmailer-send</code> for syntax and available
+ options.
+
+ WARNING: This is stored world-readable in the nix store. If you need
+ to specify any secret credentials here, consider using the
+ <code>remotesFile</code> option instead.
+ '';
+ };
+
+ sendtimeout = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The time to wait for a remote module listed above to complete sending
+ a message before killing it and trying again, in seconds.
+ Defaults to 1 hour (3600). If this is set to 0, nullmailer-send
+ will wait forever for messages to complete sending.
+ '';
+ };
+ };
+ };
+ };
+
+ config = let
+ cfg = config.services.nullmailer;
+ in mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.config.remotes == null || cfg.remotesFile == null;
+ message = "Only one of `remotesFile` or `config.remotes` may be used at a time.";
+ }
+ ];
+
+ environment = {
+ systemPackages = [ pkgs.nullmailer ];
+ etc = let
+ validAttrs = filterAttrs (name: value: value != null) cfg.config;
+ in
+ (foldl' (as: name: as // { "nullmailer/${name}".text = validAttrs.${name}; }) {} (attrNames validAttrs))
+ // optionalAttrs (cfg.remotesFile != null) { "nullmailer/remotes".source = cfg.remotesFile; };
+ };
+
+ users = {
+ users = singleton {
+ name = cfg.user;
+ description = "Nullmailer relay-only mta user";
+ group = cfg.group;
+ };
+
+ groups = singleton {
+ name = cfg.group;
+ };
+ };
+
+ systemd.tmpfiles.rules = [
+ "d /var/spool/nullmailer - ${cfg.user} - - -"
+ ];
+
+ systemd.services.nullmailer = {
+ description = "nullmailer";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ preStart = ''
+ mkdir -p /var/spool/nullmailer/{queue,tmp}
+ rm -f /var/spool/nullmailer/trigger && mkfifo -m 660 /var/spool/nullmailer/trigger
+ '';
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${pkgs.nullmailer}/bin/nullmailer-send";
+ Restart = "always";
+ };
+ };
+
+ services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail {
+ program = "sendmail";
+ source = "${pkgs.nullmailer}/bin/sendmail";
+ owner = cfg.user;
+ group = cfg.group;
+ setuid = true;
+ setgid = true;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/offlineimap.nix b/nixpkgs/nixos/modules/services/mail/offlineimap.nix
new file mode 100644
index 00000000000..294e3806f94
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/offlineimap.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.offlineimap;
+in {
+
+ options.services.offlineimap = {
+ enable = mkEnableOption "OfflineIMAP, a software to dispose your mailbox(es) as a local Maildir(s)";
+
+ install = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to install a user service for Offlineimap. Once
+ the service is started, emails will be fetched automatically.
+
+ The service must be manually started for each user with
+ "systemctl --user start offlineimap" or globally through
+ <varname>services.offlineimap.enable</varname>.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.offlineimap;
+ defaultText = "pkgs.offlineimap";
+ description = "Offlineimap derivation to use.";
+ };
+
+ path = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ example = literalExample "[ pkgs.pass pkgs.bash pkgs.notmuch ]";
+ description = "List of derivations to put in Offlineimap's path.";
+ };
+
+ onCalendar = mkOption {
+ type = types.str;
+ default = "*:0/3"; # every 3 minutes
+ description = "How often is offlineimap started. Default is '*:0/3' meaning every 3 minutes. See systemd.time(7) for more information about the format.";
+ };
+
+ timeoutStartSec = mkOption {
+ type = types.str;
+ default = "120sec"; # Kill if still alive after 2 minutes
+ description = "How long waiting for offlineimap before killing it. Default is '120sec' meaning every 2 minutes. See systemd.time(7) for more information about the format.";
+ };
+ };
+ config = mkIf (cfg.enable || cfg.install) {
+ systemd.user.services.offlineimap = {
+ description = "Offlineimap: a software to dispose your mailbox(es) as a local Maildir(s)";
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${cfg.package}/bin/offlineimap -u syslog -o -1";
+ TimeoutStartSec = cfg.timeoutStartSec;
+ };
+ path = cfg.path;
+ };
+ environment.systemPackages = [ cfg.package ];
+ systemd.user.timers.offlineimap = {
+ description = "offlineimap timer";
+ timerConfig = {
+ Unit = "offlineimap.service";
+ OnCalendar = cfg.onCalendar;
+ # start immediately after computer is started:
+ Persistent = "true";
+ };
+ } // optionalAttrs cfg.enable { wantedBy = [ "default.target" ]; };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/opendkim.nix b/nixpkgs/nixos/modules/services/mail/opendkim.nix
new file mode 100644
index 00000000000..253823cbaf9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/opendkim.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.opendkim;
+
+ defaultSock = "local:/run/opendkim/opendkim.sock";
+
+ keyFile = "${cfg.keyPath}/${cfg.selector}.private";
+
+ args = [ "-f" "-l"
+ "-p" cfg.socket
+ "-d" cfg.domains
+ "-k" keyFile
+ "-s" cfg.selector
+ ] ++ optionals (cfg.configFile != null) [ "-x" cfg.configFile ];
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.opendkim = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the OpenDKIM sender authentication system.";
+ };
+
+ socket = mkOption {
+ type = types.str;
+ default = defaultSock;
+ description = "Socket which is used for communication with OpenDKIM.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "opendkim";
+ description = "User for the daemon.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "opendkim";
+ description = "Group for the daemon.";
+ };
+
+ domains = mkOption {
+ type = types.str;
+ default = "csl:${config.networking.hostName}";
+ example = "csl:example.com,mydomain.net";
+ description = ''
+ Local domains set (see <literal>opendkim(8)</literal> for more information on datasets).
+ Messages from them are signed, not verified.
+ '';
+ };
+
+ keyPath = mkOption {
+ type = types.path;
+ description = ''
+ The path that opendkim should put its generated private keys into.
+ The DNS settings will be found in this directory with the name selector.txt.
+ '';
+ default = "/var/lib/opendkim/keys";
+ };
+
+ selector = mkOption {
+ type = types.str;
+ description = "Selector to use when signing.";
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "Additional opendkim configuration.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = optionalAttrs (cfg.user == "opendkim") (singleton
+ { name = "opendkim";
+ group = cfg.group;
+ uid = config.ids.uids.opendkim;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "opendkim") (singleton
+ { name = "opendkim";
+ gid = config.ids.gids.opendkim;
+ });
+
+ environment.systemPackages = [ pkgs.opendkim ];
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.keyPath}' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.opendkim = {
+ description = "OpenDKIM signing and verification daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ cd "${cfg.keyPath}"
+ if ! test -f ${cfg.selector}.private; then
+ ${pkgs.opendkim}/bin/opendkim-genkey -s ${cfg.selector} -d all-domains-generic-key
+ echo "Generated OpenDKIM key! Please update your DNS settings:\n"
+ echo "-------------------------------------------------------------"
+ cat ${cfg.selector}.txt
+ echo "-------------------------------------------------------------"
+ fi
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkgs.opendkim}/bin/opendkim ${escapeShellArgs args}";
+ User = cfg.user;
+ Group = cfg.group;
+ RuntimeDirectory = optional (cfg.socket == defaultSock) "opendkim";
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/opensmtpd.nix b/nixpkgs/nixos/modules/services/mail/opensmtpd.nix
new file mode 100644
index 00000000000..a870550ba50
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/opensmtpd.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.opensmtpd;
+ conf = pkgs.writeText "smtpd.conf" cfg.serverConfiguration;
+ args = concatStringsSep " " cfg.extraServerArgs;
+
+ sendmail = pkgs.runCommand "opensmtpd-sendmail" { preferLocalBuild = true; } ''
+ mkdir -p $out/bin
+ ln -s ${cfg.package}/sbin/smtpctl $out/bin/sendmail
+ '';
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.opensmtpd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the OpenSMTPD server.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.opensmtpd;
+ defaultText = "pkgs.opensmtpd";
+ description = "The OpenSMTPD package to use.";
+ };
+
+ addSendmailToSystemPath = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to add OpenSMTPD's sendmail binary to the
+ system path or not.
+ '';
+ };
+
+ extraServerArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "-v" "-P mta" ];
+ description = ''
+ Extra command line arguments provided when the smtpd process
+ is started.
+ '';
+ };
+
+ serverConfiguration = mkOption {
+ type = types.lines;
+ example = ''
+ listen on lo
+ accept for any deliver to lmtp localhost:24
+ '';
+ description = ''
+ The contents of the smtpd.conf configuration file. See the
+ OpenSMTPD documentation for syntax information.
+ '';
+ };
+
+ procPackages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = ''
+ Packages to search for filters, tables, queues, and schedulers.
+
+ Add OpenSMTPD-extras here if you want to use the filters, etc. from
+ that package.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ users.groups = {
+ smtpd.gid = config.ids.gids.smtpd;
+ smtpq.gid = config.ids.gids.smtpq;
+ };
+
+ users.users = {
+ smtpd = {
+ description = "OpenSMTPD process user";
+ uid = config.ids.uids.smtpd;
+ group = "smtpd";
+ };
+ smtpq = {
+ description = "OpenSMTPD queue user";
+ uid = config.ids.uids.smtpq;
+ group = "smtpq";
+ };
+ };
+
+ systemd.services.opensmtpd = let
+ procEnv = pkgs.buildEnv {
+ name = "opensmtpd-procs";
+ paths = [ cfg.package ] ++ cfg.procPackages;
+ pathsToLink = [ "/libexec/opensmtpd" ];
+ };
+ in {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ preStart = ''
+ mkdir -p /var/spool/smtpd
+ chmod 711 /var/spool/smtpd
+
+ mkdir -p /var/spool/smtpd/offline
+ chown root.smtpq /var/spool/smtpd/offline
+ chmod 770 /var/spool/smtpd/offline
+
+ mkdir -p /var/spool/smtpd/purge
+ chown smtpq.root /var/spool/smtpd/purge
+ chmod 700 /var/spool/smtpd/purge
+ '';
+ serviceConfig.ExecStart = "${cfg.package}/sbin/smtpd -d -f ${conf} ${args}";
+ environment.OPENSMTPD_PROC_PATH = "${procEnv}/libexec/opensmtpd";
+ };
+
+ environment.systemPackages = mkIf cfg.addSendmailToSystemPath [ sendmail ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix b/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix
new file mode 100644
index 00000000000..38984f896d6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.pfix-srsd = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to run the postfix sender rewriting scheme daemon.";
+ };
+
+ domain = mkOption {
+ description = "The domain for which to enable srs";
+ type = types.str;
+ example = "example.com";
+ };
+
+ secretsFile = mkOption {
+ description = ''
+ The secret data used to encode the SRS address.
+ to generate, use a command like:
+ <literal>for n in $(seq 5); do dd if=/dev/urandom count=1 bs=1024 status=none | sha256sum | sed 's/ -$//' | sed 's/^/ /'; done</literal>
+ '';
+ type = types.path;
+ default = "/var/lib/pfix-srsd/secrets";
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.pfix-srsd.enable {
+ environment = {
+ systemPackages = [ pkgs.pfixtools ];
+ };
+
+ systemd.services.pfix-srsd = {
+ description = "Postfix sender rewriting scheme daemon";
+ before = [ "postfix.service" ];
+ #note that we use requires rather than wants because postfix
+ #is unable to process (almost) all mail without srsd
+ requiredBy = [ "postfix.service" ];
+ serviceConfig = {
+ Type = "forking";
+ PIDFile = "/run/pfix-srsd.pid";
+ ExecStart = "${pkgs.pfixtools}/bin/pfix-srsd -p /run/pfix-srsd.pid -I ${config.services.pfix-srsd.domain} ${config.services.pfix-srsd.secretsFile}";
+ };
+ };
+ };
+} \ No newline at end of file
diff --git a/nixpkgs/nixos/modules/services/mail/postfix.nix b/nixpkgs/nixos/modules/services/mail/postfix.nix
new file mode 100644
index 00000000000..d5fd76da970
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/postfix.nix
@@ -0,0 +1,898 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.postfix;
+ user = cfg.user;
+ group = cfg.group;
+ setgidGroup = cfg.setgidGroup;
+
+ haveAliases = cfg.postmasterAlias != "" || cfg.rootAlias != ""
+ || cfg.extraAliases != "";
+ haveTransport = cfg.transport != "";
+ haveVirtual = cfg.virtual != "";
+ haveLocalRecipients = cfg.localRecipients != null;
+
+ clientAccess =
+ optional (cfg.dnsBlacklistOverrides != "")
+ "check_client_access hash:/etc/postfix/client_access";
+
+ dnsBl =
+ optionals (cfg.dnsBlacklists != [])
+ (map (s: "reject_rbl_client " + s) cfg.dnsBlacklists);
+
+ clientRestrictions = concatStringsSep ", " (clientAccess ++ dnsBl);
+
+ mainCf = let
+ escape = replaceStrings ["$"] ["$$"];
+ mkList = items: "\n " + concatStringsSep ",\n " items;
+ mkVal = value:
+ if isList value then mkList value
+ else " " + (if value == true then "yes"
+ else if value == false then "no"
+ else toString value);
+ mkEntry = name: value: "${escape name} =${mkVal value}";
+ in
+ concatStringsSep "\n" (mapAttrsToList mkEntry cfg.config)
+ + "\n" + cfg.extraConfig;
+
+ masterCfOptions = { options, config, name, ... }: {
+ options = {
+ name = mkOption {
+ type = types.str;
+ default = name;
+ example = "smtp";
+ description = ''
+ The name of the service to run. Defaults to the attribute set key.
+ '';
+ };
+
+ type = mkOption {
+ type = types.enum [ "inet" "unix" "fifo" "pass" ];
+ default = "unix";
+ example = "inet";
+ description = "The type of the service";
+ };
+
+ private = mkOption {
+ type = types.bool;
+ example = false;
+ description = ''
+ Whether the service's sockets and storage directory is restricted to
+ be only available via the mail system. If <literal>null</literal> is
+ given it uses the postfix default <literal>true</literal>.
+ '';
+ };
+
+ privileged = mkOption {
+ type = types.bool;
+ example = true;
+ description = "";
+ };
+
+ chroot = mkOption {
+ type = types.bool;
+ example = true;
+ description = ''
+ Whether the service is chrooted to have only access to the
+ <option>services.postfix.queueDir</option> and the closure of
+ store paths specified by the <option>program</option> option.
+ '';
+ };
+
+ wakeup = mkOption {
+ type = types.int;
+ example = 60;
+ description = ''
+ Automatically wake up the service after the specified number of
+ seconds. If <literal>0</literal> is given, never wake the service
+ up.
+ '';
+ };
+
+ wakeupUnusedComponent = mkOption {
+ type = types.bool;
+ example = false;
+ description = ''
+ If set to <literal>false</literal> the component will only be woken
+ up if it is used. This is equivalent to postfix' notion of adding a
+ question mark behind the wakeup time in
+ <filename>master.cf</filename>
+ '';
+ };
+
+ maxproc = mkOption {
+ type = types.int;
+ example = 1;
+ description = ''
+ The maximum number of processes to spawn for this service. If the
+ value is <literal>0</literal> it doesn't have any limit. If
+ <literal>null</literal> is given it uses the postfix default of
+ <literal>100</literal>.
+ '';
+ };
+
+ command = mkOption {
+ type = types.str;
+ default = name;
+ example = "smtpd";
+ description = ''
+ A program name specifying a Postfix service/daemon process.
+ By default it's the attribute <option>name</option>.
+ '';
+ };
+
+ args = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "-o" "smtp_helo_timeout=5" ];
+ description = ''
+ Arguments to pass to the <option>command</option>. There is no shell
+ processing involved and shell syntax is passed verbatim to the
+ process.
+ '';
+ };
+
+ rawEntry = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ internal = true;
+ description = ''
+ The raw configuration line for the <filename>master.cf</filename>.
+ '';
+ };
+ };
+
+ config.rawEntry = let
+ mkBool = bool: if bool then "y" else "n";
+ mkArg = arg: "${optionalString (hasPrefix "-" arg) "\n "}${arg}";
+
+ maybeOption = fun: option:
+ if options.${option}.isDefined then fun config.${option} else "-";
+
+ # This is special, because we have two options for this value.
+ wakeup = let
+ wakeupDefined = options.wakeup.isDefined;
+ wakeupUCDefined = options.wakeupUnusedComponent.isDefined;
+ finalValue = toString config.wakeup
+ + optionalString (wakeupUCDefined && !config.wakeupUnusedComponent) "?";
+ in if wakeupDefined then finalValue else "-";
+
+ in [
+ config.name
+ config.type
+ (maybeOption mkBool "private")
+ (maybeOption (b: mkBool (!b)) "privileged")
+ (maybeOption mkBool "chroot")
+ wakeup
+ (maybeOption toString "maxproc")
+ (config.command + " " + concatMapStringsSep " " mkArg config.args)
+ ];
+ };
+
+ masterCfContent = let
+
+ labels = [
+ "# service" "type" "private" "unpriv" "chroot" "wakeup" "maxproc"
+ "command + args"
+ ];
+
+ labelDefaults = [
+ "# " "" "(yes)" "(yes)" "(no)" "(never)" "(100)" "" ""
+ ];
+
+ masterCf = mapAttrsToList (const (getAttr "rawEntry")) cfg.masterConfig;
+
+ # A list of the maximum width of the columns across all lines and labels
+ maxWidths = let
+ foldLine = line: acc: let
+ columnLengths = map stringLength line;
+ in zipListsWith max acc columnLengths;
+ # We need to handle the last column specially here, because it's
+ # open-ended (command + args).
+ lines = [ labels labelDefaults ] ++ (map (l: init l ++ [""]) masterCf);
+ in fold foldLine (genList (const 0) (length labels)) lines;
+
+ # Pad a string with spaces from the right (opposite of fixedWidthString).
+ pad = width: str: let
+ padWidth = width - stringLength str;
+ padding = concatStrings (genList (const " ") padWidth);
+ in str + optionalString (padWidth > 0) padding;
+
+ # It's + 2 here, because that's the amount of spacing between columns.
+ fullWidth = fold (width: acc: acc + width + 2) 0 maxWidths;
+
+ formatLine = line: concatStringsSep " " (zipListsWith pad maxWidths line);
+
+ formattedLabels = let
+ sep = "# " + concatStrings (genList (const "=") (fullWidth + 5));
+ lines = [ sep (formatLine labels) (formatLine labelDefaults) sep ];
+ in concatStringsSep "\n" lines;
+
+ in formattedLabels + "\n" + concatMapStringsSep "\n" formatLine masterCf + "\n" + cfg.extraMasterConf;
+
+ headerCheckOptions = { ... }:
+ {
+ options = {
+ pattern = mkOption {
+ type = types.str;
+ default = "/^.*/";
+ example = "/^X-Mailer:/";
+ description = "A regexp pattern matching the header";
+ };
+ action = mkOption {
+ type = types.str;
+ default = "DUNNO";
+ example = "BCC mail@example.com";
+ description = "The action to be executed when the pattern is matched";
+ };
+ };
+ };
+
+ headerChecks = concatStringsSep "\n" (map (x: "${x.pattern} ${x.action}") cfg.headerChecks) + cfg.extraHeaderChecks;
+
+ aliases = let seperator = if cfg.aliasMapType == "hash" then ":" else ""; in
+ optionalString (cfg.postmasterAlias != "") ''
+ postmaster${seperator} ${cfg.postmasterAlias}
+ ''
+ + optionalString (cfg.rootAlias != "") ''
+ root${seperator} ${cfg.rootAlias}
+ ''
+ + cfg.extraAliases
+ ;
+
+ aliasesFile = pkgs.writeText "postfix-aliases" aliases;
+ virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual;
+ localRecipientMapFile = pkgs.writeText "postfix-local-recipient-map" (concatMapStrings (x: x + " ACCEPT\n") cfg.localRecipients);
+ checkClientAccessFile = pkgs.writeText "postfix-check-client-access" cfg.dnsBlacklistOverrides;
+ mainCfFile = pkgs.writeText "postfix-main.cf" mainCf;
+ masterCfFile = pkgs.writeText "postfix-master.cf" masterCfContent;
+ transportFile = pkgs.writeText "postfix-transport" cfg.transport;
+ headerChecksFile = pkgs.writeText "postfix-header-checks" headerChecks;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.postfix = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to run the Postfix mail server.";
+ };
+
+ enableSmtp = mkOption {
+ default = true;
+ description = "Whether to enable smtp in master.cf.";
+ };
+
+ enableSubmission = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable smtp submission.";
+ };
+
+ submissionOptions = mkOption {
+ type = types.attrs;
+ default = {
+ smtpd_tls_security_level = "encrypt";
+ smtpd_sasl_auth_enable = "yes";
+ smtpd_client_restrictions = "permit_sasl_authenticated,reject";
+ milter_macro_daemon_name = "ORIGINATING";
+ };
+ example = {
+ smtpd_tls_security_level = "encrypt";
+ smtpd_sasl_auth_enable = "yes";
+ smtpd_sasl_type = "dovecot";
+ smtpd_client_restrictions = "permit_sasl_authenticated,reject";
+ milter_macro_daemon_name = "ORIGINATING";
+ };
+ description = "Options for the submission config in master.cf";
+ };
+
+ setSendmail = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to set the system sendmail to postfix's.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "postfix";
+ description = "What to call the Postfix user (must be used only for postfix).";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "postfix";
+ description = "What to call the Postfix group (must be used only for postfix).";
+ };
+
+ setgidGroup = mkOption {
+ type = types.str;
+ default = "postdrop";
+ description = "
+ How to call postfix setgid group (for postdrop). Should
+ be uniquely used group.
+ ";
+ };
+
+ networks = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ example = ["192.168.0.1/24"];
+ description = "
+ Net masks for trusted - allowed to relay mail to third parties -
+ hosts. Leave empty to use mynetworks_style configuration or use
+ default (localhost-only).
+ ";
+ };
+
+ networksStyle = mkOption {
+ type = types.str;
+ default = "";
+ description = "
+ Name of standard way of trusted network specification to use,
+ leave blank if you specify it explicitly or if you want to use
+ default (localhost-only).
+ ";
+ };
+
+ hostname = mkOption {
+ type = types.str;
+ default = "";
+ description ="
+ Hostname to use. Leave blank to use just the hostname of machine.
+ It should be FQDN.
+ ";
+ };
+
+ domain = mkOption {
+ type = types.str;
+ default = "";
+ description ="
+ Domain to use. Leave blank to use hostname minus first component.
+ ";
+ };
+
+ origin = mkOption {
+ type = types.str;
+ default = "";
+ description ="
+ Origin to use in outgoing e-mail. Leave blank to use hostname.
+ ";
+ };
+
+ destination = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ example = ["localhost"];
+ description = "
+ Full (!) list of domains we deliver locally. Leave blank for
+ acceptable Postfix default.
+ ";
+ };
+
+ relayDomains = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ example = ["localdomain"];
+ description = "
+ List of domains we agree to relay to. Default is empty.
+ ";
+ };
+
+ relayHost = mkOption {
+ type = types.str;
+ default = "";
+ description = "
+ Mail relay for outbound mail.
+ ";
+ };
+
+ relayPort = mkOption {
+ type = types.int;
+ default = 25;
+ description = "
+ SMTP port for relay mail relay.
+ ";
+ };
+
+ lookupMX = mkOption {
+ type = types.bool;
+ default = false;
+ description = "
+ Whether relay specified is just domain whose MX must be used.
+ ";
+ };
+
+ postmasterAlias = mkOption {
+ type = types.str;
+ default = "root";
+ description = "
+ Who should receive postmaster e-mail. Multiple values can be added by
+ separating values with comma.
+ ";
+ };
+
+ rootAlias = mkOption {
+ type = types.str;
+ default = "";
+ description = "
+ Who should receive root e-mail. Blank for no redirection.
+ Multiple values can be added by separating values with comma.
+ ";
+ };
+
+ extraAliases = mkOption {
+ type = types.lines;
+ default = "";
+ description = "
+ Additional entries to put verbatim into aliases file, cf. man-page aliases(8).
+ ";
+ };
+
+ aliasMapType = mkOption {
+ type = with types; enum [ "hash" "regexp" "pcre" ];
+ default = "hash";
+ example = "regexp";
+ description = "The format the alias map should have. Use regexp if you want to use regular expressions.";
+ };
+
+ config = mkOption {
+ type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
+ description = ''
+ The main.cf configuration file as key value set.
+ '';
+ example = {
+ mail_owner = "postfix";
+ smtp_use_tls = true;
+ };
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "
+ Extra lines to be added verbatim to the main.cf configuration file.
+ ";
+ };
+
+ sslCert = mkOption {
+ type = types.str;
+ default = "";
+ description = "SSL certificate to use.";
+ };
+
+ sslCACert = mkOption {
+ type = types.str;
+ default = "";
+ description = "SSL certificate of CA.";
+ };
+
+ sslKey = mkOption {
+ type = types.str;
+ default = "";
+ description = "SSL key to use.";
+ };
+
+ recipientDelimiter = mkOption {
+ type = types.str;
+ default = "";
+ example = "+";
+ description = "
+ Delimiter for address extension: so mail to user+test can be handled by ~user/.forward+test
+ ";
+ };
+
+ virtual = mkOption {
+ type = types.lines;
+ default = "";
+ description = "
+ Entries for the virtual alias map, cf. man-page virtual(8).
+ ";
+ };
+
+ virtualMapType = mkOption {
+ type = types.enum ["hash" "regexp" "pcre"];
+ default = "hash";
+ description = ''
+ What type of virtual alias map file to use. Use <literal>"regexp"</literal> for regular expressions.
+ '';
+ };
+
+ localRecipients = mkOption {
+ type = with types; nullOr (listOf str);
+ default = null;
+ description = ''
+ List of accepted local users. Specify a bare username, an
+ <literal>"@domain.tld"</literal> wild-card, or a complete
+ <literal>"user@domain.tld"</literal> address. If set, these names end
+ up in the local recipient map -- see the local(8) man-page -- and
+ effectively replace the system user database lookup that's otherwise
+ used by default.
+ '';
+ };
+
+ transport = mkOption {
+ default = "";
+ description = "
+ Entries for the transport map, cf. man-page transport(8).
+ ";
+ };
+
+ dnsBlacklists = mkOption {
+ default = [];
+ type = with types; listOf str;
+ description = "dns blacklist servers to use with smtpd_client_restrictions";
+ };
+
+ dnsBlacklistOverrides = mkOption {
+ default = "";
+ description = "contents of check_client_access for overriding dnsBlacklists";
+ };
+
+ masterConfig = mkOption {
+ type = types.attrsOf (types.submodule masterCfOptions);
+ default = {};
+ example =
+ { submission = {
+ type = "inet";
+ args = [ "-o" "smtpd_tls_security_level=encrypt" ];
+ };
+ };
+ description = ''
+ An attribute set of service options, which correspond to the service
+ definitions usually done within the Postfix
+ <filename>master.cf</filename> file.
+ '';
+ };
+
+ extraMasterConf = mkOption {
+ type = types.lines;
+ default = "";
+ example = "submission inet n - n - - smtpd";
+ description = "Extra lines to append to the generated master.cf file.";
+ };
+
+ enableHeaderChecks = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = "Whether to enable postfix header checks";
+ };
+
+ headerChecks = mkOption {
+ type = types.listOf (types.submodule headerCheckOptions);
+ default = [];
+ example = [ { pattern = "/^X-Spam-Flag:/"; action = "REDIRECT spam@example.com"; } ];
+ description = "Postfix header checks.";
+ };
+
+ extraHeaderChecks = mkOption {
+ type = types.lines;
+ default = "";
+ example = "/^X-Spam-Flag:/ REDIRECT spam@example.com";
+ description = "Extra lines to /etc/postfix/header_checks file.";
+ };
+
+ aliasFiles = mkOption {
+ type = types.attrsOf types.path;
+ default = {};
+ description = "Aliases' tables to be compiled and placed into /var/lib/postfix/conf.";
+ };
+
+ mapFiles = mkOption {
+ type = types.attrsOf types.path;
+ default = {};
+ description = "Maps to be compiled and placed into /var/lib/postfix/conf.";
+ };
+
+ useSrs = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable sender rewriting scheme";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.postfix.enable (mkMerge [
+ {
+
+ environment = {
+ etc = singleton
+ { source = "/var/lib/postfix/conf";
+ target = "postfix";
+ };
+
+ # This makes it comfortable to run 'postqueue/postdrop' for example.
+ systemPackages = [ pkgs.postfix ];
+ };
+
+ services.pfix-srsd.enable = config.services.postfix.useSrs;
+
+ services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail {
+ program = "sendmail";
+ source = "${pkgs.postfix}/bin/sendmail";
+ group = setgidGroup;
+ setuid = false;
+ setgid = true;
+ };
+
+ security.wrappers.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";
+ uid = config.ids.uids.postfix;
+ group = group;
+ };
+
+ users.groups =
+ optional (group == "postfix")
+ { name = group;
+ gid = config.ids.gids.postfix;
+ }
+ ++ optional (setgidGroup == "postdrop")
+ { name = setgidGroup;
+ gid = config.ids.gids.postdrop;
+ };
+
+ systemd.services.postfix =
+ { description = "Postfix mail server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = [ pkgs.postfix ];
+
+ serviceConfig = {
+ Type = "forking";
+ Restart = "always";
+ PIDFile = "/var/lib/postfix/queue/pid/master.pid";
+ ExecStart = "${pkgs.postfix}/bin/postfix start";
+ ExecStop = "${pkgs.postfix}/bin/postfix stop";
+ ExecReload = "${pkgs.postfix}/bin/postfix reload";
+ };
+
+ preStart = ''
+ # Backwards compatibility
+ if [ ! -d /var/lib/postfix ] && [ -d /var/postfix ]; then
+ mkdir -p /var/lib
+ mv /var/postfix /var/lib/postfix
+ fi
+
+ # All permissions set according ${pkgs.postfix}/etc/postfix/postfix-files script
+ mkdir -p /var/lib/postfix /var/lib/postfix/queue/{pid,public,maildrop}
+ chmod 0755 /var/lib/postfix
+ chown root:root /var/lib/postfix
+
+ rm -rf /var/lib/postfix/conf
+ mkdir -p /var/lib/postfix/conf
+ chmod 0755 /var/lib/postfix/conf
+ ln -sf ${pkgs.postfix}/etc/postfix/postfix-files /var/lib/postfix/conf/postfix-files
+ ln -sf ${mainCfFile} /var/lib/postfix/conf/main.cf
+ ln -sf ${masterCfFile} /var/lib/postfix/conf/master.cf
+
+ ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
+ ln -sf ${from} /var/lib/postfix/conf/${to}
+ ${pkgs.postfix}/bin/postalias /var/lib/postfix/conf/${to}
+ '') cfg.aliasFiles)}
+ ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
+ ln -sf ${from} /var/lib/postfix/conf/${to}
+ ${pkgs.postfix}/bin/postmap /var/lib/postfix/conf/${to}
+ '') cfg.mapFiles)}
+
+ mkdir -p /var/spool/mail
+ chown root:root /var/spool/mail
+ chmod a+rwxt /var/spool/mail
+ ln -sf /var/spool/mail /var/
+
+ #Finally delegate to postfix checking remain directories in /var/lib/postfix and set permissions on them
+ ${pkgs.postfix}/bin/postfix set-permissions config_directory=/var/lib/postfix/conf
+ '';
+ };
+
+ services.postfix.config = (mapAttrs (_: v: mkDefault v) {
+ compatibility_level = "9999";
+ mail_owner = cfg.user;
+ default_privs = "nobody";
+
+ # NixOS specific locations
+ data_directory = "/var/lib/postfix/data";
+ queue_directory = "/var/lib/postfix/queue";
+
+ # Default location of everything in package
+ meta_directory = "${pkgs.postfix}/etc/postfix";
+ command_directory = "${pkgs.postfix}/bin";
+ sample_directory = "/etc/postfix";
+ newaliases_path = "${pkgs.postfix}/bin/newaliases";
+ mailq_path = "${pkgs.postfix}/bin/mailq";
+ readme_directory = false;
+ sendmail_path = "${pkgs.postfix}/bin/sendmail";
+ daemon_directory = "${pkgs.postfix}/libexec/postfix";
+ manpage_directory = "${pkgs.postfix}/share/man";
+ html_directory = "${pkgs.postfix}/share/postfix/doc/html";
+ shlib_directory = false;
+ mail_spool_directory = "/var/spool/mail/";
+ setgid_group = cfg.setgidGroup;
+ })
+ // optionalAttrs (cfg.relayHost != "") { relayhost = if cfg.lookupMX
+ then "${cfg.relayHost}:${toString cfg.relayPort}"
+ else "[${cfg.relayHost}]:${toString cfg.relayPort}"; }
+ // optionalAttrs config.networking.enableIPv6 { inet_protocols = mkDefault "all"; }
+ // optionalAttrs (cfg.networks != null) { mynetworks = cfg.networks; }
+ // optionalAttrs (cfg.networksStyle != "") { mynetworks_style = cfg.networksStyle; }
+ // optionalAttrs (cfg.hostname != "") { myhostname = cfg.hostname; }
+ // optionalAttrs (cfg.domain != "") { mydomain = cfg.domain; }
+ // optionalAttrs (cfg.origin != "") { myorigin = cfg.origin; }
+ // optionalAttrs (cfg.destination != null) { mydestination = cfg.destination; }
+ // optionalAttrs (cfg.relayDomains != null) { relay_domains = cfg.relayDomains; }
+ // optionalAttrs (cfg.recipientDelimiter != "") { recipient_delimiter = cfg.recipientDelimiter; }
+ // optionalAttrs haveAliases { alias_maps = [ "${cfg.aliasMapType}:/etc/postfix/aliases" ]; }
+ // optionalAttrs haveTransport { transport_maps = [ "hash:/etc/postfix/transport" ]; }
+ // optionalAttrs haveVirtual { virtual_alias_maps = [ "${cfg.virtualMapType}:/etc/postfix/virtual" ]; }
+ // optionalAttrs haveLocalRecipients { local_recipient_maps = [ "hash:/etc/postfix/local_recipients" ] ++ optional haveAliases "$alias_maps"; }
+ // optionalAttrs (cfg.dnsBlacklists != []) { smtpd_client_restrictions = clientRestrictions; }
+ // optionalAttrs cfg.useSrs {
+ sender_canonical_maps = [ "tcp:127.0.0.1:10001" ];
+ sender_canonical_classes = [ "envelope_sender" ];
+ recipient_canonical_maps = [ "tcp:127.0.0.1:10002" ];
+ recipient_canonical_classes = [ "envelope_recipient" ];
+ }
+ // optionalAttrs cfg.enableHeaderChecks { header_checks = [ "regexp:/etc/postfix/header_checks" ]; }
+ // optionalAttrs (cfg.sslCert != "") {
+ smtp_tls_CAfile = cfg.sslCACert;
+ smtp_tls_cert_file = cfg.sslCert;
+ smtp_tls_key_file = cfg.sslKey;
+
+ smtp_use_tls = true;
+
+ smtpd_tls_CAfile = cfg.sslCACert;
+ smtpd_tls_cert_file = cfg.sslCert;
+ smtpd_tls_key_file = cfg.sslKey;
+
+ smtpd_use_tls = true;
+ };
+
+ services.postfix.masterConfig = {
+ smtp_inet = {
+ name = "smtp";
+ type = "inet";
+ private = false;
+ command = "smtpd";
+ };
+ pickup = {
+ private = false;
+ wakeup = 60;
+ maxproc = 1;
+ };
+ cleanup = {
+ private = false;
+ maxproc = 0;
+ };
+ qmgr = {
+ private = false;
+ wakeup = 300;
+ maxproc = 1;
+ };
+ tlsmgr = {
+ wakeup = 1000;
+ wakeupUnusedComponent = false;
+ maxproc = 1;
+ };
+ rewrite = {
+ command = "trivial-rewrite";
+ };
+ bounce = {
+ maxproc = 0;
+ };
+ defer = {
+ maxproc = 0;
+ command = "bounce";
+ };
+ trace = {
+ maxproc = 0;
+ command = "bounce";
+ };
+ verify = {
+ maxproc = 1;
+ };
+ flush = {
+ private = false;
+ wakeup = 1000;
+ wakeupUnusedComponent = false;
+ maxproc = 0;
+ };
+ proxymap = {
+ command = "proxymap";
+ };
+ proxywrite = {
+ maxproc = 1;
+ command = "proxymap";
+ };
+ showq = {
+ private = false;
+ };
+ error = {};
+ retry = {
+ command = "error";
+ };
+ discard = {};
+ local = {
+ privileged = true;
+ };
+ virtual = {
+ privileged = true;
+ };
+ lmtp = {
+ };
+ anvil = {
+ maxproc = 1;
+ };
+ scache = {
+ maxproc = 1;
+ };
+ } // optionalAttrs cfg.enableSubmission {
+ submission = {
+ type = "inet";
+ private = false;
+ command = "smtpd";
+ args = let
+ mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
+ in concatLists (mapAttrsToList mkKeyVal cfg.submissionOptions);
+ };
+ } // optionalAttrs cfg.enableSmtp {
+ smtp = {};
+ relay = {
+ command = "smtp";
+ args = [ "-o" "smtp_fallback_relay=" ];
+ };
+ };
+ }
+
+ (mkIf haveAliases {
+ services.postfix.aliasFiles.aliases = aliasesFile;
+ })
+ (mkIf haveTransport {
+ services.postfix.mapFiles.transport = transportFile;
+ })
+ (mkIf haveVirtual {
+ services.postfix.mapFiles.virtual = virtualFile;
+ })
+ (mkIf haveLocalRecipients {
+ services.postfix.mapFiles.local_recipients = localRecipientMapFile;
+ })
+ (mkIf cfg.enableHeaderChecks {
+ services.postfix.mapFiles.header_checks = headerChecksFile;
+ })
+ (mkIf (cfg.dnsBlacklists != []) {
+ services.postfix.mapFiles.client_access = checkClientAccessFile;
+ })
+ ]);
+}
diff --git a/nixpkgs/nixos/modules/services/mail/postgrey.nix b/nixpkgs/nixos/modules/services/mail/postgrey.nix
new file mode 100644
index 00000000000..88fb7f0b4ad
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/postgrey.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib; let
+
+ cfg = config.services.postgrey;
+
+ natural = with types; addCheck int (x: x >= 0);
+ natural' = with types; addCheck int (x: x > 0);
+
+ socket = with types; addCheck (either (submodule unixSocket) (submodule inetSocket)) (x: x ? path || x ? port);
+
+ inetSocket = with types; {
+ options = {
+ addr = mkOption {
+ type = nullOr str;
+ default = null;
+ example = "127.0.0.1";
+ description = "The address to bind to. Localhost if null";
+ };
+ port = mkOption {
+ type = natural';
+ default = 10030;
+ description = "Tcp port to bind to";
+ };
+ };
+ };
+
+ unixSocket = with types; {
+ options = {
+ path = mkOption {
+ type = path;
+ default = "/run/postgrey.sock";
+ description = "Path of the unix socket";
+ };
+
+ mode = mkOption {
+ type = str;
+ default = "0777";
+ description = "Mode of the unix socket";
+ };
+ };
+ };
+
+in {
+
+ options = {
+ services.postgrey = with types; {
+ enable = mkOption {
+ type = bool;
+ default = false;
+ description = "Whether to run the Postgrey daemon";
+ };
+ socket = mkOption {
+ type = socket;
+ default = {
+ path = "/run/postgrey.sock";
+ mode = "0777";
+ };
+ example = {
+ addr = "127.0.0.1";
+ port = 10030;
+ };
+ description = "Socket to bind to";
+ };
+ greylistText = mkOption {
+ type = str;
+ default = "Greylisted for %%s seconds";
+ description = "Response status text for greylisted messages; use %%s for seconds left until greylisting is over and %%r for mail domain of recipient";
+ };
+ greylistAction = mkOption {
+ type = str;
+ default = "DEFER_IF_PERMIT";
+ description = "Response status for greylisted messages (see access(5))";
+ };
+ greylistHeader = mkOption {
+ type = str;
+ default = "X-Greylist: delayed %%t seconds by postgrey-%%v at %%h; %%d";
+ description = "Prepend header to greylisted mails; use %%t for seconds delayed due to greylisting, %%v for the version of postgrey, %%d for the date, and %%h for the host";
+ };
+ delay = mkOption {
+ type = natural;
+ default = 300;
+ description = "Greylist for N seconds";
+ };
+ maxAge = mkOption {
+ type = natural;
+ default = 35;
+ description = "Delete entries from whitelist if they haven't been seen for N days";
+ };
+ retryWindow = mkOption {
+ type = either str natural;
+ default = 2;
+ example = "12h";
+ description = "Allow N days for the first retry. Use string with appended 'h' to specify time in hours";
+ };
+ lookupBySubnet = mkOption {
+ type = bool;
+ default = true;
+ description = "Strip the last N bits from IP addresses, determined by IPv4CIDR and IPv6CIDR";
+ };
+ IPv4CIDR = mkOption {
+ type = natural;
+ default = 24;
+ description = "Strip N bits from IPv4 addresses if lookupBySubnet is true";
+ };
+ IPv6CIDR = mkOption {
+ type = natural;
+ default = 64;
+ description = "Strip N bits from IPv6 addresses if lookupBySubnet is true";
+ };
+ privacy = mkOption {
+ type = bool;
+ default = true;
+ description = "Store data using one-way hash functions (SHA1)";
+ };
+ autoWhitelist = mkOption {
+ type = nullOr natural';
+ default = 5;
+ description = "Whitelist clients after successful delivery of N messages";
+ };
+ whitelistClients = mkOption {
+ type = listOf path;
+ default = [];
+ description = "Client address whitelist files (see postgrey(8))";
+ };
+ whitelistRecipients = mkOption {
+ type = listOf path;
+ default = [];
+ description = "Recipient address whitelist files (see postgrey(8))";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.postgrey ];
+
+ users = {
+ users = {
+ postgrey = {
+ description = "Postgrey Daemon";
+ uid = config.ids.uids.postgrey;
+ group = "postgrey";
+ };
+ };
+ groups = {
+ postgrey = {
+ gid = config.ids.gids.postgrey;
+ };
+ };
+ };
+
+ systemd.services.postgrey = let
+ bind-flag = if cfg.socket ? path then
+ ''--unix=${cfg.socket.path} --socketmode=${cfg.socket.mode}''
+ else
+ ''--inet=${optionalString (cfg.socket.addr != null) (cfg.socket.addr + ":")}${toString cfg.socket.port}'';
+ in {
+ description = "Postfix Greylisting Service";
+ wantedBy = [ "multi-user.target" ];
+ before = [ "postfix.service" ];
+ preStart = ''
+ mkdir -p /var/postgrey
+ chown postgrey:postgrey /var/postgrey
+ chmod 0770 /var/postgrey
+ '';
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = ''${pkgs.postgrey}/bin/postgrey \
+ ${bind-flag} \
+ --group=postgrey --user=postgrey \
+ --dbdir=/var/postgrey \
+ --delay=${toString cfg.delay} \
+ --max-age=${toString cfg.maxAge} \
+ --retry-window=${toString cfg.retryWindow} \
+ ${if cfg.lookupBySubnet then "--lookup-by-subnet" else "--lookup-by-host"} \
+ --ipv4cidr=${toString cfg.IPv4CIDR} --ipv6cidr=${toString cfg.IPv6CIDR} \
+ ${optionalString cfg.privacy "--privacy"} \
+ --auto-whitelist-clients=${toString (if cfg.autoWhitelist == null then 0 else cfg.autoWhitelist)} \
+ --greylist-action=${cfg.greylistAction} \
+ --greylist-text="${cfg.greylistText}" \
+ --x-greylist-header="${cfg.greylistHeader}" \
+ ${concatMapStringsSep " " (x: "--whitelist-clients=" + x) cfg.whitelistClients} \
+ ${concatMapStringsSep " " (x: "--whitelist-recipients=" + x) cfg.whitelistRecipients}
+ '';
+ Restart = "always";
+ RestartSec = 5;
+ TimeoutSec = 10;
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/postsrsd.nix b/nixpkgs/nixos/modules/services/mail/postsrsd.nix
new file mode 100644
index 00000000000..8f12a16906c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/postsrsd.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.postsrsd;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.postsrsd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the postsrsd SRS server for Postfix.";
+ };
+
+ secretsFile = mkOption {
+ type = types.path;
+ default = "/var/lib/postsrsd/postsrsd.secret";
+ description = "Secret keys used for signing and verification";
+ };
+
+ domain = mkOption {
+ type = types.str;
+ description = "Domain name for rewrite";
+ };
+
+ separator = mkOption {
+ type = types.enum ["-" "=" "+"];
+ default = "=";
+ description = "First separator character in generated addresses";
+ };
+
+ # bindAddress = mkOption { # uncomment once 1.5 is released
+ # type = types.str;
+ # default = "127.0.0.1";
+ # description = "Socket listen address";
+ # };
+
+ forwardPort = mkOption {
+ type = types.int;
+ default = 10001;
+ description = "Port for the forward SRS lookup";
+ };
+
+ reversePort = mkOption {
+ type = types.int;
+ default = 10002;
+ description = "Port for the reverse SRS lookup";
+ };
+
+ timeout = mkOption {
+ type = types.int;
+ default = 1800;
+ description = "Timeout for idle client connections in seconds";
+ };
+
+ excludeDomains = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Origin domains to exclude from rewriting in addition to primary domain";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "postsrsd";
+ description = "User for the daemon";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "postsrsd";
+ description = "Group for the daemon";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.postsrsd.domain = mkDefault config.networking.hostName;
+
+ users.users = optionalAttrs (cfg.user == "postsrsd") (singleton
+ { name = "postsrsd";
+ group = cfg.group;
+ uid = config.ids.uids.postsrsd;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "postsrsd") (singleton
+ { name = "postsrsd";
+ gid = config.ids.gids.postsrsd;
+ });
+
+ systemd.services.postsrsd = {
+ description = "PostSRSd SRS rewriting server";
+ after = [ "network.target" ];
+ before = [ "postfix.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ pkgs.coreutils ];
+
+ serviceConfig = {
+ ExecStart = ''${pkgs.postsrsd}/sbin/postsrsd "-s${cfg.secretsFile}" "-d${cfg.domain}" -a${cfg.separator} -f${toString cfg.forwardPort} -r${toString cfg.reversePort} -t${toString cfg.timeout} "-X${concatStringsSep "," cfg.excludeDomains}"'';
+ User = cfg.user;
+ Group = cfg.group;
+ PermissionsStartOnly = true;
+ };
+
+ preStart = ''
+ if [ ! -e "${cfg.secretsFile}" ]; then
+ echo "WARNING: secrets file not found, autogenerating!"
+ DIR="$(dirname "${cfg.secretsFile}")"
+ if [ ! -d "$DIR" ]; then
+ mkdir -p -m750 "$DIR"
+ chown "${cfg.user}:${cfg.group}" "$DIR"
+ fi
+ dd if=/dev/random bs=18 count=1 | base64 > "${cfg.secretsFile}"
+ chmod 600 "${cfg.secretsFile}"
+ fi
+ chown "${cfg.user}:${cfg.group}" "${cfg.secretsFile}"
+ '';
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/roundcube.nix b/nixpkgs/nixos/modules/services/mail/roundcube.nix
new file mode 100644
index 00000000000..bdedfa1bb70
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/roundcube.nix
@@ -0,0 +1,175 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.roundcube;
+ fpm = config.services.phpfpm.pools.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:${fpm.socket};
+ include ${pkgs.nginx}/conf/fastcgi_params;
+ include ${pkgs.nginx}/conf/fastcgi.conf;
+ }
+ '';
+ };
+ };
+ };
+ };
+
+ services.postgresql = mkIf (cfg.database.host == "localhost") {
+ enable = true;
+ };
+
+ services.phpfpm.pools.roundcube = {
+ user = "nginx";
+ phpOptions = ''
+ error_log = 'stderr'
+ log_errors = on
+ post_max_size = 25M
+ upload_max_filesize = 25M
+ '';
+ settings = mapAttrs (name: mkDefault) {
+ "listen.owner" = "nginx";
+ "listen.group" = "nginx";
+ "listen.mode" = "0660";
+ "pm" = "dynamic";
+ "pm.max_children" = 75;
+ "pm.start_servers" = 2;
+ "pm.min_spare_servers" = 1;
+ "pm.max_spare_servers" = 20;
+ "pm.max_requests" = 500;
+ "catch_workers_output" = true;
+ };
+ };
+ systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ];
+
+ systemd.services.roundcube-setup = let
+ pgSuperUser = config.services.postgresql.superUser;
+ in mkMerge [
+ (mkIf (cfg.database.host == "localhost") {
+ requires = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+ path = [ config.services.postgresql.package ];
+ })
+ {
+ wantedBy = [ "multi-user.target" ];
+ script = ''
+ mkdir -p /var/lib/roundcube
+ if [ ! -f /var/lib/roundcube/db-created ]; then
+ if [ "${cfg.database.host}" = "localhost" ]; then
+ ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create role ${cfg.database.username} with login password '${cfg.database.password}'";
+ ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create database ${cfg.database.dbname} with owner ${cfg.database.username}";
+ fi
+ PGPASSWORD=${cfg.database.password} ${pkgs.postgresql}/bin/psql -U ${cfg.database.username} \
+ -f ${cfg.package}/SQL/postgres.initial.sql \
+ -h ${cfg.database.host} ${cfg.database.dbname}
+ touch /var/lib/roundcube/db-created
+ fi
+
+ ${pkgs.php}/bin/php ${cfg.package}/bin/update.sh
+ '';
+ serviceConfig.Type = "oneshot";
+ }
+ ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/rspamd.nix b/nixpkgs/nixos/modules/services/mail/rspamd.nix
new file mode 100644
index 00000000000..4db35d9e89a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/rspamd.nix
@@ -0,0 +1,418 @@
+{ config, options, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.rspamd;
+ postfixCfg = config.services.postfix;
+
+ bindSocketOpts = {options, config, ... }: {
+ options = {
+ socket = mkOption {
+ type = types.str;
+ example = "localhost:11333";
+ description = ''
+ Socket for this worker to listen on in a format acceptable by rspamd.
+ '';
+ };
+ mode = mkOption {
+ type = types.str;
+ default = "0644";
+ description = "Mode to set on unix socket";
+ };
+ owner = mkOption {
+ type = types.str;
+ default = "${cfg.user}";
+ description = "Owner to set on unix socket";
+ };
+ group = mkOption {
+ type = types.str;
+ default = "${cfg.group}";
+ description = "Group to set on unix socket";
+ };
+ rawEntry = mkOption {
+ type = types.str;
+ internal = true;
+ };
+ };
+ config.rawEntry = let
+ maybeOption = option:
+ optionalString options.${option}.isDefined " ${option}=${config.${option}}";
+ in
+ if (!(hasPrefix "/" config.socket)) then "${config.socket}"
+ else "${config.socket}${maybeOption "mode"}${maybeOption "owner"}${maybeOption "group"}";
+ };
+
+ traceWarning = w: x: builtins.trace "warning: ${w}" x;
+
+ workerOpts = { name, options, ... }: {
+ options = {
+ enable = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether to run the rspamd worker.";
+ };
+ name = mkOption {
+ type = types.nullOr types.str;
+ default = name;
+ description = "Name of the worker";
+ };
+ type = mkOption {
+ type = types.nullOr (types.enum [
+ "normal" "controller" "fuzzy_storage" "rspamd_proxy" "lua" "proxy"
+ ]);
+ description = ''
+ The type of this worker. The type <literal>proxy</literal> is
+ deprecated and only kept for backwards compatibility and should be
+ replaced with <literal>rspamd_proxy</literal>.
+ '';
+ apply = let
+ from = "services.rspamd.workers.\"${name}\".type";
+ files = options.type.files;
+ warning = "The option `${from}` defined in ${showFiles files} has enum value `proxy` which has been renamed to `rspamd_proxy`";
+ in x: if x == "proxy" then traceWarning warning "rspamd_proxy" else x;
+ };
+ bindSockets = mkOption {
+ type = types.listOf (types.either types.str (types.submodule bindSocketOpts));
+ default = [];
+ description = ''
+ List of sockets to listen, in format acceptable by rspamd
+ '';
+ example = [{
+ socket = "/run/rspamd.sock";
+ mode = "0666";
+ owner = "rspamd";
+ } "*:11333"];
+ apply = value: map (each: if (isString each)
+ then if (isUnixSocket each)
+ then {socket = each; owner = cfg.user; group = cfg.group; mode = "0644"; rawEntry = "${each}";}
+ else {socket = each; rawEntry = "${each}";}
+ else each) value;
+ };
+ count = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Number of worker instances to run
+ '';
+ };
+ includes = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ List of files to include in configuration
+ '';
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional entries to put verbatim into worker section of rspamd config file.";
+ };
+ };
+ config = mkIf (name == "normal" || name == "controller" || name == "fuzzy" || name == "rspamd_proxy") {
+ type = mkDefault name;
+ includes = mkDefault [ "$CONFDIR/worker-${if name == "rspamd_proxy" then "proxy" else name}.inc" ];
+ bindSockets =
+ let
+ unixSocket = name: {
+ mode = "0660";
+ socket = "/run/rspamd/${name}.sock";
+ owner = cfg.user;
+ group = cfg.group;
+ };
+ in mkDefault (if name == "normal" then [(unixSocket "rspamd")]
+ else if name == "controller" then [ "localhost:11334" ]
+ else if name == "rspamd_proxy" then [ (unixSocket "proxy") ]
+ else [] );
+ };
+ };
+
+ isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket);
+
+ mkBindSockets = enabled: socks: concatStringsSep "\n "
+ (flatten (map (each: "bind_socket = \"${each.rawEntry}\";") socks));
+
+ rspamdConfFile = pkgs.writeText "rspamd.conf"
+ ''
+ .include "$CONFDIR/common.conf"
+
+ options {
+ pidfile = "$RUNDIR/rspamd.pid";
+ .include "$CONFDIR/options.inc"
+ .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/options.inc"
+ .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/options.inc"
+ }
+
+ logging {
+ type = "syslog";
+ .include "$CONFDIR/logging.inc"
+ .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/logging.inc"
+ .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/logging.inc"
+ }
+
+ ${concatStringsSep "\n" (mapAttrsToList (name: value: let
+ includeName = if name == "rspamd_proxy" then "proxy" else name;
+ tryOverride = if value.extraConfig == "" then "true" else "false";
+ in ''
+ worker "${value.type}" {
+ type = "${value.type}";
+ ${optionalString (value.enable != null)
+ "enabled = ${if value.enable != false then "yes" else "no"};"}
+ ${mkBindSockets value.enable value.bindSockets}
+ ${optionalString (value.count != null) "count = ${toString value.count};"}
+ ${concatStringsSep "\n " (map (each: ".include \"${each}\"") value.includes)}
+ .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/worker-${includeName}.inc"
+ .include(try=${tryOverride}; priority=10) "$LOCAL_CONFDIR/override.d/worker-${includeName}.inc"
+ }
+ '') cfg.workers)}
+
+ ${optionalString (cfg.extraConfig != "") ''
+ .include(priority=10) "$LOCAL_CONFDIR/override.d/extra-config.inc"
+ ''}
+ '';
+
+ filterFiles = files: filterAttrs (n: v: v.enable) files;
+ rspamdDir = pkgs.linkFarm "etc-rspamd-dir" (
+ (mapAttrsToList (name: file: { name = "local.d/${name}"; path = file.source; }) (filterFiles cfg.locals)) ++
+ (mapAttrsToList (name: file: { name = "override.d/${name}"; path = file.source; }) (filterFiles cfg.overrides)) ++
+ (optional (cfg.localLuaRules != null) { name = "rspamd.local.lua"; path = cfg.localLuaRules; }) ++
+ [ { name = "rspamd.conf"; path = rspamdConfFile; } ]
+ );
+
+ configFileModule = prefix: { name, config, ... }: {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether this file ${prefix} should be generated. This
+ option allows specific ${prefix} files to be disabled.
+ '';
+ };
+
+ text = mkOption {
+ default = null;
+ type = types.nullOr types.lines;
+ description = "Text of the file.";
+ };
+
+ source = mkOption {
+ type = types.path;
+ description = "Path of the source file.";
+ };
+ };
+ config = {
+ source = mkIf (config.text != null) (
+ let name' = "rspamd-${prefix}-" + baseNameOf name;
+ in mkDefault (pkgs.writeText name' config.text));
+ };
+ };
+
+ configOverrides =
+ (mapAttrs' (n: v: nameValuePair "worker-${if n == "rspamd_proxy" then "proxy" else n}.inc" {
+ text = v.extraConfig;
+ })
+ (filterAttrs (n: v: v.extraConfig != "") cfg.workers))
+ // (if cfg.extraConfig == "" then {} else {
+ "extra-config.inc".text = cfg.extraConfig;
+ });
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.rspamd = {
+
+ enable = mkEnableOption "rspamd, the Rapid spam filtering system";
+
+ debug = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to run the rspamd daemon in debug mode.";
+ };
+
+ locals = mkOption {
+ type = with types; attrsOf (submodule (configFileModule "locals"));
+ default = {};
+ description = ''
+ Local configuration files, written into <filename>/etc/rspamd/local.d/{name}</filename>.
+ '';
+ example = literalExample ''
+ { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
+ "arc.conf".text = "allow_envfrom_empty = true;";
+ }
+ '';
+ };
+
+ overrides = mkOption {
+ type = with types; attrsOf (submodule (configFileModule "overrides"));
+ default = {};
+ description = ''
+ Overridden configuration files, written into <filename>/etc/rspamd/override.d/{name}</filename>.
+ '';
+ example = literalExample ''
+ { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
+ "arc.conf".text = "allow_envfrom_empty = true;";
+ }
+ '';
+ };
+
+ localLuaRules = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = ''
+ Path of file to link to <filename>/etc/rspamd/rspamd.local.lua</filename> for local
+ rules written in Lua
+ '';
+ };
+
+ workers = mkOption {
+ type = with types; attrsOf (submodule workerOpts);
+ description = ''
+ Attribute set of workers to start.
+ '';
+ default = {
+ normal = {};
+ controller = {};
+ };
+ example = literalExample ''
+ {
+ normal = {
+ includes = [ "$CONFDIR/worker-normal.inc" ];
+ bindSockets = [{
+ socket = "/run/rspamd/rspamd.sock";
+ mode = "0660";
+ owner = "${cfg.user}";
+ group = "${cfg.group}";
+ }];
+ };
+ controller = {
+ includes = [ "$CONFDIR/worker-controller.inc" ];
+ bindSockets = [ "[::1]:11334" ];
+ };
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration to add at the end of the rspamd configuration
+ file.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "rspamd";
+ description = ''
+ User to use when no root privileges are required.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "rspamd";
+ description = ''
+ Group to use when no root privileges are required.
+ '';
+ };
+
+ postfix = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Add rspamd milter to postfix main.conf";
+ };
+
+ config = mkOption {
+ type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
+ description = ''
+ Addon to postfix configuration
+ '';
+ default = {
+ smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+ non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+ };
+ example = {
+ smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+ non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+ };
+ };
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ services.rspamd.overrides = configOverrides;
+ services.rspamd.workers = mkIf cfg.postfix.enable {
+ controller = {};
+ rspamd_proxy = {
+ bindSockets = [ {
+ mode = "0660";
+ socket = "/run/rspamd/rspamd-milter.sock";
+ owner = cfg.user;
+ group = postfixCfg.group;
+ } ];
+ extraConfig = ''
+ upstream "local" {
+ default = yes; # Self-scan upstreams are always default
+ self_scan = yes; # Enable self-scan
+ }
+ '';
+ };
+ };
+ services.postfix.config = mkIf cfg.postfix.enable cfg.postfix.config;
+
+ # Allow users to run 'rspamc' and 'rspamadm'.
+ environment.systemPackages = [ pkgs.rspamd ];
+
+ users.users = singleton {
+ name = cfg.user;
+ description = "rspamd daemon";
+ uid = config.ids.uids.rspamd;
+ group = cfg.group;
+ };
+
+ users.groups = singleton {
+ name = cfg.group;
+ gid = config.ids.gids.rspamd;
+ };
+
+ environment.etc.rspamd.source = rspamdDir;
+
+ systemd.services.rspamd = {
+ description = "Rspamd Service";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ restartTriggers = [ rspamdDir ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} --user=${cfg.user} --group=${cfg.group} --pid=/run/rspamd.pid -c /etc/rspamd/rspamd.conf -f";
+ Restart = "always";
+ RuntimeDirectory = "rspamd";
+ PrivateTmp = true;
+ };
+
+ preStart = ''
+ ${pkgs.coreutils}/bin/mkdir -p /var/lib/rspamd
+ ${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} /var/lib/rspamd
+ '';
+ };
+ };
+ imports = [
+ (mkRemovedOptionModule [ "services" "rspamd" "socketActivation" ]
+ "Socket activation never worked correctly and could at this time not be fixed and so was removed")
+ (mkRenamedOptionModule [ "services" "rspamd" "bindSocket" ] [ "services" "rspamd" "workers" "normal" "bindSockets" ])
+ (mkRenamedOptionModule [ "services" "rspamd" "bindUISocket" ] [ "services" "rspamd" "workers" "controller" "bindSockets" ])
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/mail/rss2email.nix b/nixpkgs/nixos/modules/services/mail/rss2email.nix
new file mode 100644
index 00000000000..c1e5964c453
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/rss2email.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.rss2email;
+in {
+
+ ###### interface
+
+ options = {
+
+ services.rss2email = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable rss2email.";
+ };
+
+ to = mkOption {
+ type = types.str;
+ description = "Mail address to which to send emails";
+ };
+
+ interval = mkOption {
+ type = types.str;
+ default = "12h";
+ description = "How often to check the feeds, in systemd interval format";
+ };
+
+ config = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool ]);
+ default = {};
+ description = ''
+ The configuration to give rss2email.
+
+ Default will use system-wide <literal>sendmail</literal> to send the
+ email. This is rss2email's default when running
+ <literal>r2e new</literal>.
+
+ This set contains key-value associations that will be set in the
+ <literal>[DEFAULT]</literal> block along with the
+ <literal>to</literal> parameter.
+
+ See <literal>man r2e</literal> for more information on which
+ parameters are accepted.
+ '';
+ };
+
+ feeds = mkOption {
+ description = "The feeds to watch.";
+ type = types.attrsOf (types.submodule {
+ options = {
+ url = mkOption {
+ type = types.str;
+ description = "The URL at which to fetch the feed.";
+ };
+
+ to = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Email address to which to send feed items.
+
+ If <literal>null</literal>, this will not be set in the
+ configuration file, and rss2email will make it default to
+ <literal>rss2email.to</literal>.
+ '';
+ };
+ };
+ });
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ users.groups = {
+ rss2email.gid = config.ids.gids.rss2email;
+ };
+
+ users.users = {
+ rss2email = {
+ description = "rss2email user";
+ uid = config.ids.uids.rss2email;
+ group = "rss2email";
+ };
+ };
+
+ services.rss2email.config.to = cfg.to;
+
+ systemd.tmpfiles.rules = [
+ "d /var/rss2email 0700 rss2email rss2email - -"
+ ];
+
+ systemd.services.rss2email = let
+ conf = pkgs.writeText "rss2email.cfg" (lib.generators.toINI {} ({
+ DEFAULT = cfg.config;
+ } // lib.mapAttrs' (name: feed: nameValuePair "feed.${name}" (
+ { inherit (feed) url; } //
+ lib.optionalAttrs (feed.to != null) { inherit (feed) to; }
+ )) cfg.feeds
+ ));
+ in
+ {
+ preStart = ''
+ cp ${conf} /var/rss2email/conf.cfg
+ if [ ! -f /var/rss2email/db.json ]; then
+ echo '{"version":2,"feeds":[]}' > /var/rss2email/db.json
+ fi
+ '';
+ path = [ pkgs.system-sendmail ];
+ serviceConfig = {
+ ExecStart =
+ "${pkgs.rss2email}/bin/r2e -c /var/rss2email/conf.cfg -d /var/rss2email/db.json run";
+ User = "rss2email";
+ };
+ };
+
+ systemd.timers.rss2email = {
+ partOf = [ "rss2email.service" ];
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnBootSec = "0";
+ timerConfig.OnUnitActiveSec = cfg.interval;
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ ekleog ];
+}
diff --git a/nixpkgs/nixos/modules/services/mail/spamassassin.nix b/nixpkgs/nixos/modules/services/mail/spamassassin.nix
new file mode 100644
index 00000000000..1fe77ce5a0c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/spamassassin.nix
@@ -0,0 +1,199 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.spamassassin;
+ spamassassin-local-cf = pkgs.writeText "local.cf" cfg.config;
+ spamassassin-init-pre = pkgs.writeText "init.pre" cfg.initPreConf;
+
+ spamdEnv = pkgs.buildEnv {
+ name = "spamd-env";
+ paths = [];
+ postBuild = ''
+ ln -sf ${spamassassin-init-pre} $out/init.pre
+ ln -sf ${spamassassin-local-cf} $out/local.cf
+ '';
+ };
+
+in
+
+{
+ options = {
+
+ services.spamassassin = {
+ enable = mkOption {
+ default = false;
+ description = "Whether to run the SpamAssassin daemon";
+ };
+
+ debug = mkOption {
+ default = false;
+ description = "Whether to run the SpamAssassin daemon in debug mode";
+ };
+
+ config = mkOption {
+ type = types.lines;
+ description = ''
+ The SpamAssassin local.cf config
+
+ If you are using this configuration:
+ add_header all Status _YESNO_, score=_SCORE_ required=_REQD_ tests=_TESTS_ autolearn=_AUTOLEARN_ version=_VERSION_
+
+ Then you can Use this sieve filter:
+ require ["fileinto", "reject", "envelope"];
+
+ if header :contains "X-Spam-Flag" "YES" {
+ fileinto "spam";
+ }
+
+ Or this procmail filter:
+ :0:
+ * ^X-Spam-Flag: YES
+ /var/vpopmail/domains/lastlog.de/js/.maildir/.spam/new
+
+ To filter your messages based on the additional mail headers added by spamassassin.
+ '';
+ example = ''
+ #rewrite_header Subject [***** SPAM _SCORE_ *****]
+ required_score 5.0
+ use_bayes 1
+ bayes_auto_learn 1
+ add_header all Status _YESNO_, score=_SCORE_ required=_REQD_ tests=_TESTS_ autolearn=_AUTOLEARN_ version=_VERSION_
+ '';
+ default = "";
+ };
+
+ initPreConf = mkOption {
+ type = types.str;
+ description = "The SpamAssassin init.pre config.";
+ default =
+ ''
+ #
+ # to update this list, run this command in the rules directory:
+ # grep 'loadplugin.*Mail::SpamAssassin::Plugin::.*' -o -h * | sort | uniq
+ #
+
+ #loadplugin Mail::SpamAssassin::Plugin::AccessDB
+ #loadplugin Mail::SpamAssassin::Plugin::AntiVirus
+ loadplugin Mail::SpamAssassin::Plugin::AskDNS
+ # loadplugin Mail::SpamAssassin::Plugin::ASN
+ loadplugin Mail::SpamAssassin::Plugin::AutoLearnThreshold
+ #loadplugin Mail::SpamAssassin::Plugin::AWL
+ loadplugin Mail::SpamAssassin::Plugin::Bayes
+ loadplugin Mail::SpamAssassin::Plugin::BodyEval
+ loadplugin Mail::SpamAssassin::Plugin::Check
+ #loadplugin Mail::SpamAssassin::Plugin::DCC
+ loadplugin Mail::SpamAssassin::Plugin::DKIM
+ loadplugin Mail::SpamAssassin::Plugin::DNSEval
+ loadplugin Mail::SpamAssassin::Plugin::FreeMail
+ loadplugin Mail::SpamAssassin::Plugin::Hashcash
+ loadplugin Mail::SpamAssassin::Plugin::HeaderEval
+ loadplugin Mail::SpamAssassin::Plugin::HTMLEval
+ loadplugin Mail::SpamAssassin::Plugin::HTTPSMismatch
+ loadplugin Mail::SpamAssassin::Plugin::ImageInfo
+ loadplugin Mail::SpamAssassin::Plugin::MIMEEval
+ loadplugin Mail::SpamAssassin::Plugin::MIMEHeader
+ # loadplugin Mail::SpamAssassin::Plugin::PDFInfo
+ #loadplugin Mail::SpamAssassin::Plugin::PhishTag
+ loadplugin Mail::SpamAssassin::Plugin::Pyzor
+ loadplugin Mail::SpamAssassin::Plugin::Razor2
+ # loadplugin Mail::SpamAssassin::Plugin::RelayCountry
+ loadplugin Mail::SpamAssassin::Plugin::RelayEval
+ loadplugin Mail::SpamAssassin::Plugin::ReplaceTags
+ # loadplugin Mail::SpamAssassin::Plugin::Rule2XSBody
+ # loadplugin Mail::SpamAssassin::Plugin::Shortcircuit
+ loadplugin Mail::SpamAssassin::Plugin::SpamCop
+ loadplugin Mail::SpamAssassin::Plugin::SPF
+ #loadplugin Mail::SpamAssassin::Plugin::TextCat
+ # loadplugin Mail::SpamAssassin::Plugin::TxRep
+ loadplugin Mail::SpamAssassin::Plugin::URIDetail
+ loadplugin Mail::SpamAssassin::Plugin::URIDNSBL
+ loadplugin Mail::SpamAssassin::Plugin::URIEval
+ # loadplugin Mail::SpamAssassin::Plugin::URILocalBL
+ loadplugin Mail::SpamAssassin::Plugin::VBounce
+ loadplugin Mail::SpamAssassin::Plugin::WhiteListSubject
+ loadplugin Mail::SpamAssassin::Plugin::WLBLEval
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ # Allow users to run 'spamc'.
+
+ environment = {
+ etc = singleton { source = spamdEnv; target = "spamassassin"; };
+ systemPackages = [ pkgs.spamassassin ];
+ };
+
+ users.users = singleton {
+ name = "spamd";
+ description = "Spam Assassin Daemon";
+ uid = config.ids.uids.spamd;
+ group = "spamd";
+ };
+
+ users.groups = singleton {
+ name = "spamd";
+ gid = config.ids.gids.spamd;
+ };
+
+ systemd.services.sa-update = {
+ script = ''
+ set +e
+ ${pkgs.su}/bin/su -s "${pkgs.bash}/bin/bash" -c "${pkgs.spamassassin}/bin/sa-update --gpghomedir=/var/lib/spamassassin/sa-update-keys/ --siteconfigpath=${spamdEnv}/" spamd
+
+ v=$?
+ set -e
+ if [ $v -gt 1 ]; then
+ echo "sa-update execution error"
+ exit $v
+ fi
+ if [ $v -eq 0 ]; then
+ systemctl reload spamd.service
+ fi
+ '';
+ };
+
+ systemd.timers.sa-update = {
+ description = "sa-update-service";
+ partOf = [ "sa-update.service" ];
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = "1:*";
+ Persistent = true;
+ };
+ };
+
+ systemd.services.spamd = {
+ description = "Spam Assassin Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.spamassassin}/bin/spamd ${optionalString cfg.debug "-D"} --username=spamd --groupname=spamd --siteconfigpath=${spamdEnv} --virtual-config-dir=/var/lib/spamassassin/user-%u --allow-tell --pidfile=/run/spamd.pid";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+
+ # 0 and 1 no error, exitcode > 1 means error:
+ # https://spamassassin.apache.org/full/3.1.x/doc/sa-update.html#exit_codes
+ preStart = ''
+ echo "Recreating '/var/lib/spamasassin' with creating '3.004001' (or similar) and 'sa-update-keys'"
+ mkdir -p /var/lib/spamassassin
+ chown spamd:spamd /var/lib/spamassassin -R
+ set +e
+ ${pkgs.su}/bin/su -s "${pkgs.bash}/bin/bash" -c "${pkgs.spamassassin}/bin/sa-update --gpghomedir=/var/lib/spamassassin/sa-update-keys/ --siteconfigpath=${spamdEnv}/" spamd
+ v=$?
+ set -e
+ if [ $v -gt 1 ]; then
+ echo "sa-update execution error"
+ exit $v
+ fi
+ chown spamd:spamd /var/lib/spamassassin -R
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/airsonic.nix b/nixpkgs/nixos/modules/services/misc/airsonic.nix
new file mode 100644
index 00000000000..919d3b2f6e6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/airsonic.nix
@@ -0,0 +1,153 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.airsonic;
+in {
+ options = {
+
+ services.airsonic = {
+ enable = mkEnableOption "Airsonic, the Free and Open Source media streaming server (fork of Subsonic and Libresonic)";
+
+ user = mkOption {
+ type = types.str;
+ default = "airsonic";
+ description = "User account under which airsonic runs.";
+ };
+
+ home = mkOption {
+ type = types.path;
+ default = "/var/lib/airsonic";
+ description = ''
+ The directory where Airsonic will create files.
+ Make sure it is writable.
+ '';
+ };
+
+ virtualHost = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ The host name or IP address on which to bind Airsonic.
+ Only relevant if you have multiple network interfaces and want
+ to make Airsonic available on only one of them. The default value
+ will bind Airsonic to all available network interfaces.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 4040;
+ description = ''
+ The port on which Airsonic will listen for
+ incoming HTTP traffic. Set to 0 to disable.
+ '';
+ };
+
+ contextPath = mkOption {
+ type = types.path;
+ default = "/";
+ description = ''
+ The context path, i.e., the last part of the Airsonic
+ URL. Typically '/' or '/airsonic'. Default '/'
+ '';
+ };
+
+ maxMemory = mkOption {
+ type = types.int;
+ default = 100;
+ description = ''
+ The memory limit (max Java heap size) in megabytes.
+ Default: 100
+ '';
+ };
+
+ transcoders = mkOption {
+ type = types.listOf types.path;
+ default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
+ defaultText= [ "\${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
+ description = ''
+ List of paths to transcoder executables that should be accessible
+ from Airsonic. Symlinks will be created to each executable inside
+ ${cfg.home}/transcoders.
+ '';
+ };
+
+ jvmOptions = mkOption {
+ description = ''
+ Extra command line options for the JVM running AirSonic.
+ Useful for sending jukebox output to non-default alsa
+ devices.
+ '';
+ default = [
+ ];
+ type = types.listOf types.str;
+ example = [
+ "-Djavax.sound.sampled.Clip='#CODEC [plughw:1,0]'"
+ "-Djavax.sound.sampled.Port='#Port CODEC [hw:1]'"
+ "-Djavax.sound.sampled.SourceDataLine='#CODEC [plughw:1,0]'"
+ "-Djavax.sound.sampled.TargetDataLine='#CODEC [plughw:1,0]'"
+ ];
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.airsonic = {
+ description = "Airsonic Media Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ # Install transcoders.
+ rm -rf ${cfg.home}/transcode
+ mkdir -p ${cfg.home}/transcode
+ for exe in ${toString cfg.transcoders}; do
+ ln -sf "$exe" ${cfg.home}/transcode
+ done
+ '';
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.jre}/bin/java -Xmx${toString cfg.maxMemory}m \
+ -Dairsonic.home=${cfg.home} \
+ -Dserver.address=${cfg.listenAddress} \
+ -Dserver.port=${toString cfg.port} \
+ -Dairsonic.contextPath=${cfg.contextPath} \
+ -Djava.awt.headless=true \
+ ${optionalString (cfg.virtualHost != null)
+ "-Dserver.use-forward-headers=true"} \
+ ${toString cfg.jvmOptions} \
+ -verbose:gc \
+ -jar ${pkgs.airsonic}/webapps/airsonic.war
+ '';
+ Restart = "always";
+ User = "airsonic";
+ UMask = "0022";
+ };
+ };
+
+ services.nginx = mkIf (cfg.virtualHost != null) {
+ enable = true;
+ virtualHosts.${cfg.virtualHost} = {
+ locations.${cfg.contextPath}.proxyPass = "http://${cfg.listenAddress}:${toString cfg.port}";
+ };
+ };
+
+ users.users.airsonic = {
+ description = "Airsonic service user";
+ name = cfg.user;
+ home = cfg.home;
+ createHome = true;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/apache-kafka.nix b/nixpkgs/nixos/modules/services/misc/apache-kafka.nix
new file mode 100644
index 00000000000..798e902ccae
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/apache-kafka.nix
@@ -0,0 +1,155 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.apache-kafka;
+
+ serverProperties =
+ if cfg.serverProperties != null then
+ cfg.serverProperties
+ else
+ ''
+ # Generated by nixos
+ broker.id=${toString cfg.brokerId}
+ port=${toString cfg.port}
+ host.name=${cfg.hostname}
+ log.dirs=${concatStringsSep "," cfg.logDirs}
+ zookeeper.connect=${cfg.zookeeper}
+ ${toString cfg.extraProperties}
+ '';
+
+ serverConfig = pkgs.writeText "server.properties" serverProperties;
+ logConfig = pkgs.writeText "log4j.properties" cfg.log4jProperties;
+
+in {
+
+ options.services.apache-kafka = {
+ enable = mkOption {
+ description = "Whether to enable Apache Kafka.";
+ default = false;
+ type = types.bool;
+ };
+
+ brokerId = mkOption {
+ description = "Broker ID.";
+ default = -1;
+ type = types.int;
+ };
+
+ port = mkOption {
+ description = "Port number the broker should listen on.";
+ default = 9092;
+ type = types.int;
+ };
+
+ hostname = mkOption {
+ description = "Hostname the broker should bind to.";
+ default = "localhost";
+ type = types.str;
+ };
+
+ logDirs = mkOption {
+ description = "Log file directories";
+ default = [ "/tmp/kafka-logs" ];
+ type = types.listOf types.path;
+ };
+
+ zookeeper = mkOption {
+ description = "Zookeeper connection string";
+ default = "localhost:2181";
+ type = types.str;
+ };
+
+ extraProperties = mkOption {
+ description = "Extra properties for server.properties.";
+ type = types.nullOr types.lines;
+ default = null;
+ };
+
+ serverProperties = mkOption {
+ description = ''
+ Complete server.properties content. Other server.properties config
+ options will be ignored if this option is used.
+ '';
+ type = types.nullOr types.lines;
+ default = null;
+ };
+
+ log4jProperties = mkOption {
+ description = "Kafka log4j property configuration.";
+ default = ''
+ log4j.rootLogger=INFO, stdout
+
+ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+ log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n
+ '';
+ type = types.lines;
+ };
+
+ jvmOptions = mkOption {
+ description = "Extra command line options for the JVM running Kafka.";
+ default = [
+ "-server"
+ "-Xmx1G"
+ "-Xms1G"
+ "-XX:+UseCompressedOops"
+ "-XX:+UseParNewGC"
+ "-XX:+UseConcMarkSweepGC"
+ "-XX:+CMSClassUnloadingEnabled"
+ "-XX:+CMSScavengeBeforeRemark"
+ "-XX:+DisableExplicitGC"
+ "-Djava.awt.headless=true"
+ "-Djava.net.preferIPv4Stack=true"
+ ];
+ type = types.listOf types.str;
+ example = [
+ "-Djava.net.preferIPv4Stack=true"
+ "-Dcom.sun.management.jmxremote"
+ "-Dcom.sun.management.jmxremote.local.only=true"
+ ];
+ };
+
+ package = mkOption {
+ description = "The kafka package to use";
+ default = pkgs.apacheKafka;
+ defaultText = "pkgs.apacheKafka";
+ type = types.package;
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [cfg.package];
+
+ users.users = singleton {
+ name = "apache-kafka";
+ uid = config.ids.uids.apache-kafka;
+ description = "Apache Kafka daemon user";
+ home = head cfg.logDirs;
+ };
+
+ systemd.tmpfiles.rules = map (logDir: "d '${logDir} 0700 apache-kafka - - -") cfg.logDirs;
+
+ systemd.services.apache-kafka = {
+ description = "Apache Kafka Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.jre}/bin/java \
+ -cp "${cfg.package}/libs/*" \
+ -Dlog4j.configuration=file:${logConfig} \
+ ${toString cfg.jvmOptions} \
+ kafka.Kafka \
+ ${serverConfig}
+ '';
+ User = "apache-kafka";
+ SuccessExitStatus = "0 143";
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/autofs.nix b/nixpkgs/nixos/modules/services/misc/autofs.nix
new file mode 100644
index 00000000000..f1742177326
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/autofs.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.autofs;
+
+ autoMaster = pkgs.writeText "auto.master" cfg.autoMaster;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.autofs = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Mount filesystems on demand. Unmount them automatically.
+ You may also be interested in afuse.
+ '';
+ };
+
+ autoMaster = mkOption {
+ type = types.str;
+ example = literalExample ''
+ let
+ mapConf = pkgs.writeText "auto" '''
+ kernel -ro,soft,intr ftp.kernel.org:/pub/linux
+ boot -fstype=ext2 :/dev/hda1
+ windoze -fstype=smbfs ://windoze/c
+ removable -fstype=ext2 :/dev/hdd
+ cd -fstype=iso9660,ro :/dev/hdc
+ floppy -fstype=auto :/dev/fd0
+ server -rw,hard,intr / -ro myserver.me.org:/ \
+ /usr myserver.me.org:/usr \
+ /home myserver.me.org:/home
+ ''';
+ in '''
+ /auto file:''${mapConf}
+ '''
+ '';
+ description = ''
+ Contents of <literal>/etc/auto.master</literal> file. See <command>auto.master(5)</command> and <command>autofs(5)</command>.
+ '';
+ };
+
+ timeout = mkOption {
+ default = 600;
+ description = "Set the global minimum timeout, in seconds, until directories are unmounted";
+ };
+
+ debug = mkOption {
+ default = false;
+ description = ''
+ Pass -d and -7 to automount and write log to the system journal.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ boot.kernelModules = [ "autofs4" ];
+
+ systemd.services.autofs =
+ { description = "Automounts filesystems on demand";
+ after = [ "network.target" "ypbind.service" "sssd.service" "network-online.target" ];
+ wants = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ # There should be only one autofs service managed by systemd, so this should be safe.
+ rm -f /tmp/autofs-running
+ '';
+
+ serviceConfig = {
+ Type = "forking";
+ PIDFile = "/run/autofs.pid";
+ ExecStart = "${pkgs.autofs5}/bin/automount ${optionalString cfg.debug "-d"} -p /run/autofs.pid -t ${builtins.toString cfg.timeout} ${autoMaster}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/autorandr.nix b/nixpkgs/nixos/modules/services/misc/autorandr.nix
new file mode 100644
index 00000000000..4708e16e2a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/autorandr.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.autorandr;
+
+in {
+
+ options = {
+
+ services.autorandr = {
+ enable = mkEnableOption "handling of hotplug and sleep events by autorandr";
+
+ defaultTarget = mkOption {
+ default = "default";
+ type = types.str;
+ description = ''
+ Fallback if no monitor layout can be detected. See the docs
+ (https://github.com/phillipberndt/autorandr/blob/v1.0/README.md#how-to-use)
+ for further reference.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ services.udev.packages = [ pkgs.autorandr ];
+
+ environment.systemPackages = [ pkgs.autorandr ];
+
+ systemd.services.autorandr = {
+ wantedBy = [ "sleep.target" ];
+ description = "Autorandr execution hook";
+ after = [ "sleep.target" ];
+
+ serviceConfig = {
+ StartLimitInterval = 5;
+ StartLimitBurst = 1;
+ ExecStart = "${pkgs.autorandr}/bin/autorandr --batch --change --default ${cfg.defaultTarget}";
+ Type = "oneshot";
+ RemainAfterExit = false;
+ };
+ };
+
+ };
+
+ meta.maintainers = with maintainers; [ gnidorah ma27 ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/beanstalkd.nix b/nixpkgs/nixos/modules/services/misc/beanstalkd.nix
new file mode 100644
index 00000000000..06e881406b5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/beanstalkd.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.beanstalkd;
+ pkg = pkgs.beanstalkd;
+in
+
+{
+ # interface
+
+ options = {
+ services.beanstalkd = {
+ enable = mkEnableOption "the Beanstalk work queue";
+
+ listen = {
+ port = mkOption {
+ type = types.int;
+ description = "TCP port that will be used to accept client connections.";
+ default = 11300;
+ };
+
+ address = mkOption {
+ type = types.str;
+ description = "IP address to listen on.";
+ default = "127.0.0.1";
+ example = "0.0.0.0";
+ };
+ };
+ };
+ };
+
+ # implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkg ];
+
+ systemd.services.beanstalkd = {
+ description = "Beanstalk Work Queue";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ DynamicUser = true;
+ Restart = "always";
+ ExecStart = "${pkg}/bin/beanstalkd -l ${cfg.listen.address} -p ${toString cfg.listen.port}";
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/bees.nix b/nixpkgs/nixos/modules/services/misc/bees.nix
new file mode 100644
index 00000000000..b0ed2d5c286
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/bees.nix
@@ -0,0 +1,123 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.beesd;
+
+ logLevels = { emerg = 0; alert = 1; crit = 2; err = 3; warning = 4; notice = 5; info = 6; debug = 7; };
+
+ fsOptions = with types; {
+ options.spec = mkOption {
+ type = str;
+ description = ''
+ Description of how to identify the filesystem to be duplicated by this
+ instance of bees. Note that deduplication crosses subvolumes; one must
+ not configure multiple instances for subvolumes of the same filesystem
+ (or block devices which are part of the same filesystem), but only for
+ completely independent btrfs filesystems.
+ </para>
+ <para>
+ This must be in a format usable by findmnt; that could be a key=value
+ pair, or a bare path to a mount point.
+ '';
+ example = "LABEL=MyBulkDataDrive";
+ };
+ options.hashTableSizeMB = mkOption {
+ type = types.addCheck types.int (n: mod n 16 == 0);
+ default = 1024; # 1GB; default from upstream beesd script
+ description = ''
+ Hash table size in MB; must be a multiple of 16.
+ </para>
+ <para>
+ A larger ratio of index size to storage size means smaller blocks of
+ duplicate content are recognized.
+ </para>
+ <para>
+ If you have 1TB of data, a 4GB hash table (which is to say, a value of
+ 4096) will permit 4KB extents (the smallest possible size) to be
+ recognized, whereas a value of 1024 -- creating a 1GB hash table --
+ will recognize only aligned duplicate blocks of 16KB.
+ '';
+ };
+ options.verbosity = mkOption {
+ type = types.enum (attrNames logLevels ++ attrValues logLevels);
+ apply = v: if isString v then logLevels.${v} else v;
+ default = "info";
+ description = "Log verbosity (syslog keyword/level).";
+ };
+ options.workDir = mkOption {
+ type = str;
+ default = ".beeshome";
+ description = ''
+ Name (relative to the root of the filesystem) of the subvolume where
+ the hash table will be stored.
+ '';
+ };
+ options.extraOptions = mkOption {
+ type = listOf str;
+ default = [];
+ description = ''
+ Extra command-line options passed to the daemon. See upstream bees documentation.
+ '';
+ example = literalExample ''
+ [ "--thread-count" "4" ]
+ '';
+ };
+ };
+
+in {
+
+ options.services.beesd = {
+ filesystems = mkOption {
+ type = with types; attrsOf (submodule fsOptions);
+ description = "BTRFS filesystems to run block-level deduplication on.";
+ default = { };
+ example = literalExample ''
+ {
+ root = {
+ spec = "LABEL=root";
+ hashTableSizeMB = 2048;
+ verbosity = "crit";
+ extraOptions = [ "--loadavg-target" "5.0" ];
+ };
+ }
+ '';
+ };
+ };
+ config = {
+ systemd.services = mapAttrs' (name: fs: nameValuePair "beesd@${name}" {
+ description = "Block-level BTRFS deduplication for %i";
+ after = [ "sysinit.target" ];
+
+ serviceConfig = let
+ configOpts = [
+ fs.spec
+ "verbosity=${toString fs.verbosity}"
+ "idxSizeMB=${toString fs.hashTableSizeMB}"
+ "workDir=${fs.workDir}"
+ ];
+ configOptsStr = escapeShellArgs configOpts;
+ in {
+ # Values from https://github.com/Zygo/bees/blob/v0.6.1/scripts/beesd%40.service.in
+ ExecStart = "${pkgs.bees}/bin/bees-service-wrapper run ${configOptsStr} -- --no-timestamps ${escapeShellArgs fs.extraOptions}";
+ ExecStopPost = "${pkgs.bees}/bin/bees-service-wrapper cleanup ${configOptsStr}";
+ CPUAccounting = true;
+ CPUWeight = 12;
+ IOSchedulingClass = "idle";
+ IOSchedulingPriority = 7;
+ IOWeight = 10;
+ KillMode = "control-group";
+ KillSignal = "SIGTERM";
+ MemoryAccounting = true;
+ Nice = 19;
+ Restart = "on-abnormal";
+ StartupCPUWeight = 25;
+ StartupIOWeight = 25;
+ SyslogIdentifier = "bees"; # would otherwise be "bees-service-wrapper"
+ };
+ wantedBy = ["multi-user.target"];
+ }) cfg.filesystems;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/bepasty.nix b/nixpkgs/nixos/modules/services/misc/bepasty.nix
new file mode 100644
index 00000000000..87d36068144
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/bepasty.nix
@@ -0,0 +1,183 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ gunicorn = pkgs.python3Packages.gunicorn;
+ bepasty = pkgs.bepasty;
+ gevent = pkgs.python3Packages.gevent;
+ python = pkgs.python3Packages.python;
+ cfg = config.services.bepasty;
+ user = "bepasty";
+ group = "bepasty";
+ default_home = "/var/lib/bepasty";
+in
+{
+ options.services.bepasty = {
+ enable = mkEnableOption "Bepasty servers";
+
+ servers = mkOption {
+ default = {};
+ description = ''
+ configure a number of bepasty servers which will be started with
+ gunicorn.
+ '';
+ type = with types ; attrsOf (submodule ({ config, ... } : {
+
+ options = {
+
+ bind = mkOption {
+ type = types.str;
+ description = ''
+ Bind address to be used for this server.
+ '';
+ example = "0.0.0.0:8000";
+ default = "127.0.0.1:8000";
+ };
+
+ dataDir = mkOption {
+ type = types.str;
+ description = ''
+ Path to the directory where the pastes will be saved to
+ '';
+ default = default_home+"/data";
+ };
+
+ defaultPermissions = mkOption {
+ type = types.str;
+ description = ''
+ default permissions for all unauthenticated accesses.
+ '';
+ example = "read,create,delete";
+ default = "read";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ description = ''
+ Extra configuration for bepasty server to be appended on the
+ configuration.
+ see https://bepasty-server.readthedocs.org/en/latest/quickstart.html#configuring-bepasty
+ for all options.
+ '';
+ default = "";
+ example = ''
+ PERMISSIONS = {
+ 'myadminsecret': 'admin,list,create,read,delete',
+ }
+ MAX_ALLOWED_FILE_SIZE = 5 * 1000 * 1000
+ '';
+ };
+
+ secretKey = mkOption {
+ type = types.str;
+ description = ''
+ server secret for safe session cookies, must be set.
+
+ Warning: this secret is stored in the WORLD-READABLE Nix store!
+
+ It's recommended to use <option>secretKeyFile</option>
+ which takes precedence over <option>secretKey</option>.
+ '';
+ default = "";
+ };
+
+ secretKeyFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ A file that contains the server secret for safe session cookies, must be set.
+
+ <option>secretKeyFile</option> takes precedence over <option>secretKey</option>.
+
+ Warning: when <option>secretKey</option> is non-empty <option>secretKeyFile</option>
+ defaults to a file in the WORLD-READABLE Nix store containing that secret.
+ '';
+ };
+
+ workDir = mkOption {
+ type = types.str;
+ description = ''
+ Path to the working directory (used for config and pidfile).
+ Defaults to the users home directory.
+ '';
+ default = default_home;
+ };
+
+ };
+ config = {
+ secretKeyFile = mkDefault (
+ if config.secretKey != ""
+ then toString (pkgs.writeTextFile {
+ name = "bepasty-secret-key";
+ text = config.secretKey;
+ })
+ else null
+ );
+ };
+ }));
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ bepasty ];
+
+ # creates gunicorn systemd service for each configured server
+ systemd.services = mapAttrs' (name: server:
+ nameValuePair ("bepasty-server-${name}-gunicorn")
+ ({
+ description = "Bepasty Server ${name}";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ restartIfChanged = true;
+
+ environment = let
+ penv = python.buildEnv.override {
+ extraLibs = [ bepasty gevent ];
+ };
+ in {
+ BEPASTY_CONFIG = "${server.workDir}/bepasty-${name}.conf";
+ PYTHONPATH= "${penv}/${python.sitePackages}/";
+ };
+
+ serviceConfig = {
+ Type = "simple";
+ PrivateTmp = true;
+ ExecStartPre = assert server.secretKeyFile != null; pkgs.writeScript "bepasty-server.${name}-init" ''
+ #!/bin/sh
+ mkdir -p "${server.workDir}"
+ mkdir -p "${server.dataDir}"
+ chown ${user}:${group} "${server.workDir}" "${server.dataDir}"
+ cat > ${server.workDir}/bepasty-${name}.conf <<EOF
+ SITENAME="${name}"
+ STORAGE_FILESYSTEM_DIRECTORY="${server.dataDir}"
+ SECRET_KEY="$(cat "${server.secretKeyFile}")"
+ DEFAULT_PERMISSIONS="${server.defaultPermissions}"
+ ${server.extraConfig}
+ EOF
+ '';
+ ExecStart = ''${gunicorn}/bin/gunicorn bepasty.wsgi --name ${name} \
+ -u ${user} \
+ -g ${group} \
+ --workers 3 --log-level=info \
+ --bind=${server.bind} \
+ --pid ${server.workDir}/gunicorn-${name}.pid \
+ -k gevent
+ '';
+ };
+ })
+ ) cfg.servers;
+
+ users.users = [{
+ uid = config.ids.uids.bepasty;
+ name = user;
+ group = group;
+ home = default_home;
+ }];
+
+ users.groups = [{
+ name = group;
+ gid = config.ids.gids.bepasty;
+ }];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/calibre-server.nix b/nixpkgs/nixos/modules/services/misc/calibre-server.nix
new file mode 100644
index 00000000000..84c04f403d3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/calibre-server.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.calibre-server;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.calibre-server = {
+
+ enable = mkEnableOption "calibre-server";
+
+ libraryDir = mkOption {
+ description = ''
+ The directory where the Calibre library to serve is.
+ '';
+ type = types.path;
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.calibre-server =
+ {
+ description = "Calibre Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "calibre-server";
+ Restart = "always";
+ ExecStart = "${pkgs.calibre}/bin/calibre-server ${cfg.libraryDir}";
+ };
+
+ };
+
+ environment.systemPackages = [ pkgs.calibre ];
+
+ users.users.calibre-server = {
+ uid = config.ids.uids.calibre-server;
+ group = "calibre-server";
+ };
+
+ users.groups.calibre-server = {
+ gid = config.ids.gids.calibre-server;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/canto-daemon.nix b/nixpkgs/nixos/modules/services/misc/canto-daemon.nix
new file mode 100644
index 00000000000..db51a263aab
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/canto-daemon.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+cfg = config.services.canto-daemon;
+
+in {
+
+##### interface
+
+ options = {
+
+ services.canto-daemon = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the canto RSS daemon.";
+ };
+ };
+
+ };
+
+##### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.user.services.canto-daemon = {
+ description = "Canto RSS Daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "default.target" ];
+ serviceConfig.ExecStart = "${pkgs.canto-daemon}/bin/canto-daemon";
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/cfdyndns.nix b/nixpkgs/nixos/modules/services/misc/cfdyndns.nix
new file mode 100644
index 00000000000..dcf41602273
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/cfdyndns.nix
@@ -0,0 +1,70 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cfdyndns;
+in
+{
+ options = {
+ services.cfdyndns = {
+ enable = mkEnableOption "Cloudflare Dynamic DNS Client";
+
+ email = mkOption {
+ type = types.str;
+ description = ''
+ The email address to use to authenticate to CloudFlare.
+ '';
+ };
+
+ apikey = mkOption {
+ type = types.str;
+ description = ''
+ The API Key to use to authenticate to CloudFlare.
+ '';
+ };
+
+ records = mkOption {
+ default = [];
+ example = [ "host.tld" ];
+ type = types.listOf types.str;
+ description = ''
+ The records to update in CloudFlare.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.cfdyndns = {
+ description = "CloudFlare Dynamic DNS Client";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ startAt = "5 minutes";
+ serviceConfig = {
+ Type = "simple";
+ User = config.ids.uids.cfdyndns;
+ Group = config.ids.gids.cfdyndns;
+ ExecStart = "/bin/sh -c '${pkgs.cfdyndns}/bin/cfdyndns'";
+ };
+ environment = {
+ CLOUDFLARE_EMAIL="${cfg.email}";
+ CLOUDFLARE_APIKEY="${cfg.apikey}";
+ CLOUDFLARE_RECORDS="${concatStringsSep "," cfg.records}";
+ };
+ };
+
+ users.users = {
+ cfdyndns = {
+ group = "cfdyndns";
+ uid = config.ids.uids.cfdyndns;
+ };
+ };
+
+ users.groups = {
+ cfdyndns = {
+ gid = config.ids.gids.cfdyndns;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/cgminer.nix b/nixpkgs/nixos/modules/services/misc/cgminer.nix
new file mode 100644
index 00000000000..b1cf5a7d110
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/cgminer.nix
@@ -0,0 +1,145 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cgminer;
+
+ convType = with builtins;
+ v: if isBool v then boolToString v else toString v;
+ mergedHwConfig =
+ mapAttrsToList (n: v: ''"${n}": "${(concatStringsSep "," (map convType v))}"'')
+ (foldAttrs (n: a: [n] ++ a) [] cfg.hardware);
+ mergedConfig = with builtins;
+ mapAttrsToList (n: v: ''"${n}": ${if isBool v then "" else ''"''}${convType v}${if isBool v then "" else ''"''}'')
+ cfg.config;
+
+ cgminerConfig = pkgs.writeText "cgminer.conf" ''
+ {
+ ${concatStringsSep ",\n" mergedHwConfig},
+ ${concatStringsSep ",\n" mergedConfig},
+ "pools": [
+ ${concatStringsSep ",\n"
+ (map (v: ''{"url": "${v.url}", "user": "${v.user}", "pass": "${v.pass}"}'')
+ cfg.pools)}]
+ }
+ '';
+in
+{
+ ###### interface
+ options = {
+
+ services.cgminer = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable cgminer, an ASIC/FPGA/GPU miner for bitcoin and
+ litecoin.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.cgminer;
+ defaultText = "pkgs.cgminer";
+ description = "Which cgminer derivation to use.";
+ type = types.package;
+ };
+
+ user = mkOption {
+ default = "cgminer";
+ description = "User account under which cgminer runs";
+ };
+
+ pools = mkOption {
+ default = []; # Run benchmark
+ description = "List of pools where to mine";
+ example = [{
+ url = "http://p2pool.org:9332";
+ username = "17EUZxTvs9uRmPsjPZSYUU3zCz9iwstudk";
+ password="X";
+ }];
+ };
+
+ hardware = mkOption {
+ default = []; # Run without options
+ description= "List of config options for every GPU";
+ example = [
+ {
+ intensity = 9;
+ gpu-engine = "0-985";
+ gpu-fan = "0-85";
+ gpu-memclock = 860;
+ gpu-powertune = 20;
+ temp-cutoff = 95;
+ temp-overheat = 85;
+ temp-target = 75;
+ }
+ {
+ intensity = 9;
+ gpu-engine = "0-950";
+ gpu-fan = "0-85";
+ gpu-memclock = 825;
+ gpu-powertune = 20;
+ temp-cutoff = 95;
+ temp-overheat = 85;
+ temp-target = 75;
+ }];
+ };
+
+ config = mkOption {
+ default = {};
+ description = "Additional config";
+ example = {
+ auto-fan = true;
+ auto-gpu = true;
+ expiry = 120;
+ failover-only = true;
+ gpu-threads = 2;
+ log = 5;
+ queue = 1;
+ scan-time = 60;
+ temp-histeresys = 3;
+ };
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.cgminer.enable {
+
+ users.users = optionalAttrs (cfg.user == "cgminer") (singleton
+ { name = "cgminer";
+ uid = config.ids.uids.cgminer;
+ description = "Cgminer user";
+ });
+
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services.cgminer = {
+ path = [ pkgs.cgminer ];
+
+ after = [ "network.target" "display-manager.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ environment = {
+ LD_LIBRARY_PATH = ''/run/opengl-driver/lib:/run/opengl-driver-32/lib'';
+ DISPLAY = ":${toString config.services.xserver.display}";
+ GPU_MAX_ALLOC_PERCENT = "100";
+ GPU_USE_SYNC_OBJECTS = "1";
+ };
+
+ serviceConfig = {
+ ExecStart = "${pkgs.cgminer}/bin/cgminer --syslog --text-only --config ${cgminerConfig}";
+ User = cfg.user;
+ RestartSec = "30s";
+ Restart = "always";
+ StartLimitInterval = "1m";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/clipmenu.nix b/nixpkgs/nixos/modules/services/misc/clipmenu.nix
new file mode 100644
index 00000000000..3ba050044ca
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/clipmenu.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.clipmenu;
+in {
+
+ options.services.clipmenu = {
+ enable = mkEnableOption "clipmenu, the clipboard management daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.clipmenu;
+ defaultText = "pkgs.clipmenu";
+ description = "clipmenu derivation to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.clipmenu = {
+ enable = true;
+ description = "Clipboard management daemon";
+ wantedBy = [ "graphical-session.target" ];
+ after = [ "graphical-session.target" ];
+ serviceConfig.ExecStart = "${cfg.package}/bin/clipmenud";
+ };
+
+ environment.systemPackages = [ cfg.package ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/confd.nix b/nixpkgs/nixos/modules/services/misc/confd.nix
new file mode 100755
index 00000000000..8e9bec15dd4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/confd.nix
@@ -0,0 +1,90 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.confd;
+
+ confdConfig = ''
+ backend = "${cfg.backend}"
+ confdir = "${cfg.confDir}"
+ interval = ${toString cfg.interval}
+ nodes = [ ${concatMapStringsSep "," (s: ''"${s}"'') cfg.nodes}, ]
+ prefix = "${cfg.prefix}"
+ log-level = "${cfg.logLevel}"
+ watch = ${boolToString cfg.watch}
+ '';
+
+in {
+ options.services.confd = {
+ enable = mkEnableOption "confd service";
+
+ backend = mkOption {
+ description = "Confd config storage backend to use.";
+ default = "etcd";
+ type = types.enum ["etcd" "consul" "redis" "zookeeper"];
+ };
+
+ interval = mkOption {
+ description = "Confd check interval.";
+ default = 10;
+ type = types.int;
+ };
+
+ nodes = mkOption {
+ description = "Confd list of nodes to connect to.";
+ default = [ "http://127.0.0.1:2379" ];
+ type = types.listOf types.str;
+ };
+
+ watch = mkOption {
+ description = "Confd, whether to watch etcd config for changes.";
+ default = true;
+ type = types.bool;
+ };
+
+ prefix = mkOption {
+ description = "The string to prefix to keys.";
+ default = "/";
+ type = types.path;
+ };
+
+ logLevel = mkOption {
+ description = "Confd log level.";
+ default = "info";
+ type = types.enum ["info" "debug"];
+ };
+
+ confDir = mkOption {
+ description = "The path to the confd configs.";
+ default = "/etc/confd";
+ type = types.path;
+ };
+
+ package = mkOption {
+ description = "Confd package to use.";
+ default = pkgs.confd;
+ defaultText = "pkgs.confd";
+ type = types.package;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.confd = {
+ description = "Confd Service.";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = "${cfg.package.bin}/bin/confd";
+ };
+ };
+
+ environment.etc = {
+ "confd/confd.toml".text = confdConfig;
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ services.etcd.enable = mkIf (cfg.backend == "etcd") (mkDefault true);
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/couchpotato.nix b/nixpkgs/nixos/modules/services/misc/couchpotato.nix
new file mode 100644
index 00000000000..528af486b41
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/couchpotato.nix
@@ -0,0 +1,45 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.couchpotato;
+
+in
+{
+ options = {
+ services.couchpotato = {
+ enable = mkEnableOption "CouchPotato Server";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.couchpotato = {
+ description = "CouchPotato Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = "couchpotato";
+ Group = "couchpotato";
+ StateDirectory = "couchpotato";
+ ExecStart = "${pkgs.couchpotato}/bin/couchpotato";
+ Restart = "on-failure";
+ };
+ };
+
+ users.users = singleton
+ { name = "couchpotato";
+ group = "couchpotato";
+ home = "/var/lib/couchpotato/";
+ description = "CouchPotato daemon user";
+ uid = config.ids.uids.couchpotato;
+ };
+
+ users.groups = singleton
+ { name = "couchpotato";
+ gid = config.ids.gids.couchpotato;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix b/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix
new file mode 100644
index 00000000000..907b9d90da2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cpuminer-cryptonight;
+
+ json = builtins.toJSON (
+ cfg // {
+ enable = null;
+ threads =
+ if cfg.threads == 0 then null else toString cfg.threads;
+ }
+ );
+
+ confFile = builtins.toFile "cpuminer.json" json;
+in
+{
+
+ options = {
+
+ services.cpuminer-cryptonight = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the cpuminer cryptonight miner.
+ '';
+ };
+ url = mkOption {
+ type = types.str;
+ description = "URL of mining server";
+ };
+ user = mkOption {
+ type = types.str;
+ description = "Username for mining server";
+ };
+ pass = mkOption {
+ type = types.str;
+ default = "x";
+ description = "Password for mining server";
+ };
+ threads = mkOption {
+ type = types.int;
+ default = 0;
+ description = "Number of miner threads, defaults to available processors";
+ };
+ };
+
+ };
+
+ config = mkIf config.services.cpuminer-cryptonight.enable {
+
+ systemd.services.cpuminer-cryptonight = {
+ description = "Cryptonight cpuminer";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.cpuminer-multi}/bin/minerd --syslog --config=${confFile}";
+ User = "nobody";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb b/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb
new file mode 100644
index 00000000000..0b58c59c7a5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb
@@ -0,0 +1,69 @@
+worker_processes 3
+
+listen ENV["UNICORN_PATH"] + "/tmp/sockets/gitlab.socket", :backlog => 1024
+listen "/run/gitlab/gitlab.socket", :backlog => 1024
+
+working_directory ENV["GITLAB_PATH"]
+
+pid ENV["UNICORN_PATH"] + "/tmp/pids/unicorn.pid"
+
+timeout 60
+
+# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
+# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
+preload_app true
+GC.respond_to?(:copy_on_write_friendly=) and
+ GC.copy_on_write_friendly = true
+
+check_client_connection false
+
+before_fork do |server, worker|
+ # the following is highly recommended for Rails + "preload_app true"
+ # as there's no need for the master process to hold a connection
+ defined?(ActiveRecord::Base) and
+ ActiveRecord::Base.connection.disconnect!
+
+ # The following is only recommended for memory/DB-constrained
+ # installations. It is not needed if your system can house
+ # twice as many worker_processes as you have configured.
+ #
+ # This allows a new master process to incrementally
+ # phase out the old master process with SIGTTOU to avoid a
+ # thundering herd (especially in the "preload_app false" case)
+ # when doing a transparent upgrade. The last worker spawned
+ # will then kill off the old master process with a SIGQUIT.
+ old_pid = "#{server.config[:pid]}.oldbin"
+ if old_pid != server.pid
+ begin
+ sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
+ Process.kill(sig, File.read(old_pid).to_i)
+ rescue Errno::ENOENT, Errno::ESRCH
+ end
+ end
+
+ # Throttle the master from forking too quickly by sleeping. Due
+ # to the implementation of standard Unix signal handlers, this
+ # helps (but does not completely) prevent identical, repeated signals
+ # from being lost when the receiving process is busy.
+ # sleep 1
+end
+
+after_fork do |server, worker|
+ # per-process listener ports for debugging/admin/migrations
+ # addr = "127.0.0.1:#{9293 + worker.nr}"
+ # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
+
+ # the following is *required* for Rails + "preload_app true",
+ defined?(ActiveRecord::Base) and
+ ActiveRecord::Base.establish_connection
+
+ # reset prometheus client, this will cause any opened metrics files to be closed
+ defined?(::Prometheus::Client.reinitialize_on_pid_change) &&
+ Prometheus::Client.reinitialize_on_pid_change
+
+ # if preload_app is true, then you may also want to check and
+ # restart any other shared sockets/descriptors such as Memcached,
+ # and Redis. TokyoCabinet file handles are safe to reuse
+ # between any number of forked children (assuming your kernel
+ # correctly implements pread()/pwrite() system calls)
+end
diff --git a/nixpkgs/nixos/modules/services/misc/devmon.nix b/nixpkgs/nixos/modules/services/misc/devmon.nix
new file mode 100644
index 00000000000..9dc8fee2964
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/devmon.nix
@@ -0,0 +1,30 @@
+{ pkgs, config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.devmon;
+
+in {
+ options = {
+ services.devmon = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable devmon, an automatic device mounting daemon.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.devmon = {
+ description = "devmon automatic device mounting daemon";
+ wantedBy = [ "default.target" ];
+ path = [ pkgs.udevil pkgs.procps pkgs.udisks2 pkgs.which ];
+ serviceConfig.ExecStart = "${pkgs.udevil}/bin/devmon";
+ };
+
+ services.udisks2.enable = true;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/dictd.nix b/nixpkgs/nixos/modules/services/misc/dictd.nix
new file mode 100644
index 00000000000..8d3e294622d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/dictd.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dictd;
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.dictd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the DICT.org dictionary server.
+ '';
+ };
+
+ DBs = mkOption {
+ type = types.listOf types.package;
+ default = with pkgs.dictdDBs; [ wiktionary wordnet ];
+ defaultText = "with pkgs.dictdDBs; [ wiktionary wordnet ]";
+ example = literalExample "[ pkgs.dictdDBs.nld2eng ]";
+ description = ''List of databases to make available.'';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = let dictdb = pkgs.dictDBCollector { dictlist = map (x: {
+ name = x.name;
+ filename = x; } ) cfg.DBs; };
+ in mkIf cfg.enable {
+
+ # get the command line client on system path to make some use of the service
+ environment.systemPackages = [ pkgs.dict ];
+
+ users.users = singleton
+ { name = "dictd";
+ group = "dictd";
+ description = "DICT.org dictd server";
+ home = "${dictdb}/share/dictd";
+ uid = config.ids.uids.dictd;
+ };
+
+ users.groups = singleton
+ { name = "dictd";
+ gid = config.ids.gids.dictd;
+ };
+
+ systemd.services.dictd = {
+ description = "DICT.org Dictionary Server";
+ wantedBy = [ "multi-user.target" ];
+ environment = { LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; };
+ serviceConfig.Type = "forking";
+ script = "${pkgs.dict}/sbin/dictd -s -c ${dictdb}/share/dictd/dictd.conf --locale en_US.UTF-8";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/disnix.nix b/nixpkgs/nixos/modules/services/misc/disnix.nix
new file mode 100644
index 00000000000..c21cb2afc3c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/disnix.nix
@@ -0,0 +1,97 @@
+# Disnix server
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.disnix;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.disnix = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable Disnix";
+ };
+
+ enableMultiUser = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to support multi-user mode by enabling the Disnix D-Bus service";
+ };
+
+ useWebServiceInterface = mkOption {
+ default = false;
+ description = "Whether to enable the DisnixWebService interface running on Apache Tomcat";
+ };
+
+ package = mkOption {
+ type = types.path;
+ description = "The Disnix package";
+ default = pkgs.disnix;
+ defaultText = "pkgs.disnix";
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ dysnomia.enable = true;
+
+ environment.systemPackages = [ pkgs.disnix ] ++ optional cfg.useWebServiceInterface pkgs.DisnixWebService;
+
+ services.dbus.enable = true;
+ services.dbus.packages = [ pkgs.disnix ];
+
+ services.tomcat.enable = cfg.useWebServiceInterface;
+ services.tomcat.extraGroups = [ "disnix" ];
+ services.tomcat.javaOpts = "${optionalString cfg.useWebServiceInterface "-Djava.library.path=${pkgs.libmatthew_java}/lib/jni"} ";
+ services.tomcat.sharedLibs = optional cfg.useWebServiceInterface "${pkgs.DisnixWebService}/share/java/DisnixConnection.jar"
+ ++ optional cfg.useWebServiceInterface "${pkgs.dbus_java}/share/java/dbus.jar";
+ services.tomcat.webapps = optional cfg.useWebServiceInterface pkgs.DisnixWebService;
+
+ users.groups = singleton
+ { name = "disnix";
+ gid = config.ids.gids.disnix;
+ };
+
+ systemd.services = {
+ disnix = mkIf cfg.enableMultiUser {
+ description = "Disnix server";
+ wants = [ "dysnomia.target" ];
+ wantedBy = [ "multi-user.target" ];
+ after = [ "dbus.service" ]
+ ++ optional config.services.httpd.enable "httpd.service"
+ ++ optional config.services.mysql.enable "mysql.service"
+ ++ optional config.services.postgresql.enable "postgresql.service"
+ ++ optional config.services.tomcat.enable "tomcat.service"
+ ++ optional config.services.svnserve.enable "svnserve.service"
+ ++ optional config.services.mongodb.enable "mongodb.service";
+
+ restartIfChanged = false;
+
+ path = [ config.nix.package cfg.package config.dysnomia.package "/run/current-system/sw" ];
+
+ environment = {
+ HOME = "/root";
+ }
+ // (if config.environment.variables ? DYSNOMIA_CONTAINERS_PATH then { inherit (config.environment.variables) DYSNOMIA_CONTAINERS_PATH; } else {})
+ // (if config.environment.variables ? DYSNOMIA_MODULES_PATH then { inherit (config.environment.variables) DYSNOMIA_MODULES_PATH; } else {});
+
+ serviceConfig.ExecStart = "${cfg.package}/bin/disnix-service";
+ };
+
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/docker-registry.nix b/nixpkgs/nixos/modules/services/misc/docker-registry.nix
new file mode 100644
index 00000000000..c87607d2666
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/docker-registry.nix
@@ -0,0 +1,155 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dockerRegistry;
+
+ blobCache = if cfg.enableRedisCache
+ then "redis"
+ else "inmemory";
+
+ registryConfig = {
+ version = "0.1";
+ log.fields.service = "registry";
+ storage = {
+ cache.blobdescriptor = blobCache;
+ delete.enabled = cfg.enableDelete;
+ } // (if cfg.storagePath != null
+ then { filesystem.rootdirectory = cfg.storagePath; }
+ else {});
+ http = {
+ addr = "${cfg.listenAddress}:${builtins.toString cfg.port}";
+ headers.X-Content-Type-Options = ["nosniff"];
+ };
+ health.storagedriver = {
+ enabled = true;
+ interval = "10s";
+ threshold = 3;
+ };
+ };
+
+ registryConfig.redis = mkIf cfg.enableRedisCache {
+ addr = "${cfg.redisUrl}";
+ password = "${cfg.redisPassword}";
+ db = 0;
+ dialtimeout = "10ms";
+ readtimeout = "10ms";
+ writetimeout = "10ms";
+ pool = {
+ maxidle = 16;
+ maxactive = 64;
+ idletimeout = "300s";
+ };
+ };
+
+ configFile = pkgs.writeText "docker-registry-config.yml" (builtins.toJSON (recursiveUpdate registryConfig cfg.extraConfig));
+
+in {
+ options.services.dockerRegistry = {
+ enable = mkEnableOption "Docker Registry";
+
+ listenAddress = mkOption {
+ description = "Docker registry host or ip to bind to.";
+ default = "127.0.0.1";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Docker registry port to bind to.";
+ default = 5000;
+ type = types.int;
+ };
+
+ storagePath = mkOption {
+ type = types.nullOr types.path;
+ default = "/var/lib/docker-registry";
+ description = ''
+ Docker registry storage path for the filesystem storage backend. Set to
+ null to configure another backend via extraConfig.
+ '';
+ };
+
+ enableDelete = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable delete for manifests and blobs.";
+ };
+
+ enableRedisCache = mkEnableOption "redis as blob cache";
+
+ redisUrl = mkOption {
+ type = types.str;
+ default = "localhost:6379";
+ description = "Set redis host and port.";
+ };
+
+ redisPassword = mkOption {
+ type = types.str;
+ default = "";
+ description = "Set redis password.";
+ };
+
+ extraConfig = mkOption {
+ description = ''
+ Docker extra registry configuration via environment variables.
+ '';
+ default = {};
+ type = types.attrs;
+ };
+
+ enableGarbageCollect = mkEnableOption "garbage collect";
+
+ garbageCollectDates = mkOption {
+ default = "daily";
+ type = types.str;
+ description = ''
+ Specification (in the format described by
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>) of the time at
+ which the garbage collect will occur.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.docker-registry = {
+ description = "Docker Container Registry";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ script = ''
+ ${pkgs.docker-distribution}/bin/registry serve ${configFile}
+ '';
+
+ serviceConfig = {
+ User = "docker-registry";
+ WorkingDirectory = cfg.storagePath;
+ AmbientCapabilities = mkIf (cfg.port < 1024) "cap_net_bind_service";
+ };
+ };
+
+ systemd.services.docker-registry-garbage-collect = {
+ description = "Run Garbage Collection for docker registry";
+
+ restartIfChanged = false;
+ unitConfig.X-StopOnRemoval = false;
+
+ serviceConfig.Type = "oneshot";
+
+ script = ''
+ ${pkgs.docker-distribution}/bin/registry garbage-collect ${configFile}
+ ${pkgs.systemd}/bin/systemctl restart docker-registry.service
+ '';
+
+ startAt = optional cfg.enableGarbageCollect cfg.garbageCollectDates;
+ };
+
+ users.users.docker-registry =
+ if cfg.storagePath != null
+ then {
+ createHome = true;
+ home = cfg.storagePath;
+ }
+ else {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/dwm-status.nix b/nixpkgs/nixos/modules/services/misc/dwm-status.nix
new file mode 100644
index 00000000000..b98a42e6a6d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/dwm-status.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dwm-status;
+
+ order = concatMapStringsSep "," (feature: ''"${feature}"'') cfg.order;
+
+ configFile = pkgs.writeText "dwm-status.toml" ''
+ order = [${order}]
+
+ ${cfg.extraConfig}
+ '';
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.dwm-status = {
+
+ enable = mkEnableOption "dwm-status user service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.dwm-status;
+ defaultText = "pkgs.dwm-status";
+ example = "pkgs.dwm-status.override { enableAlsaUtils = false; }";
+ description = ''
+ Which dwm-status package to use.
+ '';
+ };
+
+ order = mkOption {
+ type = types.listOf (types.enum [ "audio" "backlight" "battery" "cpu_load" "network" "time" ]);
+ description = ''
+ List of enabled features in order.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra config in TOML format.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.upower.enable = elem "battery" cfg.order;
+
+ systemd.user.services.dwm-status = {
+ description = "Highly performant and configurable DWM status service";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+
+ serviceConfig.ExecStart = "${cfg.package}/bin/dwm-status ${configFile}";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/dysnomia.nix b/nixpkgs/nixos/modules/services/misc/dysnomia.nix
new file mode 100644
index 00000000000..33a6fb15264
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/dysnomia.nix
@@ -0,0 +1,217 @@
+{pkgs, lib, config, ...}:
+
+with lib;
+
+let
+ cfg = config.dysnomia;
+
+ printProperties = properties:
+ concatMapStrings (propertyName:
+ let
+ property = properties.${propertyName};
+ in
+ if isList property then "${propertyName}=(${lib.concatMapStrings (elem: "\"${toString elem}\" ") (properties.${propertyName})})\n"
+ else "${propertyName}=\"${toString property}\"\n"
+ ) (builtins.attrNames properties);
+
+ properties = pkgs.stdenv.mkDerivation {
+ name = "dysnomia-properties";
+ buildCommand = ''
+ cat > $out << "EOF"
+ ${printProperties cfg.properties}
+ EOF
+ '';
+ };
+
+ containersDir = pkgs.stdenv.mkDerivation {
+ name = "dysnomia-containers";
+ buildCommand = ''
+ mkdir -p $out
+ cd $out
+
+ ${concatMapStrings (containerName:
+ let
+ containerProperties = cfg.containers.${containerName};
+ in
+ ''
+ cat > ${containerName} <<EOF
+ ${printProperties containerProperties}
+ type=${containerName}
+ EOF
+ ''
+ ) (builtins.attrNames cfg.containers)}
+ '';
+ };
+
+ linkMutableComponents = {containerName}:
+ ''
+ mkdir ${containerName}
+
+ ${concatMapStrings (componentName:
+ let
+ component = cfg.components.${containerName}.${componentName};
+ in
+ "ln -s ${component} ${containerName}/${componentName}\n"
+ ) (builtins.attrNames (cfg.components.${containerName} or {}))}
+ '';
+
+ componentsDir = pkgs.stdenv.mkDerivation {
+ name = "dysnomia-components";
+ buildCommand = ''
+ mkdir -p $out
+ cd $out
+
+ ${concatMapStrings (containerName:
+ linkMutableComponents { inherit containerName; }
+ ) (builtins.attrNames cfg.components)}
+ '';
+ };
+in
+{
+ options = {
+ dysnomia = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable Dysnomia";
+ };
+
+ enableAuthentication = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to publish privacy-sensitive authentication credentials";
+ };
+
+ package = mkOption {
+ type = types.path;
+ description = "The Dysnomia package";
+ };
+
+ properties = mkOption {
+ description = "An attribute set in which each attribute represents a machine property. Optionally, these values can be shell substitutions.";
+ default = {};
+ };
+
+ containers = mkOption {
+ description = "An attribute set in which each key represents a container and each value an attribute set providing its configuration properties";
+ default = {};
+ };
+
+ components = mkOption {
+ description = "An atttribute set in which each key represents a container and each value an attribute set in which each key represents a component and each value a derivation constructing its initial state";
+ default = {};
+ };
+
+ extraContainerProperties = mkOption {
+ description = "An attribute set providing additional container settings in addition to the default properties";
+ default = {};
+ };
+
+ extraContainerPaths = mkOption {
+ description = "A list of paths containing additional container configurations that are added to the search folders";
+ default = [];
+ };
+
+ extraModulePaths = mkOption {
+ description = "A list of paths containing additional modules that are added to the search folders";
+ default = [];
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.etc = {
+ "dysnomia/containers" = {
+ source = containersDir;
+ };
+ "dysnomia/components" = {
+ source = componentsDir;
+ };
+ "dysnomia/properties" = {
+ source = properties;
+ };
+ };
+
+ environment.variables = {
+ DYSNOMIA_STATEDIR = "/var/state/dysnomia-nixos";
+ DYSNOMIA_CONTAINERS_PATH = "${lib.concatMapStrings (containerPath: "${containerPath}:") cfg.extraContainerPaths}/etc/dysnomia/containers";
+ DYSNOMIA_MODULES_PATH = "${lib.concatMapStrings (modulePath: "${modulePath}:") cfg.extraModulePaths}/etc/dysnomia/modules";
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ dysnomia.package = pkgs.dysnomia.override (origArgs: {
+ enableApacheWebApplication = config.services.httpd.enable;
+ enableAxis2WebService = config.services.tomcat.axis2.enable;
+ enableEjabberdDump = config.services.ejabberd.enable;
+ enableMySQLDatabase = config.services.mysql.enable;
+ enablePostgreSQLDatabase = config.services.postgresql.enable;
+ enableSubversionRepository = config.services.svnserve.enable;
+ enableTomcatWebApplication = config.services.tomcat.enable;
+ enableMongoDatabase = config.services.mongodb.enable;
+ enableInfluxDatabase = config.services.influxdb.enable;
+ });
+
+ dysnomia.properties = {
+ hostname = config.networking.hostName;
+ inherit (config.nixpkgs.localSystem) system;
+
+ supportedTypes = (import "${pkgs.stdenv.mkDerivation {
+ name = "supportedtypes";
+ buildCommand = ''
+ ( echo -n "[ "
+ cd ${cfg.package}/libexec/dysnomia
+ for i in *
+ do
+ echo -n "\"$i\" "
+ done
+ echo -n " ]") > $out
+ '';
+ }}");
+ };
+
+ dysnomia.containers = lib.recursiveUpdate ({
+ process = {};
+ wrapper = {};
+ }
+ // lib.optionalAttrs (config.services.httpd.enable) { apache-webapplication = {
+ documentRoot = config.services.httpd.documentRoot;
+ }; }
+ // lib.optionalAttrs (config.services.tomcat.axis2.enable) { axis2-webservice = {}; }
+ // lib.optionalAttrs (config.services.ejabberd.enable) { ejabberd-dump = {
+ ejabberdUser = config.services.ejabberd.user;
+ }; }
+ // lib.optionalAttrs (config.services.mysql.enable) { mysql-database = {
+ mysqlPort = config.services.mysql.port;
+ } // lib.optionalAttrs cfg.enableAuthentication {
+ mysqlUsername = "root";
+ mysqlPassword = builtins.readFile (config.services.mysql.rootPassword);
+ };
+ }
+ // lib.optionalAttrs (config.services.postgresql.enable) { postgresql-database = {
+ } // lib.optionalAttrs (cfg.enableAuthentication) {
+ postgresqlUsername = "postgres";
+ };
+ }
+ // lib.optionalAttrs (config.services.tomcat.enable) { tomcat-webapplication = {
+ tomcatPort = 8080;
+ }; }
+ // lib.optionalAttrs (config.services.mongodb.enable) { mongo-database = {}; }
+ // lib.optionalAttrs (config.services.svnserve.enable) { subversion-repository = {
+ svnBaseDir = config.services.svnserve.svnBaseDir;
+ }; }) cfg.extraContainerProperties;
+
+ system.activationScripts.dysnomia = ''
+ mkdir -p /etc/systemd-mutable/system
+ if [ ! -f /etc/systemd-mutable/system/dysnomia.target ]
+ then
+ ( echo "[Unit]"
+ echo "Description=Services that are activated and deactivated by Dysnomia"
+ echo "After=final.target"
+ ) > /etc/systemd-mutable/system/dysnomia.target
+ fi
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/errbot.nix b/nixpkgs/nixos/modules/services/misc/errbot.nix
new file mode 100644
index 00000000000..256adce2f02
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/errbot.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.errbot;
+ pluginEnv = plugins: pkgs.buildEnv {
+ name = "errbot-plugins";
+ paths = plugins;
+ };
+ mkConfigDir = instanceCfg: dataDir: pkgs.writeTextDir "config.py" ''
+ import logging
+ BACKEND = '${instanceCfg.backend}'
+ BOT_DATA_DIR = '${dataDir}'
+ BOT_EXTRA_PLUGIN_DIR = '${pluginEnv instanceCfg.plugins}'
+
+ BOT_LOG_LEVEL = logging.${instanceCfg.logLevel}
+ BOT_LOG_FILE = False
+
+ BOT_ADMINS = (${concatMapStringsSep "," (name: "'${name}'") instanceCfg.admins})
+
+ BOT_IDENTITY = ${builtins.toJSON instanceCfg.identity}
+
+ ${instanceCfg.extraConfig}
+ '';
+in {
+ options = {
+ services.errbot.instances = mkOption {
+ default = {};
+ description = "Errbot instance configs";
+ type = types.attrsOf (types.submodule {
+ options = {
+ dataDir = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "Data directory for errbot instance.";
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = "List of errbot plugin derivations.";
+ };
+
+ logLevel = mkOption {
+ type = types.str;
+ default = "INFO";
+ description = "Errbot log level";
+ };
+
+ admins = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "List of identifiers of errbot admins.";
+ };
+
+ backend = mkOption {
+ type = types.str;
+ default = "XMPP";
+ description = "Errbot backend name.";
+ };
+
+ identity = mkOption {
+ type = types.attrs;
+ description = "Errbot identity configuration";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "String to be appended to the config verbatim";
+ };
+ };
+ });
+ };
+ };
+
+ config = mkIf (cfg.instances != {}) {
+ users.users.errbot.group = "errbot";
+ users.groups.errbot = {};
+
+ systemd.services = mapAttrs' (name: instanceCfg: nameValuePair "errbot-${name}" (
+ let
+ dataDir = if instanceCfg.dataDir != null then instanceCfg.dataDir else
+ "/var/lib/errbot/${name}";
+ in {
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -p ${dataDir}
+ chown -R errbot:errbot ${dataDir}
+ '';
+ serviceConfig = {
+ User = "errbot";
+ Restart = "on-failure";
+ ExecStart = "${pkgs.errbot}/bin/errbot -c ${mkConfigDir instanceCfg dataDir}/config.py";
+ PermissionsStartOnly = true;
+ };
+ })) cfg.instances;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/etcd.nix b/nixpkgs/nixos/modules/services/misc/etcd.nix
new file mode 100644
index 00000000000..e4d5322f9b5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/etcd.nix
@@ -0,0 +1,196 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.etcd;
+
+in {
+
+ options.services.etcd = {
+ enable = mkOption {
+ description = "Whether to enable etcd.";
+ default = false;
+ type = types.bool;
+ };
+
+ name = mkOption {
+ description = "Etcd unique node name.";
+ default = config.networking.hostName;
+ type = types.str;
+ };
+
+ advertiseClientUrls = mkOption {
+ description = "Etcd list of this member's client URLs to advertise to the rest of the cluster.";
+ default = cfg.listenClientUrls;
+ type = types.listOf types.str;
+ };
+
+ listenClientUrls = mkOption {
+ description = "Etcd list of URLs to listen on for client traffic.";
+ default = ["http://127.0.0.1:2379"];
+ type = types.listOf types.str;
+ };
+
+ listenPeerUrls = mkOption {
+ description = "Etcd list of URLs to listen on for peer traffic.";
+ default = ["http://127.0.0.1:2380"];
+ type = types.listOf types.str;
+ };
+
+ initialAdvertisePeerUrls = mkOption {
+ description = "Etcd list of this member's peer URLs to advertise to rest of the cluster.";
+ default = cfg.listenPeerUrls;
+ type = types.listOf types.str;
+ };
+
+ initialCluster = mkOption {
+ description = "Etcd initial cluster configuration for bootstrapping.";
+ default = ["${cfg.name}=http://127.0.0.1:2380"];
+ type = types.listOf types.str;
+ };
+
+ initialClusterState = mkOption {
+ description = "Etcd initial cluster configuration for bootstrapping.";
+ default = "new";
+ type = types.enum ["new" "existing"];
+ };
+
+ initialClusterToken = mkOption {
+ description = "Etcd initial cluster token for etcd cluster during bootstrap.";
+ default = "etcd-cluster";
+ type = types.str;
+ };
+
+ discovery = mkOption {
+ description = "Etcd discovery url";
+ default = "";
+ type = types.str;
+ };
+
+ clientCertAuth = mkOption {
+ description = "Whether to use certs for client authentication";
+ default = false;
+ type = types.bool;
+ };
+
+ trustedCaFile = mkOption {
+ description = "Certificate authority file to use for clients";
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ certFile = mkOption {
+ description = "Cert file to use for clients";
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ keyFile = mkOption {
+ description = "Key file to use for clients";
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ peerCertFile = mkOption {
+ description = "Cert file to use for peer to peer communication";
+ default = cfg.certFile;
+ type = types.nullOr types.path;
+ };
+
+ peerKeyFile = mkOption {
+ description = "Key file to use for peer to peer communication";
+ default = cfg.keyFile;
+ type = types.nullOr types.path;
+ };
+
+ peerTrustedCaFile = mkOption {
+ description = "Certificate authority file to use for peer to peer communication";
+ default = cfg.trustedCaFile;
+ type = types.nullOr types.path;
+ };
+
+ peerClientCertAuth = mkOption {
+ description = "Whether to check all incoming peer requests from the cluster for valid client certificates signed by the supplied CA";
+ default = false;
+ type = types.bool;
+ };
+
+ extraConf = mkOption {
+ description = ''
+ Etcd extra configuration. See
+ <link xlink:href='https://github.com/coreos/etcd/blob/master/Documentation/op-guide/configuration.md#configuration-flags' />
+ '';
+ type = types.attrsOf types.str;
+ default = {};
+ example = literalExample ''
+ {
+ "CORS" = "*";
+ "NAME" = "default-name";
+ "MAX_RESULT_BUFFER" = "1024";
+ "MAX_CLUSTER_SIZE" = "9";
+ "MAX_RETRY_ATTEMPTS" = "3";
+ }
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/etcd";
+ description = "Etcd data directory.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0700 etcd - - -"
+ ];
+
+ systemd.services.etcd = {
+ description = "etcd key-value store";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ environment = (filterAttrs (n: v: v != null) {
+ ETCD_NAME = cfg.name;
+ ETCD_DISCOVERY = cfg.discovery;
+ ETCD_DATA_DIR = cfg.dataDir;
+ ETCD_ADVERTISE_CLIENT_URLS = concatStringsSep "," cfg.advertiseClientUrls;
+ ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls;
+ ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls;
+ ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls;
+ ETCD_PEER_TRUSTED_CA_FILE = cfg.peerTrustedCaFile;
+ ETCD_PEER_CERT_FILE = cfg.peerCertFile;
+ ETCD_PEER_KEY_FILE = cfg.peerKeyFile;
+ ETCD_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth;
+ ETCD_TRUSTED_CA_FILE = cfg.trustedCaFile;
+ ETCD_CERT_FILE = cfg.certFile;
+ ETCD_KEY_FILE = cfg.keyFile;
+ }) // (optionalAttrs (cfg.discovery == ""){
+ ETCD_INITIAL_CLUSTER = concatStringsSep "," cfg.initialCluster;
+ ETCD_INITIAL_CLUSTER_STATE = cfg.initialClusterState;
+ ETCD_INITIAL_CLUSTER_TOKEN = cfg.initialClusterToken;
+ }) // (mapAttrs' (n: v: nameValuePair "ETCD_${n}" v) cfg.extraConf);
+
+ unitConfig = {
+ Documentation = "https://github.com/coreos/etcd";
+ };
+
+ serviceConfig = {
+ Type = "notify";
+ ExecStart = "${pkgs.etcd.bin}/bin/etcd";
+ User = "etcd";
+ LimitNOFILE = 40000;
+ };
+ };
+
+ environment.systemPackages = [ pkgs.etcdctl ];
+
+ users.users = singleton {
+ name = "etcd";
+ uid = config.ids.uids.etcd;
+ description = "Etcd daemon user";
+ home = cfg.dataDir;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/ethminer.nix b/nixpkgs/nixos/modules/services/misc/ethminer.nix
new file mode 100644
index 00000000000..2958cf21447
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/ethminer.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.ethminer;
+ poolUrl = escapeShellArg "stratum1+tcp://${cfg.wallet}@${cfg.pool}:${toString cfg.stratumPort}/${cfg.rig}/${cfg.registerMail}";
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.ethminer = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable ethminer ether mining.";
+ };
+
+ recheckInterval = mkOption {
+ type = types.int;
+ default = 2000;
+ description = "Interval in milliseconds between farm rechecks.";
+ };
+
+ toolkit = mkOption {
+ type = types.enum [ "cuda" "opencl" ];
+ default = "cuda";
+ description = "Cuda or opencl toolkit.";
+ };
+
+ apiPort = mkOption {
+ type = types.int;
+ default = -3333;
+ description = "Ethminer api port. minus sign puts api in read-only mode.";
+ };
+
+ wallet = mkOption {
+ type = types.str;
+ example = "0x0123456789abcdef0123456789abcdef01234567";
+ description = "Ethereum wallet address.";
+ };
+
+ pool = mkOption {
+ type = types.str;
+ example = "eth-us-east1.nanopool.org";
+ description = "Mining pool address.";
+ };
+
+ stratumPort = mkOption {
+ type = types.port;
+ default = 9999;
+ description = "Stratum protocol tcp port.";
+ };
+
+ rig = mkOption {
+ type = types.str;
+ default = "mining-rig-name";
+ description = "Mining rig name.";
+ };
+
+ registerMail = mkOption {
+ type = types.str;
+ example = "email%40example.org";
+ description = "Url encoded email address to register with pool.";
+ };
+
+ maxPower = mkOption {
+ type = types.int;
+ default = 115;
+ description = "Miner max watt usage.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.ethminer = {
+ path = [ pkgs.cudatoolkit ];
+ description = "ethminer ethereum mining service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ DynamicUser = true;
+ ExecStartPost = optional (cfg.toolkit == "cuda") "+${getBin config.boot.kernelPackages.nvidia_x11}/bin/nvidia-smi -pl ${toString cfg.maxPower}";
+ };
+
+ environment = {
+ LD_LIBRARY_PATH = "${config.boot.kernelPackages.nvidia_x11}/lib";
+ };
+
+ script = ''
+ ${pkgs.ethminer}/bin/.ethminer-wrapped \
+ --farm-recheck ${toString cfg.recheckInterval} \
+ --report-hashrate \
+ --${cfg.toolkit} \
+ --api-port ${toString cfg.apiPort} \
+ --pool ${poolUrl}
+ '';
+
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/exhibitor.nix b/nixpkgs/nixos/modules/services/misc/exhibitor.nix
new file mode 100644
index 00000000000..74f4f671f46
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/exhibitor.nix
@@ -0,0 +1,420 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.exhibitor;
+ exhibitorConfig = ''
+ zookeeper-install-directory=${cfg.baseDir}/zookeeper
+ zookeeper-data-directory=${cfg.zkDataDir}
+ zookeeper-log-directory=${cfg.zkLogDir}
+ zoo-cfg-extra=${cfg.zkExtraCfg}
+ client-port=${toString cfg.zkClientPort}
+ connect-port=${toString cfg.zkConnectPort}
+ election-port=${toString cfg.zkElectionPort}
+ cleanup-period-ms=${toString cfg.zkCleanupPeriod}
+ servers-spec=${concatStringsSep "," cfg.zkServersSpec}
+ auto-manage-instances=${toString cfg.autoManageInstances}
+ ${cfg.extraConf}
+ '';
+ # NB: toString rather than lib.boolToString on cfg.autoManageInstances is intended.
+ # Exhibitor tests if it's an integer not equal to 0, so the empty string (toString false)
+ # will operate in the same fashion as a 0.
+ configDir = pkgs.writeTextDir "exhibitor.properties" exhibitorConfig;
+ cliOptionsCommon = {
+ configtype = cfg.configType;
+ defaultconfig = "${configDir}/exhibitor.properties";
+ port = toString cfg.port;
+ hostname = cfg.hostname;
+ headingtext = if (cfg.headingText != null) then (lib.escapeShellArg cfg.headingText) else null;
+ nodemodification = lib.boolToString cfg.nodeModification;
+ configcheckms = toString cfg.configCheckMs;
+ jquerystyle = cfg.jqueryStyle;
+ loglines = toString cfg.logLines;
+ servo = lib.boolToString cfg.servo;
+ timeout = toString cfg.timeout;
+ };
+ s3CommonOptions = { s3region = cfg.s3Region; s3credentials = cfg.s3Credentials; };
+ cliOptionsPerConfig = {
+ s3 = {
+ s3config = "${cfg.s3Config.bucketName}:${cfg.s3Config.objectKey}";
+ s3configprefix = cfg.s3Config.configPrefix;
+ };
+ zookeeper = {
+ zkconfigconnect = concatStringsSep "," cfg.zkConfigConnect;
+ zkconfigexhibitorpath = cfg.zkConfigExhibitorPath;
+ zkconfigpollms = toString cfg.zkConfigPollMs;
+ zkconfigretry = "${toString cfg.zkConfigRetry.sleepMs}:${toString cfg.zkConfigRetry.retryQuantity}";
+ zkconfigzpath = cfg.zkConfigZPath;
+ zkconfigexhibitorport = toString cfg.zkConfigExhibitorPort; # NB: This might be null
+ };
+ file = {
+ fsconfigdir = cfg.fsConfigDir;
+ fsconfiglockprefix = cfg.fsConfigLockPrefix;
+ fsConfigName = fsConfigName;
+ };
+ none = {
+ noneconfigdir = configDir;
+ };
+ };
+ cliOptions = concatStringsSep " " (mapAttrsToList (k: v: "--${k} ${v}") (filterAttrs (k: v: v != null && v != "") (cliOptionsCommon //
+ cliOptionsPerConfig.${cfg.configType} //
+ s3CommonOptions //
+ optionalAttrs cfg.s3Backup { s3backup = "true"; } //
+ optionalAttrs cfg.fileSystemBackup { filesystembackup = "true"; }
+ )));
+in
+{
+ options = {
+ services.exhibitor = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "
+ Whether to enable the exhibitor server.
+ ";
+ };
+ # See https://github.com/soabase/exhibitor/wiki/Running-Exhibitor for what these mean
+ # General options for any type of config
+ port = mkOption {
+ type = types.int;
+ default = 8080;
+ description = ''
+ The port for exhibitor to listen on and communicate with other exhibitors.
+ '';
+ };
+ baseDir = mkOption {
+ type = types.str;
+ default = "/var/exhibitor";
+ description = ''
+ Baseline directory for exhibitor runtime config.
+ '';
+ };
+ configType = mkOption {
+ type = types.enum [ "file" "s3" "zookeeper" "none" ];
+ description = ''
+ Which configuration type you want to use. Additional config will be
+ required depending on which type you are using.
+ '';
+ };
+ hostname = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Hostname to use and advertise
+ '';
+ default = null;
+ };
+ nodeModification = mkOption {
+ type = types.bool;
+ description = ''
+ Whether the Explorer UI will allow nodes to be modified (use with caution).
+ '';
+ default = true;
+ };
+ configCheckMs = mkOption {
+ type = types.int;
+ description = ''
+ Period (ms) to check for shared config updates.
+ '';
+ default = 30000;
+ };
+ headingText = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Extra text to display in UI header
+ '';
+ default = null;
+ };
+ jqueryStyle = mkOption {
+ type = types.enum [ "red" "black" "custom" ];
+ description = ''
+ Styling used for the JQuery-based UI.
+ '';
+ default = "red";
+ };
+ logLines = mkOption {
+ type = types.int;
+ description = ''
+ Max lines of logging to keep in memory for display.
+ '';
+ default = 1000;
+ };
+ servo = mkOption {
+ type = types.bool;
+ description = ''
+ ZooKeeper will be queried once a minute for its state via the 'mntr' four
+ letter word (this requires ZooKeeper 3.4.x+). Servo will be used to publish
+ this data via JMX.
+ '';
+ default = false;
+ };
+ timeout = mkOption {
+ type = types.int;
+ description = ''
+ Connection timeout (ms) for ZK connections.
+ '';
+ default = 30000;
+ };
+ autoManageInstances = mkOption {
+ type = types.bool;
+ description = ''
+ Automatically manage ZooKeeper instances in the ensemble
+ '';
+ default = false;
+ };
+ zkDataDir = mkOption {
+ type = types.str;
+ default = "${cfg.baseDir}/zkData";
+ description = ''
+ The Zookeeper data directory
+ '';
+ };
+ zkLogDir = mkOption {
+ type = types.path;
+ default = "${cfg.baseDir}/zkLogs";
+ description = ''
+ The Zookeeper logs directory
+ '';
+ };
+ extraConf = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Extra Exhibitor configuration to put in the ZooKeeper config file.
+ '';
+ };
+ zkExtraCfg = mkOption {
+ type = types.str;
+ default = ''initLimit=5&syncLimit=2&tickTime=2000'';
+ description = ''
+ Extra options to pass into Zookeeper
+ '';
+ };
+ zkClientPort = mkOption {
+ type = types.int;
+ default = 2181;
+ description = ''
+ Zookeeper client port
+ '';
+ };
+ zkConnectPort = mkOption {
+ type = types.int;
+ default = 2888;
+ description = ''
+ The port to use for followers to talk to each other.
+ '';
+ };
+ zkElectionPort = mkOption {
+ type = types.int;
+ default = 3888;
+ description = ''
+ The port for Zookeepers to use for leader election.
+ '';
+ };
+ zkCleanupPeriod = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ How often (in milliseconds) to run the Zookeeper log cleanup task.
+ '';
+ };
+ zkServersSpec = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Zookeeper server spec for all servers in the ensemble.
+ '';
+ example = [ "S:1:zk1.example.com" "S:2:zk2.example.com" "S:3:zk3.example.com" "O:4:zk-observer.example.com" ];
+ };
+
+ # Backup options
+ s3Backup = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable backups to S3
+ '';
+ };
+ fileSystemBackup = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables file system backup of ZooKeeper log files
+ '';
+ };
+
+ # Options for using zookeeper configType
+ zkConfigConnect = mkOption {
+ type = types.listOf types.str;
+ description = ''
+ The initial connection string for ZooKeeper shared config storage
+ '';
+ example = ["host1:2181" "host2:2181"];
+ };
+ zkConfigExhibitorPath = mkOption {
+ type = types.str;
+ description = ''
+ If the ZooKeeper shared config is also running Exhibitor, the URI path for the REST call
+ '';
+ default = "/";
+ };
+ zkConfigExhibitorPort = mkOption {
+ type = types.nullOr types.int;
+ description = ''
+ If the ZooKeeper shared config is also running Exhibitor, the port that
+ Exhibitor is listening on. IMPORTANT: if this value is not set it implies
+ that Exhibitor is not being used on the ZooKeeper shared config.
+ '';
+ };
+ zkConfigPollMs = mkOption {
+ type = types.int;
+ description = ''
+ The period in ms to check for changes in the config ensemble
+ '';
+ default = 10000;
+ };
+ zkConfigRetry = {
+ sleepMs = mkOption {
+ type = types.int;
+ default = 1000;
+ description = ''
+ Retry sleep time connecting to the ZooKeeper config
+ '';
+ };
+ retryQuantity = mkOption {
+ type = types.int;
+ default = 3;
+ description = ''
+ Retries connecting to the ZooKeeper config
+ '';
+ };
+ };
+ zkConfigZPath = mkOption {
+ type = types.str;
+ description = ''
+ The base ZPath that Exhibitor should use
+ '';
+ example = "/exhibitor/config";
+ };
+
+ # Config options for s3 configType
+ s3Config = {
+ bucketName = mkOption {
+ type = types.str;
+ description = ''
+ Bucket name to store config
+ '';
+ };
+ objectKey = mkOption {
+ type = types.str;
+ description = ''
+ S3 key name to store the config
+ '';
+ };
+ configPrefix = mkOption {
+ type = types.str;
+ description = ''
+ When using AWS S3 shared config files, the prefix to use for values such as locks
+ '';
+ default = "exhibitor-";
+ };
+ };
+
+ # The next two are used for either s3backup or s3 configType
+ s3Credentials = mkOption {
+ type = types.nullOr types.path;
+ description = ''
+ Optional credentials to use for s3backup or s3config. Argument is the path
+ to an AWS credential properties file with two properties:
+ com.netflix.exhibitor.s3.access-key-id and com.netflix.exhibitor.s3.access-secret-key
+ '';
+ default = null;
+ };
+ s3Region = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Optional region for S3 calls
+ '';
+ default = null;
+ };
+
+ # Config options for file config type
+ fsConfigDir = mkOption {
+ type = types.path;
+ description = ''
+ Directory to store Exhibitor properties (cannot be used with s3config).
+ Exhibitor uses file system locks so you can specify a shared location
+ so as to enable complete ensemble management.
+ '';
+ };
+ fsConfigLockPrefix = mkOption {
+ type = types.str;
+ description = ''
+ A prefix for a locking mechanism used in conjunction with fsconfigdir
+ '';
+ default = "exhibitor-lock-";
+ };
+ fsConfigName = mkOption {
+ type = types.str;
+ description = ''
+ The name of the file to store config in
+ '';
+ default = "exhibitor.properties";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.exhibitor = {
+ description = "Exhibitor Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment = {
+ ZOO_LOG_DIR = cfg.baseDir;
+ };
+ serviceConfig = {
+ /***
+ Exhibitor is a bit un-nixy. It wants to present to you a user interface in order to
+ mutate the configuration of both itself and ZooKeeper, and to coordinate changes
+ among the members of the Zookeeper ensemble. I'm going for a different approach here,
+ which is to manage all the configuration via nix and have it write out the configuration
+ files that exhibitor will use, and to reduce the amount of inter-exhibitor orchestration.
+ ***/
+ ExecStart = ''
+ ${pkgs.exhibitor}/bin/startExhibitor.sh ${cliOptions}
+ '';
+ User = "zookeeper";
+ PermissionsStartOnly = true;
+ };
+ # This is a bit wonky, but the reason for this is that Exhibitor tries to write to
+ # ${cfg.baseDir}/zookeeper/bin/../conf/zoo.cfg
+ # I want everything but the conf directory to be in the immutable nix store, and I want defaults
+ # from the nix store
+ # If I symlink the bin directory in, then bin/../ will resolve to the parent of the symlink in the
+ # immutable nix store. Bind mounting a writable conf over the existing conf might work, but it gets very
+ # messy with trying to copy the existing out into a mutable store.
+ # Another option is to try to patch upstream exhibitor, but the current package just pulls down the
+ # prebuild JARs off of Maven, rather than building them ourselves, as Maven support in Nix isn't
+ # very mature. So, it seems like a reasonable compromise is to just copy out of the immutable store
+ # just before starting the service, so we're running binaries from the immutable store, but we work around
+ # Exhibitor's desire to mutate its current installation.
+ preStart = ''
+ mkdir -m 0700 -p ${cfg.baseDir}/zookeeper
+ # Not doing a chown -R to keep the base ZK files owned by root
+ chown zookeeper ${cfg.baseDir} ${cfg.baseDir}/zookeeper
+ cp -Rf ${pkgs.zookeeper}/* ${cfg.baseDir}/zookeeper
+ chown -R zookeeper ${cfg.baseDir}/zookeeper/conf
+ chmod -R u+w ${cfg.baseDir}/zookeeper/conf
+ replace_what=$(echo ${pkgs.zookeeper} | sed 's/[\/&]/\\&/g')
+ replace_with=$(echo ${cfg.baseDir}/zookeeper | sed 's/[\/&]/\\&/g')
+ sed -i 's/'"$replace_what"'/'"$replace_with"'/g' ${cfg.baseDir}/zookeeper/bin/zk*.sh
+ '';
+ };
+ users.users = singleton {
+ name = "zookeeper";
+ uid = config.ids.uids.zookeeper;
+ description = "Zookeeper daemon user";
+ home = cfg.baseDir;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/felix.nix b/nixpkgs/nixos/modules/services/misc/felix.nix
new file mode 100644
index 00000000000..1c5ece86825
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/felix.nix
@@ -0,0 +1,109 @@
+# Felix server
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.felix;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.felix = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable the Apache Felix OSGi service";
+ };
+
+ bundles = mkOption {
+ type = types.listOf types.package;
+ default = [ pkgs.felix_remoteshell ];
+ defaultText = "[ pkgs.felix_remoteshell ]";
+ description = "List of bundles that should be activated on startup";
+ };
+
+ user = mkOption {
+ default = "osgi";
+ description = "User account under which Apache Felix runs.";
+ };
+
+ group = mkOption {
+ default = "osgi";
+ description = "Group account under which Apache Felix runs.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ users.groups = singleton
+ { name = "osgi";
+ gid = config.ids.gids.osgi;
+ };
+
+ users.users = singleton
+ { name = "osgi";
+ uid = config.ids.uids.osgi;
+ description = "OSGi user";
+ home = "/homeless-shelter";
+ };
+
+ systemd.services.felix = {
+ description = "Felix server";
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ # Initialise felix instance on first startup
+ if [ ! -d /var/felix ]
+ then
+ # Symlink system files
+
+ mkdir -p /var/felix
+ chown ${cfg.user}:${cfg.group} /var/felix
+
+ for i in ${pkgs.felix}/*
+ do
+ if [ "$i" != "${pkgs.felix}/bundle" ]
+ then
+ ln -sfn $i /var/felix/$(basename $i)
+ fi
+ done
+
+ # Symlink bundles
+ mkdir -p /var/felix/bundle
+ chown ${cfg.user}:${cfg.group} /var/felix/bundle
+
+ for i in ${pkgs.felix}/bundle/* ${toString cfg.bundles}
+ do
+ if [ -f $i ]
+ then
+ ln -sfn $i /var/felix/bundle/$(basename $i)
+ elif [ -d $i ]
+ then
+ for j in $i/bundle/*
+ do
+ ln -sfn $j /var/felix/bundle/$(basename $j)
+ done
+ fi
+ done
+ fi
+ '';
+
+ script = ''
+ cd /var/felix
+ ${pkgs.su}/bin/su -s ${pkgs.bash}/bin/sh ${cfg.user} -c '${pkgs.jre}/bin/java -jar bin/felix.jar'
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/folding-at-home.nix b/nixpkgs/nixos/modules/services/misc/folding-at-home.nix
new file mode 100644
index 00000000000..122c89ce068
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/folding-at-home.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ stateDir = "/var/lib/foldingathome";
+ cfg = config.services.foldingAtHome;
+ fahUser = "foldingathome";
+in {
+
+ ###### interface
+
+ options = {
+
+ services.foldingAtHome = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable the Folding@Home to use idle CPU time.
+ '';
+ };
+
+ nickname = mkOption {
+ default = "Anonymous";
+ description = ''
+ A unique handle for statistics.
+ '';
+ };
+
+ config = mkOption {
+ default = "";
+ description = ''
+ Extra configuration. Contents will be added verbatim to the
+ configuration file.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton
+ { name = fahUser;
+ uid = config.ids.uids.foldingathome;
+ description = "Folding@Home user";
+ home = stateDir;
+ };
+
+ systemd.services.foldingathome = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ chown ${fahUser} ${stateDir}
+ cp -f ${pkgs.writeText "client.cfg" cfg.config} ${stateDir}/client.cfg
+ '';
+ script = "${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${fahUser} -c 'cd ${stateDir}; ${pkgs.foldingathome}/bin/fah6'";
+ };
+
+ services.foldingAtHome.config = ''
+ [settings]
+ username=${cfg.nickname}
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/fstrim.nix b/nixpkgs/nixos/modules/services/misc/fstrim.nix
new file mode 100644
index 00000000000..b8841a7fe74
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/fstrim.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.fstrim;
+
+in {
+
+ options = {
+
+ services.fstrim = {
+ enable = mkEnableOption "periodic SSD TRIM of mounted partitions in background";
+
+ interval = mkOption {
+ type = types.str;
+ default = "weekly";
+ description = ''
+ How often we run fstrim. For most desktop and server systems
+ a sufficient trimming frequency is once a week.
+
+ The format is described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.packages = [ pkgs.utillinux ];
+
+ systemd.timers.fstrim = {
+ timerConfig = {
+ OnCalendar = cfg.interval;
+ };
+ wantedBy = [ "timers.target" ];
+ };
+
+ };
+
+ meta.maintainers = with maintainers; [ gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix b/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix
new file mode 100644
index 00000000000..3057d7fd1a0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix
@@ -0,0 +1,253 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+let
+ cfg = config.services.gammu-smsd;
+
+ configFile = pkgs.writeText "gammu-smsd.conf" ''
+ [gammu]
+ Device = ${cfg.device.path}
+ Connection = ${cfg.device.connection}
+ SynchronizeTime = ${if cfg.device.synchronizeTime then "yes" else "no"}
+ LogFormat = ${cfg.log.format}
+ ${if (cfg.device.pin != null) then "PIN = ${cfg.device.pin}" else ""}
+ ${cfg.extraConfig.gammu}
+
+
+ [smsd]
+ LogFile = ${cfg.log.file}
+ Service = ${cfg.backend.service}
+
+ ${optionalString (cfg.backend.service == "files") ''
+ InboxPath = ${cfg.backend.files.inboxPath}
+ OutboxPath = ${cfg.backend.files.outboxPath}
+ SentSMSPath = ${cfg.backend.files.sentSMSPath}
+ ErrorSMSPath = ${cfg.backend.files.errorSMSPath}
+ ''}
+
+ ${optionalString (cfg.backend.service == "sql" && cfg.backend.sql.driver == "sqlite") ''
+ Driver = ${cfg.backend.sql.driver}
+ DBDir = ${cfg.backend.sql.database}
+ ''}
+
+ ${optionalString (cfg.backend.service == "sql" && cfg.backend.sql.driver == "native_pgsql") (
+ with cfg.backend; ''
+ Driver = ${sql.driver}
+ ${if (sql.database!= null) then "Database = ${sql.database}" else ""}
+ ${if (sql.host != null) then "Host = ${sql.host}" else ""}
+ ${if (sql.user != null) then "User = ${sql.user}" else ""}
+ ${if (sql.password != null) then "Password = ${sql.password}" else ""}
+ '')}
+
+ ${cfg.extraConfig.smsd}
+ '';
+
+ initDBDir = "share/doc/gammu/examples/sql";
+
+ gammuPackage = with cfg.backend; (pkgs.gammu.override {
+ dbiSupport = (service == "sql" && sql.driver == "sqlite");
+ postgresSupport = (service == "sql" && sql.driver == "native_pgsql");
+ });
+
+in {
+ options = {
+ services.gammu-smsd = {
+
+ enable = mkEnableOption "gammu-smsd daemon";
+
+ user = mkOption {
+ type = types.str;
+ default = "smsd";
+ description = "User that has access to the device";
+ };
+
+ device = {
+ path = mkOption {
+ type = types.path;
+ description = "Device node or address of the phone";
+ example = "/dev/ttyUSB2";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "root";
+ description = "Owner group of the device";
+ example = "dialout";
+ };
+
+ connection = mkOption {
+ type = types.str;
+ default = "at";
+ description = "Protocol which will be used to talk to the phone";
+ };
+
+ synchronizeTime = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to set time from computer to the phone during starting connection";
+ };
+
+ pin = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "PIN code for the simcard";
+ };
+ };
+
+
+ log = {
+ file = mkOption {
+ type = types.str;
+ default = "syslog";
+ description = "Path to file where information about communication will be stored";
+ };
+
+ format = mkOption {
+ type = types.enum [ "nothing" "text" "textall" "textalldate" "errors" "errorsdate" "binary" ];
+ default = "errors";
+ description = "Determines what will be logged to the LogFile";
+ };
+ };
+
+
+ extraConfig = {
+ gammu = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra config lines to be added into [gammu] section";
+ };
+
+
+ smsd = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra config lines to be added into [smsd] section";
+ };
+ };
+
+
+ backend = {
+ service = mkOption {
+ type = types.enum [ "null" "files" "sql" ];
+ default = "null";
+ description = "Service to use to store sms data.";
+ };
+
+ files = {
+ inboxPath = mkOption {
+ type = types.path;
+ default = "/var/spool/sms/inbox/";
+ description = "Where the received SMSes are stored";
+ };
+
+ outboxPath = mkOption {
+ type = types.path;
+ default = "/var/spool/sms/outbox/";
+ description = "Where SMSes to be sent should be placed";
+ };
+
+ sentSMSPath = mkOption {
+ type = types.path;
+ default = "/var/spool/sms/sent/";
+ description = "Where the transmitted SMSes are placed";
+ };
+
+ errorSMSPath = mkOption {
+ type = types.path;
+ default = "/var/spool/sms/error/";
+ description = "Where SMSes with error in transmission is placed";
+ };
+ };
+
+ sql = {
+ driver = mkOption {
+ type = types.enum [ "native_mysql" "native_pgsql" "odbc" "dbi" ];
+ description = "DB driver to use";
+ };
+
+ sqlDialect = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "SQL dialect to use (odbc driver only)";
+ };
+
+ database = mkOption {
+ type = types.str;
+ default = null;
+ description = "Database name to store sms data";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Database server address";
+ };
+
+ user = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "User name used for connection to the database";
+ };
+
+ password = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "User password used for connetion to the database";
+ };
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.${cfg.user} = {
+ description = "gammu-smsd user";
+ uid = config.ids.uids.gammu-smsd;
+ extraGroups = [ "${cfg.device.group}" ];
+ };
+
+ environment.systemPackages = with cfg.backend; [ gammuPackage ]
+ ++ optionals (service == "sql" && sql.driver == "sqlite") [ pkgs.sqlite ];
+
+ systemd.services.gammu-smsd = {
+ description = "gammu-smsd daemon";
+
+ wantedBy = [ "multi-user.target" ];
+
+ wants = with cfg.backend; [ ]
+ ++ optionals (service == "sql" && sql.driver == "native_pgsql") [ "postgresql.service" ];
+
+ preStart = with cfg.backend;
+
+ optionalString (service == "files") (with files; ''
+ mkdir -m 755 -p ${inboxPath} ${outboxPath} ${sentSMSPath} ${errorSMSPath}
+ chown ${cfg.user} -R ${inboxPath}
+ chown ${cfg.user} -R ${outboxPath}
+ chown ${cfg.user} -R ${sentSMSPath}
+ chown ${cfg.user} -R ${errorSMSPath}
+ '')
+ + optionalString (service == "sql" && sql.driver == "sqlite") ''
+ cat "${gammuPackage}/${initDBDir}/sqlite.sql" \
+ | ${pkgs.sqlite.bin}/bin/sqlite3 ${sql.database}
+ ''
+ + (let execPsql = extraArgs: concatStringsSep " " [
+ (optionalString (sql.password != null) "PGPASSWORD=${sql.password}")
+ "${config.services.postgresql.package}/bin/psql"
+ (optionalString (sql.host != null) "-h ${sql.host}")
+ (optionalString (sql.user != null) "-U ${sql.user}")
+ "$extraArgs"
+ "${sql.database}"
+ ]; in optionalString (service == "sql" && sql.driver == "native_pgsql") ''
+ echo '\i '"${gammuPackage}/${initDBDir}/pgsql.sql" | ${execPsql ""}
+ '');
+
+ serviceConfig = {
+ User = "${cfg.user}";
+ Group = "${cfg.device.group}";
+ PermissionsStartOnly = true;
+ ExecStart = "${gammuPackage}/bin/gammu-smsd -c ${configFile}";
+ };
+
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/geoip-updater.nix b/nixpkgs/nixos/modules/services/misc/geoip-updater.nix
new file mode 100644
index 00000000000..baf0a8d73d1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/geoip-updater.nix
@@ -0,0 +1,306 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.geoip-updater;
+
+ dbBaseUrl = "https://geolite.maxmind.com/download/geoip/database";
+
+ randomizedTimerDelaySec = "3600";
+
+ # Use writeScriptBin instead of writeScript, so that argv[0] (logged to the
+ # journal) doesn't include the long nix store path hash. (Prefixing the
+ # ExecStart= command with '@' doesn't work because we start a shell (new
+ # process) that creates a new argv[0].)
+ geoip-updater = pkgs.writeScriptBin "geoip-updater" ''
+ #!${pkgs.runtimeShell}
+ skipExisting=0
+ debug()
+ {
+ echo "<7>$@"
+ }
+ info()
+ {
+ echo "<6>$@"
+ }
+ error()
+ {
+ echo "<3>$@"
+ }
+ die()
+ {
+ error "$@"
+ exit 1
+ }
+ waitNetworkOnline()
+ {
+ ret=1
+ for i in $(seq 6); do
+ curl_out=$("${pkgs.curl.bin}/bin/curl" \
+ --silent --fail --show-error --max-time 60 "${dbBaseUrl}" 2>&1)
+ if [ $? -eq 0 ]; then
+ debug "Server is reachable (try $i)"
+ ret=0
+ break
+ else
+ debug "Server is unreachable (try $i): $curl_out"
+ sleep 10
+ fi
+ done
+ return $ret
+ }
+ dbFnameTmp()
+ {
+ dburl=$1
+ echo "${cfg.databaseDir}/.$(basename "$dburl")"
+ }
+ dbFnameTmpDecompressed()
+ {
+ dburl=$1
+ echo "${cfg.databaseDir}/.$(basename "$dburl")" | sed 's/\.\(gz\|xz\)$//'
+ }
+ dbFname()
+ {
+ dburl=$1
+ echo "${cfg.databaseDir}/$(basename "$dburl")" | sed 's/\.\(gz\|xz\)$//'
+ }
+ downloadDb()
+ {
+ dburl=$1
+ curl_out=$("${pkgs.curl.bin}/bin/curl" \
+ --silent --fail --show-error --max-time 900 -L -o "$(dbFnameTmp "$dburl")" "$dburl" 2>&1)
+ if [ $? -ne 0 ]; then
+ error "Failed to download $dburl: $curl_out"
+ return 1
+ fi
+ }
+ decompressDb()
+ {
+ fn=$(dbFnameTmp "$1")
+ ret=0
+ case "$fn" in
+ *.gz)
+ cmd_out=$("${pkgs.gzip}/bin/gzip" --decompress --force "$fn" 2>&1)
+ ;;
+ *.xz)
+ cmd_out=$("${pkgs.xz.bin}/bin/xz" --decompress --force "$fn" 2>&1)
+ ;;
+ *)
+ cmd_out=$(echo "File \"$fn\" is neither a .gz nor .xz file")
+ false
+ ;;
+ esac
+ if [ $? -ne 0 ]; then
+ error "$cmd_out"
+ ret=1
+ fi
+ }
+ atomicRename()
+ {
+ dburl=$1
+ mv "$(dbFnameTmpDecompressed "$dburl")" "$(dbFname "$dburl")"
+ }
+ removeIfNotInConfig()
+ {
+ # Arg 1 is the full path of an installed DB.
+ # If the corresponding database is not specified in the NixOS config we
+ # remove it.
+ db=$1
+ for cdb in ${lib.concatStringsSep " " cfg.databases}; do
+ confDb=$(echo "$cdb" | sed 's/\.\(gz\|xz\)$//')
+ if [ "$(basename "$db")" = "$(basename "$confDb")" ]; then
+ return 0
+ fi
+ done
+ rm "$db"
+ if [ $? -eq 0 ]; then
+ debug "Removed $(basename "$db") (not listed in services.geoip-updater.databases)"
+ else
+ error "Failed to remove $db"
+ fi
+ }
+ removeUnspecifiedDbs()
+ {
+ for f in "${cfg.databaseDir}/"*; do
+ test -f "$f" || continue
+ case "$f" in
+ *.dat|*.mmdb|*.csv)
+ removeIfNotInConfig "$f"
+ ;;
+ *)
+ debug "Not removing \"$f\" (unknown file extension)"
+ ;;
+ esac
+ done
+ }
+ downloadAndInstall()
+ {
+ dburl=$1
+ if [ "$skipExisting" -eq 1 -a -f "$(dbFname "$dburl")" ]; then
+ debug "Skipping existing file: $(dbFname "$dburl")"
+ return 0
+ fi
+ downloadDb "$dburl" || return 1
+ decompressDb "$dburl" || return 1
+ atomicRename "$dburl" || return 1
+ info "Updated $(basename "$(dbFname "$dburl")")"
+ }
+ for arg in "$@"; do
+ case "$arg" in
+ --skip-existing)
+ skipExisting=1
+ info "Option --skip-existing is set: not updating existing databases"
+ ;;
+ *)
+ error "Unknown argument: $arg";;
+ esac
+ done
+ waitNetworkOnline || die "Network is down (${dbBaseUrl} is unreachable)"
+ test -d "${cfg.databaseDir}" || die "Database directory (${cfg.databaseDir}) doesn't exist"
+ debug "Starting update of GeoIP databases in ${cfg.databaseDir}"
+ all_ret=0
+ for db in ${lib.concatStringsSep " \\\n " cfg.databases}; do
+ downloadAndInstall "${dbBaseUrl}/$db" || all_ret=1
+ done
+ removeUnspecifiedDbs || all_ret=1
+ if [ $all_ret -eq 0 ]; then
+ info "Completed GeoIP database update in ${cfg.databaseDir}"
+ else
+ error "Completed GeoIP database update in ${cfg.databaseDir}, with error(s)"
+ fi
+ # Hack to work around systemd journal race:
+ # https://github.com/systemd/systemd/issues/2913
+ sleep 2
+ exit $all_ret
+ '';
+
+in
+
+{
+ options = {
+ services.geoip-updater = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable periodic downloading of GeoIP databases from
+ maxmind.com. You might want to enable this if you, for instance, use
+ ntopng or Wireshark.
+ '';
+ };
+
+ interval = mkOption {
+ type = types.str;
+ default = "weekly";
+ description = ''
+ Update the GeoIP databases at this time / interval.
+ The format is described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ To prevent load spikes on maxmind.com, the timer interval is
+ randomized by an additional delay of ${randomizedTimerDelaySec}
+ seconds. Setting a shorter interval than this is not recommended.
+ '';
+ };
+
+ databaseDir = mkOption {
+ type = types.path;
+ default = "/var/lib/geoip-databases";
+ description = ''
+ Directory that will contain GeoIP databases.
+ '';
+ };
+
+ databases = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "GeoLiteCountry/GeoIP.dat.gz"
+ "GeoIPv6.dat.gz"
+ "GeoLiteCity.dat.xz"
+ "GeoLiteCityv6-beta/GeoLiteCityv6.dat.gz"
+ "asnum/GeoIPASNum.dat.gz"
+ "asnum/GeoIPASNumv6.dat.gz"
+ "GeoLite2-Country.mmdb.gz"
+ "GeoLite2-City.mmdb.gz"
+ ];
+ description = ''
+ Which GeoIP databases to update. The full URL is ${dbBaseUrl}/ +
+ <literal>the_database</literal>.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = (builtins.filter
+ (x: builtins.match ".*\\.(gz|xz)$" x == null) cfg.databases) == [];
+ message = ''
+ services.geoip-updater.databases supports only .gz and .xz databases.
+
+ Current value:
+ ${toString cfg.databases}
+
+ Offending element(s):
+ ${toString (builtins.filter (x: builtins.match ".*\\.(gz|xz)$" x == null) cfg.databases)};
+ '';
+ }
+ ];
+
+ users.users.geoip = {
+ group = "root";
+ description = "GeoIP database updater";
+ uid = config.ids.uids.geoip;
+ };
+
+ systemd.timers.geoip-updater =
+ { description = "GeoIP Updater Timer";
+ partOf = [ "geoip-updater.service" ];
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnCalendar = cfg.interval;
+ timerConfig.Persistent = "true";
+ timerConfig.RandomizedDelaySec = randomizedTimerDelaySec;
+ };
+
+ systemd.services.geoip-updater = {
+ description = "GeoIP Updater";
+ after = [ "network-online.target" "nss-lookup.target" ];
+ wants = [ "network-online.target" ];
+ preStart = ''
+ mkdir -p "${cfg.databaseDir}"
+ chmod 755 "${cfg.databaseDir}"
+ chown geoip:root "${cfg.databaseDir}"
+ '';
+ serviceConfig = {
+ ExecStart = "${geoip-updater}/bin/geoip-updater";
+ User = "geoip";
+ PermissionsStartOnly = true;
+ };
+ };
+
+ systemd.services.geoip-updater-setup = {
+ description = "GeoIP Updater Setup";
+ after = [ "network-online.target" "nss-lookup.target" ];
+ wants = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ conflicts = [ "geoip-updater.service" ];
+ preStart = ''
+ mkdir -p "${cfg.databaseDir}"
+ chmod 755 "${cfg.databaseDir}"
+ chown geoip:root "${cfg.databaseDir}"
+ '';
+ serviceConfig = {
+ ExecStart = "${geoip-updater}/bin/geoip-updater --skip-existing";
+ User = "geoip";
+ PermissionsStartOnly = true;
+ # So it won't be (needlessly) restarted:
+ RemainAfterExit = true;
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gitea.nix b/nixpkgs/nixos/modules/services/misc/gitea.nix
new file mode 100644
index 00000000000..4992b13c9d4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitea.nix
@@ -0,0 +1,455 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gitea;
+ gitea = cfg.package;
+ pg = config.services.postgresql;
+ useMysql = cfg.database.type == "mysql";
+ usePostgresql = cfg.database.type == "postgres";
+ useSqlite = cfg.database.type == "sqlite3";
+ configFile = pkgs.writeText "app.ini" ''
+ APP_NAME = ${cfg.appName}
+ RUN_USER = ${cfg.user}
+ RUN_MODE = prod
+
+ [database]
+ DB_TYPE = ${cfg.database.type}
+ ${optionalString (usePostgresql || useMysql) ''
+ HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port}
+ NAME = ${cfg.database.name}
+ USER = ${cfg.database.user}
+ PASSWD = #dbpass#
+ ''}
+ ${optionalString useSqlite ''
+ PATH = ${cfg.database.path}
+ ''}
+ ${optionalString usePostgresql ''
+ SSL_MODE = disable
+ ''}
+
+ [repository]
+ ROOT = ${cfg.repositoryRoot}
+
+ [server]
+ DOMAIN = ${cfg.domain}
+ HTTP_ADDR = ${cfg.httpAddress}
+ HTTP_PORT = ${toString cfg.httpPort}
+ ROOT_URL = ${cfg.rootUrl}
+ STATIC_ROOT_PATH = ${cfg.staticRootPath}
+ LFS_JWT_SECRET = #jwtsecret#
+
+ [session]
+ COOKIE_NAME = session
+ COOKIE_SECURE = ${boolToString cfg.cookieSecure}
+
+ [security]
+ SECRET_KEY = #secretkey#
+ INSTALL_LOCK = true
+
+ [log]
+ ROOT_PATH = ${cfg.log.rootPath}
+ LEVEL = ${cfg.log.level}
+
+ [service]
+ DISABLE_REGISTRATION = ${boolToString cfg.disableRegistration}
+
+ ${optionalString (cfg.mailerPasswordFile != null) ''
+ [mailer]
+ PASSWD = #mailerpass#
+ ''}
+
+ ${cfg.extraConfig}
+ '';
+in
+
+{
+ options = {
+ services.gitea = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Enable Gitea Service.";
+ };
+
+ package = mkOption {
+ default = pkgs.gitea;
+ type = types.package;
+ defaultText = "pkgs.gitea";
+ description = "gitea derivation to use";
+ };
+
+ useWizard = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Do not generate a configuration and use gitea' installation wizard instead. The first registered user will be administrator.";
+ };
+
+ stateDir = mkOption {
+ default = "/var/lib/gitea";
+ type = types.str;
+ description = "gitea data directory.";
+ };
+
+ log = {
+ rootPath = mkOption {
+ default = "${cfg.stateDir}/log";
+ type = types.str;
+ description = "Root path for log files.";
+ };
+ level = mkOption {
+ default = "Trace";
+ type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
+ description = "General log level.";
+ };
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "gitea";
+ description = "User account under which gitea runs.";
+ };
+
+ database = {
+ type = mkOption {
+ type = types.enum [ "sqlite3" "mysql" "postgres" ];
+ example = "mysql";
+ default = "sqlite3";
+ description = "Database engine to use.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = (if !usePostgresql then 3306 else pg.port);
+ description = "Database host port.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "gitea";
+ description = "Database name.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "gitea";
+ description = "Database user.";
+ };
+
+ password = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ The password corresponding to <option>database.user</option>.
+ Warning: this is stored in cleartext in the Nix store!
+ Use <option>database.passwordFile</option> instead.
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/gitea-dbpassword";
+ description = ''
+ A file containing the password corresponding to
+ <option>database.user</option>.
+ '';
+ };
+
+ socket = mkOption {
+ type = types.nullOr types.path;
+ default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
+ defaultText = "null";
+ example = "/run/mysqld/mysqld.sock";
+ description = "Path to the unix socket file to use for authentication.";
+ };
+
+ path = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/data/gitea.db";
+ description = "Path to the sqlite3 database file.";
+ };
+
+ createDatabase = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to create a local database automatically.";
+ };
+ };
+
+ dump = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable a timer that runs gitea dump to generate backup-files of the
+ current gitea database and repositories.
+ '';
+ };
+
+ interval = mkOption {
+ type = types.str;
+ default = "04:31";
+ example = "hourly";
+ description = ''
+ Run a gitea dump at this interval. Runs by default at 04:31 every day.
+
+ The format is described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ '';
+ };
+ };
+
+ appName = mkOption {
+ type = types.str;
+ default = "gitea: Gitea Service";
+ description = "Application name.";
+ };
+
+ repositoryRoot = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/repositories";
+ description = "Path to the git repositories.";
+ };
+
+ domain = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Domain name of your server.";
+ };
+
+ rootUrl = mkOption {
+ type = types.str;
+ default = "http://localhost:3000/";
+ description = "Full public URL of gitea server.";
+ };
+
+ httpAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = "HTTP listen address.";
+ };
+
+ httpPort = mkOption {
+ type = types.int;
+ default = 3000;
+ description = "HTTP listen port.";
+ };
+
+ cookieSecure = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Marks session cookies as "secure" as a hint for browsers to only send
+ them via HTTPS. This option is recommend, if gitea is being served over HTTPS.
+ '';
+ };
+
+ staticRootPath = mkOption {
+ type = types.str;
+ default = "${gitea.data}";
+ example = "/var/lib/gitea/data";
+ description = "Upper level of template and static files path.";
+ };
+
+ mailerPasswordFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/var/lib/secrets/gitea/mailpw";
+ description = "Path to a file containing the SMTP password.";
+ };
+
+ disableRegistration = mkEnableOption "the registration lock" // {
+ description = ''
+ By default any user can create an account on this <literal>gitea</literal> instance.
+ This can be disabled by using this option.
+
+ <emphasis>Note:</emphasis> please keep in mind that this should be added after the initial
+ deploy unless <link linkend="opt-services.gitea.useWizard">services.gitea.useWizard</link>
+ is <literal>true</literal> as the first registered user will be the administrator if
+ no install wizard is used.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = "Configuration lines appended to the generated gitea configuration file.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ { assertion = cfg.database.createDatabase -> cfg.database.user == cfg.user;
+ message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
+ }
+ ];
+
+ services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
+ enable = mkDefault true;
+
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
+ enable = mkDefault true;
+ package = mkDefault pkgs.mariadb;
+
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.stateDir}' - ${cfg.user} gitea - -"
+ "d '${cfg.stateDir}/conf' - ${cfg.user} gitea - -"
+ "d '${cfg.stateDir}/custom' - ${cfg.user} gitea - -"
+ "d '${cfg.stateDir}/custom/conf' - ${cfg.user} gitea - -"
+ "d '${cfg.stateDir}/log' - ${cfg.user} gitea - -"
+ "d '${cfg.repositoryRoot}' - ${cfg.user} gitea - -"
+ "Z '${cfg.stateDir}' - ${cfg.user} gitea - -"
+
+ # If we have a folder or symlink with gitea locales, remove it
+ # And symlink the current gitea locales in place
+ "L+ '${cfg.stateDir}/conf/locale' - - - - ${gitea.out}/locale"
+ ];
+
+ systemd.services.gitea = {
+ description = "gitea";
+ after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
+ wantedBy = [ "multi-user.target" ];
+ path = [ gitea.bin pkgs.gitAndTools.git ];
+
+ preStart = let
+ runConfig = "${cfg.stateDir}/custom/conf/app.ini";
+ secretKey = "${cfg.stateDir}/custom/conf/secret_key";
+ jwtSecret = "${cfg.stateDir}/custom/conf/jwt_secret";
+ in ''
+ # copy custom configuration and generate a random secret key if needed
+ ${optionalString (cfg.useWizard == false) ''
+ cp -f ${configFile} ${runConfig}
+
+ if [ ! -e ${secretKey} ]; then
+ ${gitea.bin}/bin/gitea generate secret SECRET_KEY > ${secretKey}
+ fi
+
+ if [ ! -e ${jwtSecret} ]; then
+ ${gitea.bin}/bin/gitea generate secret LFS_JWT_SECRET > ${jwtSecret}
+ fi
+
+ KEY="$(head -n1 ${secretKey})"
+ DBPASS="$(head -n1 ${cfg.database.passwordFile})"
+ JWTSECRET="$(head -n1 ${jwtSecret})"
+ ${if (cfg.mailerPasswordFile == null) then ''
+ MAILERPASSWORD="#mailerpass#"
+ '' else ''
+ MAILERPASSWORD="$(head -n1 ${cfg.mailerPasswordFile} || :)"
+ ''}
+ sed -e "s,#secretkey#,$KEY,g" \
+ -e "s,#dbpass#,$DBPASS,g" \
+ -e "s,#jwtsecet#,$JWTSECET,g" \
+ -e "s,#mailerpass#,$MAILERPASSWORD,g" \
+ -i ${runConfig}
+ chmod 640 ${runConfig} ${secretKey} ${jwtSecret}
+ ''}
+
+ # update all hooks' binary paths
+ HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*")
+ if [ "$HOOKS" ]
+ then
+ sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea.bin}/bin/gitea,g' $HOOKS
+ sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS
+ sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS
+ sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS
+ fi
+
+ # update command option in authorized_keys
+ if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
+ then
+ sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea.bin}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys
+ fi
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = "gitea";
+ WorkingDirectory = cfg.stateDir;
+ ExecStart = "${gitea.bin}/bin/gitea web";
+ Restart = "always";
+ };
+
+ environment = {
+ USER = cfg.user;
+ HOME = cfg.stateDir;
+ GITEA_WORK_DIR = cfg.stateDir;
+ };
+ };
+
+ users.users = mkIf (cfg.user == "gitea") {
+ gitea = {
+ description = "Gitea Service";
+ home = cfg.stateDir;
+ useDefaultShell = true;
+ group = "gitea";
+ };
+ };
+
+ users.groups.gitea = {};
+
+ warnings = optional (cfg.database.password != "")
+ ''config.services.gitea.database.password will be stored as plaintext
+ in the Nix store. Use database.passwordFile instead.'';
+
+ # Create database passwordFile default when password is configured.
+ services.gitea.database.passwordFile =
+ (mkDefault (toString (pkgs.writeTextFile {
+ name = "gitea-database-password";
+ text = cfg.database.password;
+ })));
+
+ systemd.services.gitea-dump = mkIf cfg.dump.enable {
+ description = "gitea dump";
+ after = [ "gitea.service" ];
+ wantedBy = [ "default.target" ];
+ path = [ gitea.bin ];
+
+ environment = {
+ USER = cfg.user;
+ HOME = cfg.stateDir;
+ GITEA_WORK_DIR = cfg.stateDir;
+ };
+
+ serviceConfig = {
+ Type = "oneshot";
+ User = cfg.user;
+ ExecStart = "${gitea.bin}/bin/gitea dump";
+ WorkingDirectory = cfg.stateDir;
+ };
+ };
+
+ systemd.timers.gitea-dump = mkIf cfg.dump.enable {
+ description = "Update timer for gitea-dump";
+ partOf = [ "gitea-dump.service" ];
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnCalendar = cfg.dump.interval;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gitit.nix b/nixpkgs/nixos/modules/services/misc/gitit.nix
new file mode 100644
index 00000000000..1ec030549f9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitit.nix
@@ -0,0 +1,724 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.gitit;
+
+ homeDir = "/var/lib/gitit";
+
+ toYesNo = b: if b then "yes" else "no";
+
+ gititShared = with cfg.haskellPackages; gitit + "/share/" + pkgs.stdenv.hostPlatform.system + "-" + ghc.name + "/" + gitit.pname + "-" + gitit.version;
+
+ gititWithPkgs = hsPkgs: extras: hsPkgs.ghcWithPackages (self: with self; [ gitit ] ++ (extras self));
+
+ gititSh = hsPkgs: extras: with pkgs; let
+ env = gititWithPkgs hsPkgs extras;
+ in writeScript "gitit" ''
+ #!${runtimeShell}
+ cd $HOME
+ export NIX_GHC="${env}/bin/ghc"
+ export NIX_GHCPKG="${env}/bin/ghc-pkg"
+ export NIX_GHC_DOCDIR="${env}/share/doc/ghc/html"
+ export NIX_GHC_LIBDIR=$( $NIX_GHC --print-libdir )
+ ${env}/bin/gitit -f ${configFile}
+ '';
+
+ gititOptions = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the gitit service.";
+ };
+
+ haskellPackages = mkOption {
+ default = pkgs.haskellPackages;
+ defaultText = "pkgs.haskellPackages";
+ example = literalExample "pkgs.haskell.packages.ghc784";
+ description = "haskellPackages used to build gitit and plugins.";
+ };
+
+ extraPackages = mkOption {
+ default = self: [];
+ example = literalExample ''
+ haskellPackages: [
+ haskellPackages.wreq
+ ]
+ '';
+ description = ''
+ Extra packages available to ghc when running gitit. The
+ value must be a function which receives the attrset defined
+ in <varname>haskellPackages</varname> as the sole argument.
+ '';
+ };
+
+ address = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = "IP address on which the web server will listen.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 5001;
+ description = "Port on which the web server will run.";
+ };
+
+ wikiTitle = mkOption {
+ type = types.str;
+ default = "Gitit!";
+ description = "The wiki title.";
+ };
+
+ repositoryType = mkOption {
+ type = types.enum ["git" "darcs" "mercurial"];
+ default = "git";
+ description = "Specifies the type of repository used for wiki content.";
+ };
+
+ repositoryPath = mkOption {
+ type = types.path;
+ default = homeDir + "/wiki";
+ description = ''
+ Specifies the path of the repository directory. If it does not
+ exist, gitit will create it on startup.
+ '';
+ };
+
+ requireAuthentication = mkOption {
+ type = types.enum [ "none" "modify" "read" ];
+ default = "modify";
+ description = ''
+ If 'none', login is never required, and pages can be edited
+ anonymously. If 'modify', login is required to modify the wiki
+ (edit, add, delete pages, upload files). If 'read', login is
+ required to see any wiki pages.
+ '';
+ };
+
+ authenticationMethod = mkOption {
+ type = types.enum [ "form" "http" "generic" "github" ];
+ default = "form";
+ description = ''
+ 'form' means that users will be logged in and registered using forms
+ in the gitit web interface. 'http' means that gitit will assume that
+ HTTP authentication is in place and take the logged in username from
+ the "Authorization" field of the HTTP request header (in addition,
+ the login/logout and registration links will be suppressed).
+ 'generic' means that gitit will assume that some form of
+ authentication is in place that directly sets REMOTE_USER to the name
+ of the authenticated user (e.g. mod_auth_cas on apache). 'rpx' means
+ that gitit will attempt to log in through https://rpxnow.com. This
+ requires that 'rpx-domain', 'rpx-key', and 'base-url' be set below,
+ and that 'curl' be in the system path.
+ '';
+ };
+
+ userFile = mkOption {
+ type = types.path;
+ default = homeDir + "/gitit-users";
+ description = ''
+ Specifies the path of the file containing user login information. If
+ it does not exist, gitit will create it (with an empty user list).
+ This file is not used if 'http' is selected for
+ authentication-method.
+ '';
+ };
+
+ sessionTimeout = mkOption {
+ type = types.int;
+ default = 60;
+ description = ''
+ Number of minutes of inactivity before a session expires.
+ '';
+ };
+
+ staticDir = mkOption {
+ type = types.path;
+ default = gititShared + "/data/static";
+ description = ''
+ Specifies the path of the static directory (containing javascript,
+ css, and images). If it does not exist, gitit will create it and
+ populate it with required scripts, stylesheets, and images.
+ '';
+ };
+
+ defaultPageType = mkOption {
+ type = types.enum [ "markdown" "rst" "latex" "html" "markdown+lhs" "rst+lhs" "latex+lhs" ];
+ default = "markdown";
+ description = ''
+ Specifies the type of markup used to interpret pages in the wiki.
+ Possible values are markdown, rst, latex, html, markdown+lhs,
+ rst+lhs, and latex+lhs. (the +lhs variants treat the input as
+ literate Haskell. See pandoc's documentation for more details.) If
+ Markdown is selected, pandoc's syntax extensions (for footnotes,
+ delimited code blocks, etc.) will be enabled. Note that pandoc's
+ restructuredtext parser is not complete, so some pages may not be
+ rendered correctly if rst is selected. The same goes for latex and
+ html.
+ '';
+ };
+
+ math = mkOption {
+ type = types.enum [ "mathml" "raw" "mathjax" "jsmath" "google" ];
+ default = "mathml";
+ description = ''
+ Specifies how LaTeX math is to be displayed. Possible values are
+ mathml, raw, mathjax, jsmath, and google. If mathml is selected,
+ gitit will convert LaTeX math to MathML and link in a script,
+ MathMLinHTML.js, that allows the MathML to be seen in Gecko browsers,
+ IE + mathplayer, and Opera. In other browsers you may get a jumble of
+ characters. If raw is selected, the LaTeX math will be displayed as
+ raw LaTeX math. If mathjax is selected, gitit will link to the
+ remote mathjax script. If jsMath is selected, gitit will link to the
+ script /js/jsMath/easy/load.js, and will assume that jsMath has been
+ installed into the js/jsMath directory. This is the most portable
+ solution. If google is selected, the google chart API is called to
+ render the formula as an image. This requires a connection to google,
+ and might raise a technical or a privacy problem.
+ '';
+ };
+
+ mathJaxScript = mkOption {
+ type = types.str;
+ default = "https://d3eoax9i5htok0.cloudfront.net/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
+ description = ''
+ Specifies the path to MathJax rendering script. You might want to
+ use your own MathJax script to render formulas without Internet
+ connection or if you want to use some special LaTeX packages. Note:
+ path specified there cannot be an absolute path to a script on your
+ hdd, instead you should run your (local if you wish) HTTP server
+ which will serve the MathJax.js script. You can easily (in four lines
+ of code) serve MathJax.js using
+ http://happstack.com/docs/crashcourse/FileServing.html Do not forget
+ the "http://" prefix (e.g. http://localhost:1234/MathJax.js).
+ '';
+ };
+
+ showLhsBirdTracks = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Specifies whether to show Haskell code blocks in "bird style", with
+ "> " at the beginning of each line.
+ '';
+ };
+
+ templatesDir = mkOption {
+ type = types.path;
+ default = gititShared + "/data/templates";
+ description = ''
+ Specifies the path of the directory containing page templates. If it
+ does not exist, gitit will create it with default templates. Users
+ may wish to edit the templates to customize the appearance of their
+ wiki. The template files are HStringTemplate templates. Variables to
+ be interpolated appear between $\'s. Literal $\'s must be
+ backslash-escaped.
+ '';
+ };
+
+ logFile = mkOption {
+ type = types.path;
+ default = homeDir + "/gitit.log";
+ description = ''
+ Specifies the path of gitit's log file. If it does not exist, gitit
+ will create it. The log is in Apache combined log format.
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.enum [ "DEBUG" "INFO" "NOTICE" "WARNING" "ERROR" "CRITICAL" "ALERT" "EMERGENCY" ];
+ default = "ERROR";
+ description = ''
+ Determines how much information is logged. Possible values (from
+ most to least verbose) are DEBUG, INFO, NOTICE, WARNING, ERROR,
+ CRITICAL, ALERT, EMERGENCY.
+ '';
+ };
+
+ frontPage = mkOption {
+ type = types.str;
+ default = "Front Page";
+ description = ''
+ Specifies which wiki page is to be used as the wiki's front page.
+ Gitit creates a default front page on startup, if one does not exist
+ already.
+ '';
+ };
+
+ noDelete = mkOption {
+ type = types.str;
+ default = "Front Page, Help";
+ description = ''
+ Specifies pages that cannot be deleted through the web interface.
+ (They can still be deleted directly using git or darcs.) A
+ comma-separated list of page names. Leave blank to allow every page
+ to be deleted.
+ '';
+ };
+
+ noEdit = mkOption {
+ type = types.str;
+ default = "Help";
+ description = ''
+ Specifies pages that cannot be edited through the web interface.
+ Leave blank to allow every page to be edited.
+ '';
+ };
+
+ defaultSummary = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Specifies text to be used in the change description if the author
+ leaves the "description" field blank. If default-summary is blank
+ (the default), the author will be required to fill in the description
+ field.
+ '';
+ };
+
+ tableOfContents = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Specifies whether to print a tables of contents (with links to
+ sections) on each wiki page.
+ '';
+ };
+
+ plugins = mkOption {
+ type = with types; listOf str;
+ default = [ (gititShared + "/plugins/Dot.hs") ];
+ description = ''
+ Specifies a list of plugins to load. Plugins may be specified either
+ by their path or by their module name. If the plugin name starts
+ with Gitit.Plugin., gitit will assume that the plugin is an installed
+ module and will not try to find a source file.
+ '';
+ };
+
+ useCache = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Specifies whether to cache rendered pages. Note that if use-feed is
+ selected, feeds will be cached regardless of the value of use-cache.
+ '';
+ };
+
+ cacheDir = mkOption {
+ type = types.path;
+ default = homeDir + "/cache";
+ description = "Path where rendered pages will be cached.";
+ };
+
+ maxUploadSize = mkOption {
+ type = types.str;
+ default = "1000K";
+ description = ''
+ Specifies an upper limit on the size (in bytes) of files uploaded
+ through the wiki's web interface. To disable uploads, set this to
+ 0K. This will result in the uploads link disappearing and the
+ _upload url becoming inactive.
+ '';
+ };
+
+ maxPageSize = mkOption {
+ type = types.str;
+ default = "1000K";
+ description = "Specifies an upper limit on the size (in bytes) of pages.";
+ };
+
+ debugMode = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Causes debug information to be logged while gitit is running.";
+ };
+
+ compressResponses = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Specifies whether HTTP responses should be compressed.";
+ };
+
+ mimeTypesFile = mkOption {
+ type = types.path;
+ default = "/etc/mime/types.info";
+ description = ''
+ Specifies the path of a file containing mime type mappings. Each
+ line of the file should contain two fields, separated by whitespace.
+ The first field is the mime type, the second is a file extension.
+ For example:
+<programlisting>
+video/x-ms-wmx wmx
+</programlisting>
+ If the file is not found, some simple defaults will be used.
+ '';
+ };
+
+ useReCaptcha = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If true, causes gitit to use the reCAPTCHA service
+ (http://recaptcha.net) to prevent bots from creating accounts.
+ '';
+ };
+
+ reCaptchaPrivateKey = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Specifies the private key for the reCAPTCHA service. To get
+ these, you need to create an account at http://recaptcha.net.
+ '';
+ };
+
+ reCaptchaPublicKey = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Specifies the public key for the reCAPTCHA service. To get
+ these, you need to create an account at http://recaptcha.net.
+ '';
+ };
+
+ accessQuestion = mkOption {
+ type = types.str;
+ default = "What is the code given to you by Ms. X?";
+ description = ''
+ Specifies a question that users must answer when they attempt to
+ create an account
+ '';
+ };
+
+ accessQuestionAnswers = mkOption {
+ type = types.str;
+ default = "RED DOG, red dog";
+ description = ''
+ Specifies a question that users must answer when they attempt to
+ create an account, along with a comma-separated list of acceptable
+ answers. This can be used to institute a rudimentary password for
+ signing up as a user on the wiki, or as an alternative to reCAPTCHA.
+ Example:
+ access-question: What is the code given to you by Ms. X?
+ access-question-answers: RED DOG, red dog
+ '';
+ };
+
+ rpxDomain = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Specifies the domain and key of your RPX account. The domain is just
+ the prefix of the complete RPX domain, so if your full domain is
+ 'https://foo.rpxnow.com/', use 'foo' as the value of rpx-domain.
+ '';
+ };
+
+ rpxKey = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "RPX account access key.";
+ };
+
+ mailCommand = mkOption {
+ type = types.str;
+ default = "sendmail %s";
+ description = ''
+ Specifies the command to use to send notification emails. '%s' will
+ be replaced by the destination email address. The body of the
+ message will be read from stdin. If this field is left blank,
+ password reset will not be offered.
+ '';
+ };
+
+ resetPasswordMessage = mkOption {
+ type = types.lines;
+ default = ''
+ > From: gitit@$hostname$
+ > To: $useremail$
+ > Subject: Wiki password reset
+ >
+ > Hello $username$,
+ >
+ > To reset your password, please follow the link below:
+ > http://$hostname$:$port$$resetlink$
+ >
+ > Regards
+ '';
+ description = ''
+ Gives the text of the message that will be sent to the user should
+ she want to reset her password, or change other registration info.
+ The lines must be indented, and must begin with '>'. The initial
+ spaces and '> ' will be stripped off. $username$ will be replaced by
+ the user's username, $useremail$ by her email address, $hostname$ by
+ the hostname on which the wiki is running (as returned by the
+ hostname system call), $port$ by the port on which the wiki is
+ running, and $resetlink$ by the relative path of a reset link derived
+ from the user's existing hashed password. If your gitit wiki is being
+ proxied to a location other than the root path of $port$, you should
+ change the link to reflect this: for example, to
+ http://$hostname$/path/to/wiki$resetlink$ or
+ http://gitit.$hostname$$resetlink$
+ '';
+ };
+
+ useFeed = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Specifies whether an ATOM feed should be enabled (for the site and
+ for individual pages).
+ '';
+ };
+
+ baseUrl = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ The base URL of the wiki, to be used in constructing feed IDs and RPX
+ token_urls. Set this if useFeed is false or authentication-method
+ is 'rpx'.
+ '';
+ };
+
+ absoluteUrls = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Make wikilinks absolute with respect to the base-url. So, for
+ example, in a wiki served at the base URL '/wiki', on a page
+ Sub/Page, the wikilink '[Cactus]()' will produce a link to
+ '/wiki/Cactus' if absoluteUrls is true, and a relative link to
+ 'Cactus' (referring to '/wiki/Sub/Cactus') if absolute-urls is 'no'.
+ '';
+ };
+
+ feedDays = mkOption {
+ type = types.int;
+ default = 14;
+ description = "Number of days to be included in feeds.";
+ };
+
+ feedRefreshTime = mkOption {
+ type = types.int;
+ default = 60;
+ description = "Number of minutes to cache feeds before refreshing.";
+ };
+
+ pdfExport = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If true, PDF will appear in export options. PDF will be created using
+ pdflatex, which must be installed and in the path. Note that PDF
+ exports create significant additional server load.
+ '';
+ };
+
+ pandocUserData = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ If a directory is specified, this will be searched for pandoc
+ customizations. These can include a templates/ directory for custom
+ templates for various export formats, an S5 directory for custom S5
+ styles, and a reference.odt for ODT exports. If no directory is
+ specified, $HOME/.pandoc will be searched. See pandoc's README for
+ more information.
+ '';
+ };
+
+ xssSanitize = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If true, all HTML (including that produced by pandoc) is filtered
+ through xss-sanitize. Set to no only if you trust all of your users.
+ '';
+ };
+
+ oauthClientId = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "OAuth client ID";
+ };
+
+ oauthClientSecret = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "OAuth client secret";
+ };
+
+ oauthCallback = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "OAuth callback URL";
+ };
+
+ oauthAuthorizeEndpoint = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "OAuth authorize endpoint";
+ };
+
+ oauthAccessTokenEndpoint = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "OAuth access token endpoint";
+ };
+
+ githubOrg = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "Github organization";
+ };
+ };
+
+ configFile = pkgs.writeText "gitit.conf" ''
+ address: ${cfg.address}
+ port: ${toString cfg.port}
+ wiki-title: ${cfg.wikiTitle}
+ repository-type: ${cfg.repositoryType}
+ repository-path: ${cfg.repositoryPath}
+ require-authentication: ${cfg.requireAuthentication}
+ authentication-method: ${cfg.authenticationMethod}
+ user-file: ${cfg.userFile}
+ session-timeout: ${toString cfg.sessionTimeout}
+ static-dir: ${cfg.staticDir}
+ default-page-type: ${cfg.defaultPageType}
+ math: ${cfg.math}
+ mathjax-script: ${cfg.mathJaxScript}
+ show-lhs-bird-tracks: ${toYesNo cfg.showLhsBirdTracks}
+ templates-dir: ${cfg.templatesDir}
+ log-file: ${cfg.logFile}
+ log-level: ${cfg.logLevel}
+ front-page: ${cfg.frontPage}
+ no-delete: ${cfg.noDelete}
+ no-edit: ${cfg.noEdit}
+ default-summary: ${cfg.defaultSummary}
+ table-of-contents: ${toYesNo cfg.tableOfContents}
+ plugins: ${concatStringsSep "," cfg.plugins}
+ use-cache: ${toYesNo cfg.useCache}
+ cache-dir: ${cfg.cacheDir}
+ max-upload-size: ${cfg.maxUploadSize}
+ max-page-size: ${cfg.maxPageSize}
+ debug-mode: ${toYesNo cfg.debugMode}
+ compress-responses: ${toYesNo cfg.compressResponses}
+ mime-types-file: ${cfg.mimeTypesFile}
+ use-recaptcha: ${toYesNo cfg.useReCaptcha}
+ recaptcha-private-key: ${toString cfg.reCaptchaPrivateKey}
+ recaptcha-public-key: ${toString cfg.reCaptchaPublicKey}
+ access-question: ${cfg.accessQuestion}
+ access-question-answers: ${cfg.accessQuestionAnswers}
+ rpx-domain: ${toString cfg.rpxDomain}
+ rpx-key: ${toString cfg.rpxKey}
+ mail-command: ${cfg.mailCommand}
+ reset-password-message: ${cfg.resetPasswordMessage}
+ use-feed: ${toYesNo cfg.useFeed}
+ base-url: ${toString cfg.baseUrl}
+ absolute-urls: ${toYesNo cfg.absoluteUrls}
+ feed-days: ${toString cfg.feedDays}
+ feed-refresh-time: ${toString cfg.feedRefreshTime}
+ pdf-export: ${toYesNo cfg.pdfExport}
+ pandoc-user-data: ${toString cfg.pandocUserData}
+ xss-sanitize: ${toYesNo cfg.xssSanitize}
+
+ [Github]
+ oauthclientid: ${toString cfg.oauthClientId}
+ oauthclientsecret: ${toString cfg.oauthClientSecret}
+ oauthcallback: ${toString cfg.oauthCallback}
+ oauthauthorizeendpoint: ${toString cfg.oauthAuthorizeEndpoint}
+ oauthaccesstokenendpoint: ${toString cfg.oauthAccessTokenEndpoint}
+ github-org: ${toString cfg.githubOrg}
+ '';
+
+in
+
+{
+
+ options.services.gitit = gititOptions;
+
+ config = mkIf cfg.enable {
+
+ users.users.gitit = {
+ group = config.users.groups.gitit.name;
+ description = "Gitit user";
+ home = homeDir;
+ createHome = true;
+ uid = config.ids.uids.gitit;
+ };
+
+ users.groups.gitit.gid = config.ids.gids.gitit;
+
+ systemd.services.gitit = let
+ uid = toString config.ids.uids.gitit;
+ gid = toString config.ids.gids.gitit;
+ in {
+ description = "Git and Pandoc Powered Wiki";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [ curl ]
+ ++ optional cfg.pdfExport texlive.combined.scheme-basic
+ ++ optional (cfg.repositoryType == "darcs") darcs
+ ++ optional (cfg.repositoryType == "mercurial") mercurial
+ ++ optional (cfg.repositoryType == "git") git;
+
+ preStart = let
+ gm = "gitit@${config.networking.hostName}";
+ in
+ with cfg; ''
+ chown ${uid}:${gid} -R ${homeDir}
+ for dir in ${repositoryPath} ${staticDir} ${templatesDir} ${cacheDir}
+ do
+ if [ ! -d $dir ]
+ then
+ mkdir -p $dir
+ find $dir -type d -exec chmod 0750 {} +
+ find $dir -type f -exec chmod 0640 {} +
+ fi
+ done
+ cd ${repositoryPath}
+ ${
+ if repositoryType == "darcs" then
+ ''
+ if [ ! -d _darcs ]
+ then
+ ${pkgs.darcs}/bin/darcs initialize
+ echo "${gm}" > _darcs/prefs/email
+ ''
+ else if repositoryType == "mercurial" then
+ ''
+ if [ ! -d .hg ]
+ then
+ ${pkgs.mercurial}/bin/hg init
+ cat >> .hg/hgrc <<NAMED
+[ui]
+username = gitit ${gm}
+NAMED
+ ''
+ else
+ ''
+ if [ ! -d .git ]
+ then
+ ${pkgs.git}/bin/git init
+ ${pkgs.git}/bin/git config user.email "${gm}"
+ ${pkgs.git}/bin/git config user.name "gitit"
+ ''}
+ chown ${uid}:${gid} -R ${repositoryPath}
+ fi
+ cd -
+ '';
+
+ serviceConfig = {
+ User = config.users.users.gitit.name;
+ Group = config.users.groups.gitit.name;
+ ExecStart = with cfg; gititSh haskellPackages extraPackages;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.nix b/nixpkgs/nixos/modules/services/misc/gitlab.nix
new file mode 100644
index 00000000000..4c1ffead00c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitlab.nix
@@ -0,0 +1,851 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gitlab;
+
+ ruby = cfg.packages.gitlab.ruby;
+
+ gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket";
+ gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket";
+ pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url;
+
+ databaseConfig = {
+ production = {
+ adapter = "postgresql";
+ database = cfg.databaseName;
+ host = cfg.databaseHost;
+ username = cfg.databaseUsername;
+ encoding = "utf8";
+ pool = cfg.databasePool;
+ } // cfg.extraDatabaseConfig;
+ };
+
+ gitalyToml = pkgs.writeText "gitaly.toml" ''
+ socket_path = "${lib.escape ["\""] gitalySocket}"
+ bin_dir = "${cfg.packages.gitaly}/bin"
+ prometheus_listen_addr = "localhost:9236"
+
+ [git]
+ bin_path = "${pkgs.git}/bin/git"
+
+ [gitaly-ruby]
+ dir = "${cfg.packages.gitaly.ruby}"
+
+ [gitlab-shell]
+ dir = "${cfg.packages.gitlab-shell}"
+
+ ${concatStringsSep "\n" (attrValues (mapAttrs (k: v: ''
+ [[storage]]
+ name = "${lib.escape ["\""] k}"
+ path = "${lib.escape ["\""] v.path}"
+ '') gitlabConfig.production.repositories.storages))}
+ '';
+
+ gitlabShellConfig = {
+ user = cfg.user;
+ gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}";
+ http_settings.self_signed_cert = false;
+ repos_path = "${cfg.statePath}/repositories";
+ secret_file = "${cfg.statePath}/gitlab_shell_secret";
+ log_file = "${cfg.statePath}/log/gitlab-shell.log";
+ custom_hooks_dir = "${cfg.statePath}/custom_hooks";
+ redis = {
+ bin = "${pkgs.redis}/bin/redis-cli";
+ host = "127.0.0.1";
+ port = 6379;
+ database = 0;
+ namespace = "resque:gitlab";
+ };
+ };
+
+ redisConfig.production.url = "redis://localhost:6379/";
+
+ gitlabConfig = {
+ # These are the default settings from config/gitlab.example.yml
+ production = flip recursiveUpdate cfg.extraConfig {
+ gitlab = {
+ host = cfg.host;
+ port = cfg.port;
+ https = cfg.https;
+ user = cfg.user;
+ email_enabled = true;
+ email_display_name = "GitLab";
+ email_reply_to = "noreply@localhost";
+ default_theme = 2;
+ default_projects_features = {
+ issues = true;
+ merge_requests = true;
+ wiki = true;
+ snippets = true;
+ builds = true;
+ container_registry = true;
+ };
+ };
+ repositories.storages.default.path = "${cfg.statePath}/repositories";
+ repositories.storages.default.gitaly_address = "unix:${gitalySocket}";
+ artifacts.enabled = true;
+ lfs.enabled = true;
+ gravatar.enabled = true;
+ cron_jobs = { };
+ gitlab_ci.builds_path = "${cfg.statePath}/builds";
+ ldap.enabled = false;
+ omniauth.enabled = false;
+ shared.path = "${cfg.statePath}/shared";
+ gitaly.client_path = "${cfg.packages.gitaly}/bin";
+ backup.path = "${cfg.backupPath}";
+ gitlab_shell = {
+ path = "${cfg.packages.gitlab-shell}";
+ hooks_path = "${cfg.statePath}/shell/hooks";
+ secret_file = "${cfg.statePath}/gitlab_shell_secret";
+ upload_pack = true;
+ receive_pack = true;
+ };
+ workhorse.secret_file = "${cfg.statePath}/.gitlab_workhorse_secret";
+ git.bin_path = "git";
+ monitoring = {
+ ip_whitelist = [ "127.0.0.0/8" "::1/128" ];
+ sidekiq_exporter = {
+ enable = true;
+ address = "localhost";
+ port = 3807;
+ };
+ };
+ extra = {};
+ uploads.storage_path = cfg.statePath;
+ };
+ };
+
+ gitlabEnv = {
+ HOME = "${cfg.statePath}/home";
+ UNICORN_PATH = "${cfg.statePath}/";
+ GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/";
+ SCHEMA = "${cfg.statePath}/db/schema.rb";
+ GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
+ GITLAB_LOG_PATH = "${cfg.statePath}/log";
+ GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig);
+ prometheus_multiproc_dir = "/run/gitlab";
+ RAILS_ENV = "production";
+ };
+
+ gitlab-rake = pkgs.stdenv.mkDerivation {
+ name = "gitlab-rake";
+ buildInputs = [ pkgs.makeWrapper ];
+ dontBuild = true;
+ dontUnpack = true;
+ installPhase = ''
+ mkdir -p $out/bin
+ makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \
+ ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
+ --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar 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'
+ '';
+ };
+
+ gitlab-rails = pkgs.stdenv.mkDerivation {
+ name = "gitlab-rails";
+ buildInputs = [ pkgs.makeWrapper ];
+ dontBuild = true;
+ dontUnpack = true;
+ installPhase = ''
+ mkdir -p $out/bin
+ makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rails $out/bin/gitlab-rails \
+ ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
+ --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar config.services.postgresql.package pkgs.coreutils pkgs.procps ]}:$PATH' \
+ --run 'cd ${cfg.packages.gitlab}/share/gitlab'
+ '';
+ };
+
+ extraGitlabRb = pkgs.writeText "extra-gitlab.rb" cfg.extraGitlabRb;
+
+ smtpSettings = pkgs.writeText "gitlab-smtp-settings.rb" ''
+ if Rails.env.production?
+ Rails.application.config.action_mailer.delivery_method = :smtp
+
+ ActionMailer::Base.delivery_method = :smtp
+ ActionMailer::Base.smtp_settings = {
+ address: "${cfg.smtp.address}",
+ port: ${toString cfg.smtp.port},
+ ${optionalString (cfg.smtp.username != null) ''user_name: "${cfg.smtp.username}",''}
+ ${optionalString (cfg.smtp.passwordFile != null) ''password: "@smtpPassword@",''}
+ domain: "${cfg.smtp.domain}",
+ ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"}
+ enable_starttls_auto: ${toString cfg.smtp.enableStartTLSAuto},
+ ca_file: "/etc/ssl/certs/ca-certificates.crt",
+ openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}'
+ }
+ end
+ '';
+
+in {
+
+ options = {
+ services.gitlab = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the gitlab service.
+ '';
+ };
+
+ packages.gitlab = mkOption {
+ type = types.package;
+ default = pkgs.gitlab;
+ defaultText = "pkgs.gitlab";
+ description = "Reference to the gitlab package";
+ example = "pkgs.gitlab-ee";
+ };
+
+ packages.gitlab-shell = mkOption {
+ type = types.package;
+ default = pkgs.gitlab-shell;
+ defaultText = "pkgs.gitlab-shell";
+ description = "Reference to the gitlab-shell package";
+ };
+
+ packages.gitlab-workhorse = mkOption {
+ type = types.package;
+ default = pkgs.gitlab-workhorse;
+ defaultText = "pkgs.gitlab-workhorse";
+ description = "Reference to the gitlab-workhorse package";
+ };
+
+ packages.gitaly = mkOption {
+ type = types.package;
+ default = pkgs.gitaly;
+ defaultText = "pkgs.gitaly";
+ description = "Reference to the gitaly package";
+ };
+
+ statePath = mkOption {
+ type = types.str;
+ default = "/var/gitlab/state";
+ description = ''
+ Gitlab state directory. Configuration, repositories and
+ logs, among other things, are stored here.
+
+ The directory will be created automatically if it doesn't
+ exist already. Its parent directories must be owned by
+ either <literal>root</literal> or the user set in
+ <option>services.gitlab.user</option>.
+ '';
+ };
+
+ backupPath = mkOption {
+ type = types.str;
+ default = cfg.statePath + "/backup";
+ description = "Gitlab path for backups.";
+ };
+
+ databaseHost = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Gitlab database hostname. An empty string means <quote>use
+ local unix socket connection</quote>.
+ '';
+ };
+
+ databasePasswordFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ File containing the Gitlab database user password.
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ databaseCreateLocally = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether a database should be automatically created on the
+ local host. Set this to <literal>false</literal> if you plan
+ on provisioning a local database yourself or use an external
+ one.
+ '';
+ };
+
+ databaseName = mkOption {
+ type = types.str;
+ default = "gitlab";
+ description = "Gitlab database name.";
+ };
+
+ databaseUsername = mkOption {
+ type = types.str;
+ default = "gitlab";
+ description = "Gitlab database user.";
+ };
+
+ databasePool = mkOption {
+ type = types.int;
+ default = 5;
+ description = "Database connection pool size.";
+ };
+
+ extraDatabaseConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ description = "Extra configuration in config/database.yml.";
+ };
+
+ extraGitlabRb = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ if Rails.env.production?
+ Rails.application.config.action_mailer.delivery_method = :sendmail
+ ActionMailer::Base.delivery_method = :sendmail
+ ActionMailer::Base.sendmail_settings = {
+ location: "/run/wrappers/bin/sendmail",
+ arguments: "-i -t"
+ }
+ end
+ '';
+ description = ''
+ Extra configuration to be placed in config/extra-gitlab.rb. This can
+ be used to add configuration not otherwise exposed through this module's
+ options.
+ '';
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = config.networking.hostName;
+ description = "Gitlab host name. Used e.g. for copy-paste URLs.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8080;
+ description = ''
+ Gitlab server port for copy-paste URLs, e.g. 80 or 443 if you're
+ service over https.
+ '';
+ };
+
+ https = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether gitlab prints URLs with https as scheme.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "gitlab";
+ description = "User to run gitlab and all related services.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "gitlab";
+ description = "Group to run gitlab and all related services.";
+ };
+
+ initialRootEmail = mkOption {
+ type = types.str;
+ default = "admin@local.host";
+ description = ''
+ Initial email address of the root account if this is a new install.
+ '';
+ };
+
+ initialRootPasswordFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ File containing the initial password of the root account if
+ this is a new install.
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ smtp = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable gitlab mail delivery over SMTP.";
+ };
+
+ address = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Address of the SMTP server for Gitlab.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 465;
+ description = "Port of the SMTP server for Gitlab.";
+ };
+
+ username = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "Username of the SMTP server for Gitlab.";
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ File containing the password of the SMTP server for Gitlab.
+
+ This should be a string, not a nix path, since nix paths
+ are copied into the world-readable nix store.
+ '';
+ };
+
+ domain = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "HELO domain to use for outgoing mail.";
+ };
+
+ authentication = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "Authentitcation type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
+ };
+
+ enableStartTLSAuto = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to try to use StartTLS.";
+ };
+
+ opensslVerifyMode = mkOption {
+ type = types.str;
+ default = "peer";
+ description = "How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
+ };
+ };
+
+ secrets.secretFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ A file containing the secret used to encrypt variables in
+ the DB. If you change or lose this key you will be unable to
+ access variables stored in database.
+
+ Make sure the secret is at least 30 characters and all random,
+ no regular words or you'll be exposed to dictionary attacks.
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ secrets.dbFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ A file containing the secret used to encrypt variables in
+ the DB. If you change or lose this key you will be unable to
+ access variables stored in database.
+
+ Make sure the secret is at least 30 characters and all random,
+ no regular words or you'll be exposed to dictionary attacks.
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ secrets.otpFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ A file containing the secret used to encrypt secrets for OTP
+ tokens. If you change or lose this key, users which have 2FA
+ enabled for login won't be able to login anymore.
+
+ Make sure the secret is at least 30 characters and all random,
+ no regular words or you'll be exposed to dictionary attacks.
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ secrets.jwsFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ A file containing the secret used to encrypt session
+ keys. If you change or lose this key, users will be
+ disconnected.
+
+ Make sure the secret is an RSA private key in PEM format. You can
+ generate one with
+
+ openssl genrsa 2048
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ example = literalExample ''
+ {
+ gitlab = {
+ default_projects_features = {
+ builds = false;
+ };
+ };
+ omniauth = {
+ enabled = true;
+ auto_sign_in_with_provider = "openid_connect";
+ allow_single_sign_on = ["openid_connect"];
+ block_auto_created_users = false;
+ providers = [
+ {
+ name = "openid_connect";
+ label = "OpenID Connect";
+ args = {
+ name = "openid_connect";
+ scope = ["openid" "profile"];
+ response_type = "code";
+ issuer = "https://keycloak.example.com/auth/realms/My%20Realm";
+ discovery = true;
+ client_auth_method = "query";
+ uid_field = "preferred_username";
+ client_options = {
+ identifier = "gitlab";
+ secret = { _secret = "/var/keys/gitlab_oidc_secret"; };
+ redirect_uri = "https://git.example.com/users/auth/openid_connect/callback";
+ };
+ };
+ }
+ ];
+ };
+ };
+ '';
+ description = ''
+ Extra options to be added under
+ <literal>production</literal> in
+ <filename>config/gitlab.yml</filename>, as a nix attribute
+ set.
+
+ Options containing secret data should be set to an attribute
+ set containing the attribute <literal>_secret</literal> - a
+ string pointing to a file containing the value the option
+ should be set to. See the example to get a better picture of
+ this: in the resulting
+ <filename>config/gitlab.yml</filename> file, the
+ <literal>production.omniauth.providers[0].args.client_options.secret</literal>
+ key will be set to the contents of the
+ <filename>/var/keys/gitlab_oidc_secret</filename> file.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ {
+ assertion = cfg.databaseCreateLocally -> (cfg.user == cfg.databaseUsername);
+ message = "For local automatic database provisioning services.gitlab.user and services.gitlab.databaseUsername should be identical.";
+ }
+ {
+ assertion = (cfg.databaseHost != "") -> (cfg.databasePasswordFile != null);
+ message = "When services.gitlab.databaseHost is customized, services.gitlab.databasePasswordFile must be set!";
+ }
+ {
+ assertion = cfg.initialRootPasswordFile != null;
+ message = "services.gitlab.initialRootPasswordFile must be set!";
+ }
+ {
+ assertion = cfg.secrets.secretFile != null;
+ message = "services.gitlab.secrets.secretFile must be set!";
+ }
+ {
+ assertion = cfg.secrets.dbFile != null;
+ message = "services.gitlab.secrets.dbFile must be set!";
+ }
+ {
+ assertion = cfg.secrets.otpFile != null;
+ message = "services.gitlab.secrets.otpFile must be set!";
+ }
+ {
+ assertion = cfg.secrets.jwsFile != null;
+ message = "services.gitlab.secrets.jwsFile must be set!";
+ }
+ ];
+
+ environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
+
+ # Redis is required for the sidekiq queue runner.
+ services.redis.enable = mkDefault true;
+
+ # We use postgres as the main data store.
+ services.postgresql = optionalAttrs cfg.databaseCreateLocally {
+ enable = true;
+ ensureUsers = singleton { name = cfg.databaseUsername; };
+ };
+ # The postgresql module doesn't currently support concepts like
+ # objects owners and extensions; for now we tack on what's needed
+ # here.
+ systemd.services.postgresql.postStart = mkAfter (optionalString cfg.databaseCreateLocally ''
+ $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
+ current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
+ if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
+ $PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
+ if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then
+ echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..."
+ exit 1
+ fi
+ touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
+ $PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
+ rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
+ fi
+ $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
+ '');
+
+ # Use postfix to send out mails.
+ services.postfix.enable = mkDefault true;
+
+ users.users = [
+ { name = cfg.user;
+ group = cfg.group;
+ home = "${cfg.statePath}/home";
+ shell = "${pkgs.bash}/bin/bash";
+ uid = config.ids.uids.gitlab;
+ }
+ ];
+
+ users.groups = [
+ { name = cfg.group;
+ gid = config.ids.gids.gitlab;
+ }
+ ];
+
+ systemd.tmpfiles.rules = [
+ "d /run/gitlab 0755 ${cfg.user} ${cfg.group} -"
+ "d ${gitlabEnv.HOME} 0750 ${cfg.user} ${cfg.group} -"
+ "z ${gitlabEnv.HOME}/.ssh/authorized_keys 0600 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.backupPath} 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath} 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/builds 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/config 0750 ${cfg.user} ${cfg.group} -"
+ "D ${cfg.statePath}/config/initializers 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/db 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/log 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/repositories 2770 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/shell 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/tmp 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/tmp/pids 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/tmp/sockets 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/uploads 0700 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/custom_hooks 0700 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/custom_hooks/pre-receive.d 0700 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/custom_hooks/post-receive.d 0700 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath}/custom_hooks/update.d 0700 ${cfg.user} ${cfg.group} -"
+ "d ${gitlabConfig.production.shared.path} 0750 ${cfg.user} ${cfg.group} -"
+ "d ${gitlabConfig.production.shared.path}/artifacts 0750 ${cfg.user} ${cfg.group} -"
+ "d ${gitlabConfig.production.shared.path}/lfs-objects 0750 ${cfg.user} ${cfg.group} -"
+ "d ${gitlabConfig.production.shared.path}/pages 0750 ${cfg.user} ${cfg.group} -"
+ "L+ ${cfg.statePath}/lib - - - - ${cfg.packages.gitlab}/share/gitlab/lib"
+ "L+ /run/gitlab/config - - - - ${cfg.statePath}/config"
+ "L+ /run/gitlab/log - - - - ${cfg.statePath}/log"
+ "L+ /run/gitlab/tmp - - - - ${cfg.statePath}/tmp"
+ "L+ /run/gitlab/uploads - - - - ${cfg.statePath}/uploads"
+
+ "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}"
+
+ "L+ ${cfg.statePath}/config/unicorn.rb - - - - ${./defaultUnicornConfig.rb}"
+ "L+ ${cfg.statePath}/config/initializers/extra-gitlab.rb - - - - ${extraGitlabRb}"
+ ];
+
+ systemd.services.gitlab-sidekiq = {
+ after = [ "network.target" "redis.service" "gitlab.service" ];
+ wantedBy = [ "multi-user.target" ];
+ environment = gitlabEnv;
+ path = with pkgs; [
+ config.services.postgresql.package
+ gitAndTools.git
+ ruby
+ openssh
+ nodejs
+ gnupg
+
+ # Needed for GitLab project imports
+ gnutar
+ gzip
+ ];
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ TimeoutSec = "infinity";
+ Restart = "on-failure";
+ WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
+ ExecStart="${cfg.packages.gitlab.rubyEnv}/bin/sidekiq -C \"${cfg.packages.gitlab}/share/gitlab/config/sidekiq_queues.yml\" -e production -P ${cfg.statePath}/tmp/sidekiq.pid";
+ };
+ };
+
+ systemd.services.gitaly = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [
+ openssh
+ procps # See https://gitlab.com/gitlab-org/gitaly/issues/1562
+ gitAndTools.git
+ cfg.packages.gitaly.rubyEnv
+ cfg.packages.gitaly.rubyEnv.wrappedRuby
+ gzip
+ bzip2
+ ];
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ TimeoutSec = "infinity";
+ Restart = "on-failure";
+ WorkingDirectory = gitlabEnv.HOME;
+ ExecStart = "${cfg.packages.gitaly}/bin/gitaly ${gitalyToml}";
+ };
+ };
+
+ systemd.services.gitlab-workhorse = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [
+ exiftool
+ gitAndTools.git
+ gnutar
+ gzip
+ openssh
+ gitlab-workhorse
+ ];
+ serviceConfig = {
+ PermissionsStartOnly = true; # preStart must be run as root
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ TimeoutSec = "infinity";
+ Restart = "on-failure";
+ WorkingDirectory = gitlabEnv.HOME;
+ ExecStart =
+ "${cfg.packages.gitlab-workhorse}/bin/gitlab-workhorse "
+ + "-listenUmask 0 "
+ + "-listenNetwork unix "
+ + "-listenAddr /run/gitlab/gitlab-workhorse.socket "
+ + "-authSocket ${gitlabSocket} "
+ + "-documentRoot ${cfg.packages.gitlab}/share/gitlab/public "
+ + "-secretPath ${cfg.statePath}/.gitlab_workhorse_secret";
+ };
+ };
+
+ systemd.services.gitlab = {
+ after = [ "gitlab-workhorse.service" "gitaly.service" "network.target" "postgresql.service" "redis.service" ];
+ requires = [ "gitlab-sidekiq.service" ];
+ wantedBy = [ "multi-user.target" ];
+ environment = gitlabEnv;
+ path = with pkgs; [
+ config.services.postgresql.package
+ gitAndTools.git
+ openssh
+ nodejs
+ procps
+ gnupg
+ ];
+ preStart = ''
+ cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
+ rm -rf ${cfg.statePath}/db/*
+ cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
+ cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
+
+ ${cfg.packages.gitlab-shell}/bin/install
+
+ ${optionalString cfg.smtp.enable ''
+ install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
+ ${optionalString (cfg.smtp.passwordFile != null) ''
+ smtp_password=$(<'${cfg.smtp.passwordFile}')
+ ${pkgs.replace}/bin/replace-literal -e '@smtpPassword@' "$smtp_password" '${cfg.statePath}/config/initializers/smtp_settings.rb'
+ ''}
+ ''}
+
+ (
+ umask u=rwx,g=,o=
+
+ ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
+
+ ${if cfg.databasePasswordFile != null then ''
+ export db_password="$(<'${cfg.databasePasswordFile}')"
+
+ if [[ -z "$db_password" ]]; then
+ >&2 echo "Database password was an empty string!"
+ exit 1
+ fi
+
+ ${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
+ '.production.password = $ENV.db_password' \
+ >'${cfg.statePath}/config/database.yml'
+ ''
+ else ''
+ ${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
+ >'${cfg.statePath}/config/database.yml'
+ ''
+ }
+
+ ${utils.genJqSecretsReplacementSnippet
+ gitlabConfig
+ "${cfg.statePath}/config/gitlab.yml"
+ }
+
+ if [[ -h '${cfg.statePath}/config/secrets.yml' ]]; then
+ rm '${cfg.statePath}/config/secrets.yml'
+ fi
+
+ export secret="$(<'${cfg.secrets.secretFile}')"
+ export db="$(<'${cfg.secrets.dbFile}')"
+ export otp="$(<'${cfg.secrets.otpFile}')"
+ export jws="$(<'${cfg.secrets.jwsFile}')"
+ ${pkgs.jq}/bin/jq -n '{production: {secret_key_base: $ENV.secret,
+ otp_key_base: $ENV.otp,
+ db_key_base: $ENV.db,
+ openid_connect_signing_key: $ENV.jws}}' \
+ > '${cfg.statePath}/config/secrets.yml'
+ )
+
+ initial_root_password="$(<'${cfg.initialRootPasswordFile}')"
+ ${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \
+ GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}'
+
+ # We remove potentially broken links to old gitlab-shell versions
+ rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks
+
+ ${pkgs.git}/bin/git config --global core.autocrlf "input"
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ TimeoutSec = "infinity";
+ Restart = "on-failure";
+ WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
+ ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/unicorn -c ${cfg.statePath}/config/unicorn.rb -E production";
+ };
+
+ };
+
+ };
+
+ meta.doc = ./gitlab.xml;
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.xml b/nixpkgs/nixos/modules/services/misc/gitlab.xml
new file mode 100644
index 00000000000..b6171a9a194
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitlab.xml
@@ -0,0 +1,126 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="module-services-gitlab">
+ <title>Gitlab</title>
+ <para>
+ Gitlab is a feature-rich git hosting service.
+ </para>
+ <section xml:id="module-services-gitlab-prerequisites">
+ <title>Prerequisites</title>
+
+ <para>
+ The gitlab service exposes only an Unix socket at
+ <literal>/run/gitlab/gitlab-workhorse.socket</literal>. You need to
+ configure a webserver to proxy HTTP requests to the socket.
+ </para>
+
+ <para>
+ For instance, the following configuration could be used to use nginx as
+ frontend proxy:
+<programlisting>
+<link linkend="opt-services.nginx.enable">services.nginx</link> = {
+ <link linkend="opt-services.nginx.enable">enable</link> = true;
+ <link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true;
+ <link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true;
+ <link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true;
+ <link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true;
+ <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link>."git.example.com" = {
+ <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
+ <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
+ <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/".proxyPass</link> = "http://unix:/run/gitlab/gitlab-workhorse.socket";
+ };
+};
+</programlisting>
+ </para>
+ </section>
+ <section xml:id="module-services-gitlab-configuring">
+ <title>Configuring</title>
+
+ <para>
+ Gitlab depends on both PostgreSQL and Redis and will automatically enable
+ both services. In the case of PostgreSQL, a database and a role will be
+ created.
+ </para>
+
+ <para>
+ The default state dir is <literal>/var/gitlab/state</literal>. This is where
+ all data like the repositories and uploads will be stored.
+ </para>
+
+ <para>
+ A basic configuration with some custom settings could look like this:
+<programlisting>
+services.gitlab = {
+ <link linkend="opt-services.gitlab.enable">enable</link> = true;
+ <link linkend="opt-services.gitlab.databasePasswordFile">databasePasswordFile</link> = "/var/keys/gitlab/db_password";
+ <link linkend="opt-services.gitlab.initialRootPasswordFile">initialRootPasswordFile</link> = "/var/keys/gitlab/root_password";
+ <link linkend="opt-services.gitlab.https">https</link> = true;
+ <link linkend="opt-services.gitlab.host">host</link> = "git.example.com";
+ <link linkend="opt-services.gitlab.port">port</link> = 443;
+ <link linkend="opt-services.gitlab.user">user</link> = "git";
+ <link linkend="opt-services.gitlab.group">group</link> = "git";
+ smtp = {
+ <link linkend="opt-services.gitlab.smtp.enable">enable</link> = true;
+ <link linkend="opt-services.gitlab.smtp.address">address</link> = "localhost";
+ <link linkend="opt-services.gitlab.smtp.port">port</link> = 25;
+ };
+ secrets = {
+ <link linkend="opt-services.gitlab.secrets.dbFile">dbFile</link> = "/var/keys/gitlab/db";
+ <link linkend="opt-services.gitlab.secrets.secretFile">secretFile</link> = "/var/keys/gitlab/secret";
+ <link linkend="opt-services.gitlab.secrets.otpFile">otpFile</link> = "/var/keys/gitlab/otp";
+ <link linkend="opt-services.gitlab.secrets.jwsFile">jwsFile</link> = "/var/keys/gitlab/jws";
+ };
+ <link linkend="opt-services.gitlab.extraConfig">extraConfig</link> = {
+ gitlab = {
+ email_from = "gitlab-no-reply@example.com";
+ email_display_name = "Example GitLab";
+ email_reply_to = "gitlab-no-reply@example.com";
+ default_projects_features = { builds = false; };
+ };
+ };
+};
+</programlisting>
+ </para>
+
+ <para>
+ If you're setting up a new Gitlab instance, generate new
+ secrets. You for instance use <literal>tr -dc A-Za-z0-9 &lt;
+ /dev/urandom | head -c 128 &gt; /var/keys/gitlab/db</literal> to
+ generate a new db secret. Make sure the files can be read by, and
+ only by, the user specified by <link
+ linkend="opt-services.gitlab.user">services.gitlab.user</link>. Gitlab
+ encrypts sensitive data stored in the database. If you're restoring
+ an existing Gitlab instance, you must specify the secrets secret
+ from <literal>config/secrets.yml</literal> located in your Gitlab
+ state folder.
+ </para>
+
+ <para>
+ Refer to <xref linkend="ch-options" /> for all available configuration
+ options for the
+ <link linkend="opt-services.gitlab.enable">services.gitlab</link> module.
+ </para>
+ </section>
+ <section xml:id="module-services-gitlab-maintenance">
+ <title>Maintenance</title>
+
+ <para>
+ You can run Gitlab's rake tasks with <literal>gitlab-rake</literal> which
+ will be available on the system when gitlab is enabled. You will have to run
+ the command as the user that you configured to run gitlab with.
+ </para>
+
+ <para>
+ For example, to backup a Gitlab instance:
+<screen>
+<prompt>$ </prompt>sudo -u git -H gitlab-rake gitlab:backup:create
+</screen>
+ A list of all availabe rake tasks can be obtained by running:
+<screen>
+<prompt>$ </prompt>sudo -u git -H gitlab-rake -T
+</screen>
+ </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/gitolite.nix b/nixpkgs/nixos/modules/services/misc/gitolite.nix
new file mode 100644
index 00000000000..cc69f81bbcc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitolite.nix
@@ -0,0 +1,231 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gitolite;
+ # Use writeTextDir to not leak Nix store hash into file name
+ pubkeyFile = (pkgs.writeTextDir "gitolite-admin.pub" cfg.adminPubkey) + "/gitolite-admin.pub";
+ hooks = lib.concatMapStrings (hook: "${hook} ") cfg.commonHooks;
+in
+{
+ options = {
+ services.gitolite = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable gitolite management under the
+ <literal>gitolite</literal> user. After
+ switching to a configuration with Gitolite enabled, you can
+ then run <literal>git clone
+ gitolite@host:gitolite-admin.git</literal> to manage it further.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/gitolite";
+ description = ''
+ Gitolite home directory (used to store all the repositories).
+ '';
+ };
+
+ adminPubkey = mkOption {
+ type = types.str;
+ description = ''
+ Initial administrative public key for Gitolite. This should
+ be an SSH Public Key. Note that this key will only be used
+ once, upon the first initialization of the Gitolite user.
+ The key string cannot have any line breaks in it.
+ '';
+ };
+
+ enableGitAnnex = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable git-annex support. Uses the <literal>extraGitoliteRc</literal> option
+ to apply the necessary configuration.
+ '';
+ };
+
+ commonHooks = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ A list of custom git hooks that get copied to <literal>~/.gitolite/hooks/common</literal>.
+ '';
+ };
+
+ extraGitoliteRc = mkOption {
+ type = types.lines;
+ default = "";
+ example = literalExample ''
+ $RC{UMASK} = 0027;
+ $RC{SITE_INFO} = 'This is our private repository host';
+ push( @{$RC{ENABLE}}, 'Kindergarten' ); # enable the command/feature
+ @{$RC{ENABLE}} = grep { $_ ne 'desc' } @{$RC{ENABLE}}; # disable the command/feature
+ '';
+ description = ''
+ Extra configuration to append to the default <literal>~/.gitolite.rc</literal>.
+
+ This should be Perl code that modifies the <literal>%RC</literal>
+ configuration variable. The default <literal>~/.gitolite.rc</literal>
+ content is generated by invoking <literal>gitolite print-default-rc</literal>,
+ and extra configuration from this option is appended to it. The result
+ is placed to Nix store, and the <literal>~/.gitolite.rc</literal> file
+ becomes a symlink to it.
+
+ If you already have a customized (or otherwise changed)
+ <literal>~/.gitolite.rc</literal> file, NixOS will refuse to replace
+ it with a symlink, and the `gitolite-init` initialization service
+ will fail. In this situation, in order to use this option, you
+ will need to take any customizations you may have in
+ <literal>~/.gitolite.rc</literal>, convert them to appropriate Perl
+ statements, add them to this option, and remove the file.
+
+ See also the <literal>enableGitAnnex</literal> option.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "gitolite";
+ description = ''
+ Gitolite user account. This is the username of the gitolite endpoint.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "gitolite";
+ description = ''
+ Primary group of the Gitolite user account.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (
+ let
+ manageGitoliteRc = cfg.extraGitoliteRc != "";
+ rcDir = pkgs.runCommand "gitolite-rc" { preferLocalBuild = true; } rcDirScript;
+ rcDirScript =
+ ''
+ mkdir "$out"
+ export HOME=temp-home
+ mkdir -p "$HOME/.gitolite/logs" # gitolite can't run without it
+ '${pkgs.gitolite}'/bin/gitolite print-default-rc >>"$out/gitolite.rc.default"
+ cat <<END >>"$out/gitolite.rc"
+ # This file is managed by NixOS.
+ # Use services.gitolite options to control it.
+
+ END
+ cat "$out/gitolite.rc.default" >>"$out/gitolite.rc"
+ '' +
+ optionalString (cfg.extraGitoliteRc != "") ''
+ echo -n ${escapeShellArg ''
+
+ # Added by NixOS:
+ ${removeSuffix "\n" cfg.extraGitoliteRc}
+
+ # per perl rules, this should be the last line in such a file:
+ 1;
+ ''} >>"$out/gitolite.rc"
+ '';
+ in {
+ services.gitolite.extraGitoliteRc = optionalString cfg.enableGitAnnex ''
+ # Enable git-annex support:
+ push( @{$RC{ENABLE}}, 'git-annex-shell ua');
+ '';
+
+ users.users.${cfg.user} = {
+ description = "Gitolite user";
+ home = cfg.dataDir;
+ uid = config.ids.uids.gitolite;
+ group = cfg.group;
+ useDefaultShell = true;
+ };
+ users.groups.${cfg.group}.gid = config.ids.gids.gitolite;
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.dataDir}'/.gitolite - ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.dataDir}'/.gitolite/logs - ${cfg.user} ${cfg.group} - -"
+
+ "Z ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.gitolite-init = {
+ description = "Gitolite initialization";
+ wantedBy = [ "multi-user.target" ];
+ unitConfig.RequiresMountsFor = cfg.dataDir;
+
+ environment = {
+ GITOLITE_RC = ".gitolite.rc";
+ GITOLITE_RC_DEFAULT = "${rcDir}/gitolite.rc.default";
+ };
+
+ serviceConfig = {
+ Type = "oneshot";
+ User = cfg.user;
+ Group = cfg.group;
+ WorkingDirectory = "~";
+ RemainAfterExit = true;
+ };
+
+ path = [ pkgs.gitolite pkgs.git pkgs.perl pkgs.bash pkgs.diffutils config.programs.ssh.package ];
+ script =
+ let
+ rcSetupScriptIfCustomFile =
+ if manageGitoliteRc then ''
+ cat <<END
+ <3>ERROR: NixOS can't apply declarative configuration
+ <3>to your .gitolite.rc file, because it seems to be
+ <3>already customized manually.
+ <3>See the services.gitolite.extraGitoliteRc option
+ <3>in "man configuration.nix" for more information.
+ END
+ # Not sure if the line below addresses the issue directly or just
+ # adds a delay, but without it our error message often doesn't
+ # show up in `systemctl status gitolite-init`.
+ journalctl --flush
+ exit 1
+ '' else ''
+ :
+ '';
+ rcSetupScriptIfDefaultFileOrStoreSymlink =
+ if manageGitoliteRc then ''
+ ln -sf "${rcDir}/gitolite.rc" "$GITOLITE_RC"
+ '' else ''
+ [[ -L "$GITOLITE_RC" ]] && rm -f "$GITOLITE_RC"
+ '';
+ in
+ ''
+ if ( [[ ! -e "$GITOLITE_RC" ]] && [[ ! -L "$GITOLITE_RC" ]] ) ||
+ ( [[ -f "$GITOLITE_RC" ]] && diff -q "$GITOLITE_RC" "$GITOLITE_RC_DEFAULT" >/dev/null ) ||
+ ( [[ -L "$GITOLITE_RC" ]] && [[ "$(readlink "$GITOLITE_RC")" =~ ^/nix/store/ ]] )
+ then
+ '' + rcSetupScriptIfDefaultFileOrStoreSymlink +
+ ''
+ else
+ '' + rcSetupScriptIfCustomFile +
+ ''
+ fi
+
+ if [ ! -d repositories ]; then
+ gitolite setup -pk ${pubkeyFile}
+ fi
+ if [ -n "${hooks}" ]; then
+ cp -f ${hooks} .gitolite/hooks/common/
+ chmod +x .gitolite/hooks/common/*
+ fi
+ gitolite setup # Upgrade if needed
+ '';
+ };
+
+ environment.systemPackages = [ pkgs.gitolite pkgs.git ]
+ ++ optional cfg.enableGitAnnex pkgs.gitAndTools.git-annex;
+ });
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gitweb.nix b/nixpkgs/nixos/modules/services/misc/gitweb.nix
new file mode 100644
index 00000000000..ca21366b779
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitweb.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gitweb;
+
+in
+{
+
+ options.services.gitweb = {
+
+ projectroot = mkOption {
+ default = "/srv/git";
+ type = types.path;
+ description = ''
+ Path to git projects (bare repositories) that should be served by
+ gitweb. Must not end with a slash.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Verbatim configuration text appended to the generated gitweb.conf file.
+ '';
+ example = ''
+ $feature{'highlight'}{'default'} = [1];
+ $feature{'ctags'}{'default'} = [1];
+ $feature{'avatar'}{'default'} = ['gravatar'];
+ '';
+ };
+
+ gitwebTheme = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Use an alternative theme for gitweb, strongly inspired by GitHub.
+ '';
+ };
+
+ gitwebConfigFile = mkOption {
+ default = pkgs.writeText "gitweb.conf" ''
+ # path to git projects (<project>.git)
+ $projectroot = "${cfg.projectroot}";
+ $highlight_bin = "${pkgs.highlight}/bin/highlight";
+ ${cfg.extraConfig}
+ '';
+ type = types.path;
+ readOnly = true;
+ internal = true;
+ };
+
+ };
+
+ meta.maintainers = with maintainers; [ gnidorah ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gogs.nix b/nixpkgs/nixos/modules/services/misc/gogs.nix
new file mode 100644
index 00000000000..ee99967c261
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gogs.nix
@@ -0,0 +1,279 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gogs;
+ configFile = pkgs.writeText "app.ini" ''
+ APP_NAME = ${cfg.appName}
+ RUN_USER = ${cfg.user}
+ RUN_MODE = prod
+
+ [database]
+ DB_TYPE = ${cfg.database.type}
+ HOST = ${cfg.database.host}:${toString cfg.database.port}
+ NAME = ${cfg.database.name}
+ USER = ${cfg.database.user}
+ PASSWD = #dbpass#
+ PATH = ${cfg.database.path}
+
+ [repository]
+ ROOT = ${cfg.repositoryRoot}
+
+ [server]
+ DOMAIN = ${cfg.domain}
+ HTTP_ADDR = ${cfg.httpAddress}
+ HTTP_PORT = ${toString cfg.httpPort}
+ ROOT_URL = ${cfg.rootUrl}
+ STATIC_ROOT_PATH = ${cfg.staticRootPath}
+
+ [session]
+ COOKIE_NAME = session
+ COOKIE_SECURE = ${boolToString cfg.cookieSecure}
+
+ [security]
+ SECRET_KEY = #secretkey#
+ INSTALL_LOCK = true
+
+ [log]
+ ROOT_PATH = ${cfg.stateDir}/log
+
+ ${cfg.extraConfig}
+ '';
+in
+
+{
+ options = {
+ services.gogs = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Enable Go Git Service.";
+ };
+
+ useWizard = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Do not generate a configuration and use Gogs' installation wizard instead. The first registered user will be administrator.";
+ };
+
+ stateDir = mkOption {
+ default = "/var/lib/gogs";
+ type = types.str;
+ description = "Gogs data directory.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "gogs";
+ description = "User account under which Gogs runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "gogs";
+ description = "Group account under which Gogs runs.";
+ };
+
+ database = {
+ type = mkOption {
+ type = types.enum [ "sqlite3" "mysql" "postgres" ];
+ example = "mysql";
+ default = "sqlite3";
+ description = "Database engine to use.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 3306;
+ description = "Database host port.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "gogs";
+ description = "Database name.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "gogs";
+ description = "Database user.";
+ };
+
+ password = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ The password corresponding to <option>database.user</option>.
+ Warning: this is stored in cleartext in the Nix store!
+ Use <option>database.passwordFile</option> instead.
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/gogs-dbpassword";
+ description = ''
+ A file containing the password corresponding to
+ <option>database.user</option>.
+ '';
+ };
+
+ path = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/data/gogs.db";
+ description = "Path to the sqlite3 database file.";
+ };
+ };
+
+ appName = mkOption {
+ type = types.str;
+ default = "Gogs: Go Git Service";
+ description = "Application name.";
+ };
+
+ repositoryRoot = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/repositories";
+ description = "Path to the git repositories.";
+ };
+
+ domain = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Domain name of your server.";
+ };
+
+ rootUrl = mkOption {
+ type = types.str;
+ default = "http://localhost:3000/";
+ description = "Full public URL of Gogs server.";
+ };
+
+ httpAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = "HTTP listen address.";
+ };
+
+ httpPort = mkOption {
+ type = types.int;
+ default = 3000;
+ description = "HTTP listen port.";
+ };
+
+ cookieSecure = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Marks session cookies as "secure" as a hint for browsers to only send
+ them via HTTPS. This option is recommend, if Gogs is being served over HTTPS.
+ '';
+ };
+
+ staticRootPath = mkOption {
+ type = types.str;
+ default = "${pkgs.gogs.data}";
+ example = "/var/lib/gogs/data";
+ description = "Upper level of template and static files path.";
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = "Configuration lines appended to the generated Gogs configuration file.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.gogs = {
+ description = "Gogs (Go Git Service)";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.gogs.bin ];
+
+ preStart = let
+ runConfig = "${cfg.stateDir}/custom/conf/app.ini";
+ secretKey = "${cfg.stateDir}/custom/conf/secret_key";
+ in ''
+ mkdir -p ${cfg.stateDir}
+
+ # copy custom configuration and generate a random secret key if needed
+ ${optionalString (cfg.useWizard == false) ''
+ mkdir -p ${cfg.stateDir}/custom/conf
+ cp -f ${configFile} ${runConfig}
+
+ if [ ! -e ${secretKey} ]; then
+ head -c 16 /dev/urandom | base64 > ${secretKey}
+ fi
+
+ KEY=$(head -n1 ${secretKey})
+ DBPASS=$(head -n1 ${cfg.database.passwordFile})
+ sed -e "s,#secretkey#,$KEY,g" \
+ -e "s,#dbpass#,$DBPASS,g" \
+ -i ${runConfig}
+ chmod 440 ${runConfig} ${secretKey}
+ ''}
+
+ mkdir -p ${cfg.repositoryRoot}
+ # update all hooks' binary paths
+ HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 4 -type f -wholename "*git/hooks/*")
+ if [ "$HOOKS" ]
+ then
+ sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gogs,${pkgs.gogs.bin}/bin/gogs,g' $HOOKS
+ sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS
+ sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS
+ sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS
+ fi
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ WorkingDirectory = cfg.stateDir;
+ ExecStart = "${pkgs.gogs.bin}/bin/gogs web";
+ Restart = "always";
+ };
+
+ environment = {
+ USER = cfg.user;
+ HOME = cfg.stateDir;
+ GOGS_WORK_DIR = cfg.stateDir;
+ };
+ };
+
+ users = mkIf (cfg.user == "gogs") {
+ users.gogs = {
+ description = "Go Git Service";
+ uid = config.ids.uids.gogs;
+ group = "gogs";
+ home = cfg.stateDir;
+ createHome = true;
+ shell = pkgs.bash;
+ };
+ groups.gogs.gid = config.ids.gids.gogs;
+ };
+
+ warnings = optional (cfg.database.password != "")
+ ''config.services.gogs.database.password will be stored as plaintext
+ in the Nix store. Use database.passwordFile instead.'';
+
+ # Create database passwordFile default when password is configured.
+ services.gogs.database.passwordFile =
+ (mkDefault (toString (pkgs.writeTextFile {
+ name = "gogs-database-password";
+ text = cfg.database.password;
+ })));
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gollum.nix b/nixpkgs/nixos/modules/services/misc/gollum.nix
new file mode 100644
index 00000000000..7653b415bf0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gollum.nix
@@ -0,0 +1,110 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gollum;
+in
+
+{
+ options.services.gollum = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the Gollum service.";
+ };
+
+ address = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = "IP address on which the web server will listen.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 4567;
+ description = "Port on which the web server will run.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Content of the configuration file";
+ };
+
+ mathjax = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable support for math rendering using MathJax";
+ };
+
+ allowUploads = mkOption {
+ type = types.nullOr (types.enum [ "dir" "page" ]);
+ default = null;
+ description = "Enable uploads of external files";
+ };
+
+ emoji = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Parse and interpret emoji tags";
+ };
+
+ branch = mkOption {
+ type = types.str;
+ default = "master";
+ example = "develop";
+ description = "Git branch to serve";
+ };
+
+ stateDir = mkOption {
+ type = types.path;
+ default = "/var/lib/gollum";
+ description = "Specifies the path of the repository directory. If it does not exist, Gollum will create it on startup.";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users.gollum = {
+ group = config.users.users.gollum.name;
+ description = "Gollum user";
+ createHome = false;
+ };
+
+ users.groups.gollum = { };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.stateDir}' - ${config.users.users.gollum.name} ${config.users.groups.gollum.name} - -"
+ ];
+
+ systemd.services.gollum = {
+ description = "Gollum wiki";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.git ];
+
+ preStart = ''
+ # This is safe to be run on an existing repo
+ git init ${cfg.stateDir}
+ '';
+
+ serviceConfig = {
+ User = config.users.users.gollum.name;
+ Group = config.users.groups.gollum.name;
+ ExecStart = ''
+ ${pkgs.gollum}/bin/gollum \
+ --port ${toString cfg.port} \
+ --host ${cfg.address} \
+ --config ${builtins.toFile "gollum-config.rb" cfg.extraConfig} \
+ --ref ${cfg.branch} \
+ ${optionalString cfg.mathjax "--mathjax"} \
+ ${optionalString cfg.emoji "--emoji"} \
+ ${optionalString (cfg.allowUploads != null) "--allow-uploads ${cfg.allowUploads}"} \
+ ${cfg.stateDir}
+ '';
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gpsd.nix b/nixpkgs/nixos/modules/services/misc/gpsd.nix
new file mode 100644
index 00000000000..3bfcb636a3c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gpsd.nix
@@ -0,0 +1,119 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ uid = config.ids.uids.gpsd;
+ gid = config.ids.gids.gpsd;
+ cfg = config.services.gpsd;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gpsd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable `gpsd', a GPS service daemon.
+ '';
+ };
+
+ device = mkOption {
+ type = types.str;
+ default = "/dev/ttyUSB0";
+ description = ''
+ A device may be a local serial device for GPS input, or a URL of the form:
+ <literal>[{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]</literal>
+ in which case it specifies an input source for DGPS or ntrip data.
+ '';
+ };
+
+ readonly = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable the broken-device-safety, otherwise
+ known as read-only mode. Some popular bluetooth and USB
+ receivers lock up or become totally inaccessible when
+ probed or reconfigured. This switch prevents gpsd from
+ writing to a receiver. This means that gpsd cannot
+ configure the receiver for optimal performance, but it
+ also means that gpsd cannot break the receiver. A better
+ solution would be for Bluetooth to not be so fragile. A
+ platform independent method to identify
+ serial-over-Bluetooth devices would also be nice.
+ '';
+ };
+
+ nowait = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ don't wait for client connects to poll GPS
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 2947;
+ description = ''
+ The port where to listen for TCP connections.
+ '';
+ };
+
+ debugLevel = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ The debugging level.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton
+ { name = "gpsd";
+ inherit uid;
+ description = "gpsd daemon user";
+ home = "/var/empty";
+ };
+
+ users.groups = singleton
+ { name = "gpsd";
+ inherit gid;
+ };
+
+ systemd.services.gpsd = {
+ description = "GPSD daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = ''
+ ${pkgs.gpsd}/sbin/gpsd -D "${toString cfg.debugLevel}" \
+ -S "${toString cfg.port}" \
+ ${optionalString cfg.readonly "-b"} \
+ ${optionalString cfg.nowait "-n"} \
+ "${cfg.device}"
+ '';
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/greenclip.nix b/nixpkgs/nixos/modules/services/misc/greenclip.nix
new file mode 100644
index 00000000000..9152a782d7f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/greenclip.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.greenclip;
+in {
+
+ options.services.greenclip = {
+ enable = mkEnableOption "Greenclip daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.haskellPackages.greenclip;
+ defaultText = "pkgs.haskellPackages.greenclip";
+ description = "greenclip derivation to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.greenclip = {
+ enable = true;
+ description = "greenclip daemon";
+ wantedBy = [ "graphical-session.target" ];
+ after = [ "graphical-session.target" ];
+ serviceConfig.ExecStart = "${cfg.package}/bin/greenclip daemon";
+ };
+
+ environment.systemPackages = [ cfg.package ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/headphones.nix b/nixpkgs/nixos/modules/services/misc/headphones.nix
new file mode 100644
index 00000000000..4a77045be28
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/headphones.nix
@@ -0,0 +1,87 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ name = "headphones";
+
+ cfg = config.services.headphones;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.headphones = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the headphones server.";
+ };
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/${name}";
+ description = "Path where to store data files.";
+ };
+ configFile = mkOption {
+ type = types.path;
+ default = "${cfg.dataDir}/config.ini";
+ description = "Path to config file.";
+ };
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Host to listen on.";
+ };
+ port = mkOption {
+ type = types.ints.u16;
+ default = 8181;
+ description = "Port to bind to.";
+ };
+ user = mkOption {
+ type = types.str;
+ default = name;
+ description = "User to run the service as";
+ };
+ group = mkOption {
+ type = types.str;
+ default = name;
+ description = "Group to run the service as";
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = optionalAttrs (cfg.user == name) (singleton {
+ name = name;
+ uid = config.ids.uids.headphones;
+ group = cfg.group;
+ description = "headphones user";
+ home = cfg.dataDir;
+ createHome = true;
+ });
+
+ users.groups = optionalAttrs (cfg.group == name) (singleton {
+ name = name;
+ gid = config.ids.gids.headphones;
+ });
+
+ systemd.services.headphones = {
+ description = "Headphones Server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${pkgs.headphones}/bin/headphones --datadir ${cfg.dataDir} --config ${cfg.configFile} --host ${cfg.host} --port ${toString cfg.port}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/home-assistant.nix b/nixpkgs/nixos/modules/services/misc/home-assistant.nix
new file mode 100644
index 00000000000..74702c97f55
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/home-assistant.nix
@@ -0,0 +1,250 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.home-assistant;
+
+ # cfg.config != null can be assumed here
+ configJSON = pkgs.writeText "configuration.json"
+ (builtins.toJSON (if cfg.applyDefaultConfig then
+ (recursiveUpdate defaultConfig cfg.config) else cfg.config));
+ configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
+ ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
+ '';
+
+ lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json"
+ (builtins.toJSON cfg.lovelaceConfig);
+ lovelaceConfigFile = pkgs.runCommand "ui-lovelace.yaml" { preferLocalBuild = true; } ''
+ ${pkgs.remarshal}/bin/json2yaml -i ${lovelaceConfigJSON} -o $out
+ '';
+
+ availableComponents = cfg.package.availableComponents;
+
+ usedPlatforms = config:
+ if isAttrs config then
+ optional (config ? platform) config.platform
+ ++ concatMap usedPlatforms (attrValues config)
+ else if isList config then
+ concatMap usedPlatforms config
+ else [ ];
+
+ # Given a component "platform", looks up whether it is used in the config
+ # as `platform = "platform";`.
+ #
+ # For example, the component mqtt.sensor is used as follows:
+ # config.sensor = [ {
+ # platform = "mqtt";
+ # ...
+ # } ];
+ useComponentPlatform = component: elem component (usedPlatforms cfg.config);
+
+ # Returns whether component is used in config
+ useComponent = component:
+ hasAttrByPath (splitString "." component) cfg.config
+ || useComponentPlatform component;
+
+ # List of components used in config
+ extraComponents = filter useComponent availableComponents;
+
+ package = if (cfg.autoExtraComponents && cfg.config != null)
+ then (cfg.package.override { inherit extraComponents; })
+ else cfg.package;
+
+ # If you are changing this, please update the description in applyDefaultConfig
+ defaultConfig = {
+ homeassistant.time_zone = config.time.timeZone;
+ http.server_port = cfg.port;
+ } // optionalAttrs (cfg.lovelaceConfig != null) {
+ lovelace.mode = "yaml";
+ };
+
+in {
+ meta.maintainers = with maintainers; [ dotlambda ];
+
+ options.services.home-assistant = {
+ enable = mkEnableOption "Home Assistant";
+
+ configDir = mkOption {
+ default = "/var/lib/hass";
+ type = types.path;
+ description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
+ };
+
+ port = mkOption {
+ default = 8123;
+ type = types.int;
+ description = "The port on which to listen.";
+ };
+
+ applyDefaultConfig = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Setting this option enables a few configuration options for HA based on NixOS configuration (such as time zone) to avoid having to manually specify configuration we already have.
+ </para>
+ <para>
+ Currently one side effect of enabling this is that the <literal>http</literal> component will be enabled.
+ </para>
+ <para>
+ This only takes effect if <literal>config != null</literal> in order to ensure that a manually managed <filename>configuration.yaml</filename> is not overwritten.
+ '';
+ };
+
+ config = mkOption {
+ default = null;
+ type = with types; nullOr attrs;
+ example = literalExample ''
+ {
+ homeassistant = {
+ name = "Home";
+ time_zone = "UTC";
+ };
+ frontend = { };
+ http = { };
+ feedreader.urls = [ "https://nixos.org/blogs.xml" ];
+ }
+ '';
+ description = ''
+ Your <filename>configuration.yaml</filename> as a Nix attribute set.
+ Beware that setting this option will delete your previous <filename>configuration.yaml</filename>.
+ '';
+ };
+
+ configWritable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to make <filename>configuration.yaml</filename> writable.
+ This only has an effect if <option>config</option> is set.
+ This will allow you to edit it from Home Assistant's web interface.
+ However, bear in mind that it will be overwritten at every start of the service.
+ '';
+ };
+
+ lovelaceConfig = mkOption {
+ default = null;
+ type = with types; nullOr attrs;
+ # from https://www.home-assistant.io/lovelace/yaml-mode/
+ example = literalExample ''
+ {
+ title = "My Awesome Home";
+ views = [ {
+ title = "Example";
+ cards = [ {
+ type = "markdown";
+ title = "Lovelace";
+ content = "Welcome to your **Lovelace UI**.";
+ } ];
+ } ];
+ }
+ '';
+ description = ''
+ Your <filename>ui-lovelace.yaml</filename> as a Nix attribute set.
+ Setting this option will automatically add
+ <literal>lovelace.mode = "yaml";</literal> to your <option>config</option>.
+ Beware that setting this option will delete your previous <filename>ui-lovelace.yaml</filename>
+ '';
+ };
+
+ lovelaceConfigWritable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to make <filename>ui-lovelace.yaml</filename> writable.
+ This only has an effect if <option>lovelaceConfig</option> is set.
+ This will allow you to edit it from Home Assistant's web interface.
+ However, bear in mind that it will be overwritten at every start of the service.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.home-assistant;
+ defaultText = "pkgs.home-assistant";
+ type = types.package;
+ example = literalExample ''
+ pkgs.home-assistant.override {
+ extraPackages = ps: with ps; [ colorlog ];
+ }
+ '';
+ description = ''
+ Home Assistant package to use.
+ Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies.
+ If you specify <option>config</option> and do not set <option>autoExtraComponents</option>
+ to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect.
+ '';
+ };
+
+ autoExtraComponents = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ If set to <literal>true</literal>, the components used in <literal>config</literal>
+ are set as the specified package's <literal>extraComponents</literal>.
+ This in turn adds all packaged dependencies to the derivation.
+ You might still see import errors in your log.
+ In this case, you will need to package the necessary dependencies yourself
+ or ask for someone else to package them.
+ If a dependency is packaged but not automatically added to this list,
+ you might need to specify it in <literal>extraPackages</literal>.
+ '';
+ };
+
+ openFirewall = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to open the firewall for the specified port.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+ systemd.services.home-assistant = {
+ description = "Home Assistant";
+ after = [ "network.target" ];
+ preStart = optionalString (cfg.config != null) (if cfg.configWritable then ''
+ cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml"
+ '' else ''
+ rm -f "${cfg.configDir}/configuration.yaml"
+ ln -s ${configFile} "${cfg.configDir}/configuration.yaml"
+ '') + optionalString (cfg.lovelaceConfig != null) (if cfg.lovelaceConfigWritable then ''
+ cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+ '' else ''
+ rm -f "${cfg.configDir}/ui-lovelace.yaml"
+ ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+ '');
+ serviceConfig = {
+ ExecStart = "${package}/bin/hass --config '${cfg.configDir}'";
+ User = "hass";
+ Group = "hass";
+ Restart = "on-failure";
+ ProtectSystem = "strict";
+ ReadWritePaths = "${cfg.configDir}";
+ KillSignal = "SIGINT";
+ PrivateTmp = true;
+ RemoveIPC = true;
+ AmbientCapabilities = "cap_net_raw,cap_net_admin+eip";
+ };
+ path = [
+ "/run/wrappers" # needed for ping
+ ];
+ };
+
+ systemd.targets.home-assistant = rec {
+ description = "Home Assistant";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "home-assistant.service" ];
+ after = wants;
+ };
+
+ users.users.hass = {
+ home = cfg.configDir;
+ createHome = true;
+ group = "hass";
+ uid = config.ids.uids.hass;
+ };
+
+ users.groups.hass.gid = config.ids.gids.hass;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/ihaskell.nix b/nixpkgs/nixos/modules/services/misc/ihaskell.nix
new file mode 100644
index 00000000000..11597706d0d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/ihaskell.nix
@@ -0,0 +1,62 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.ihaskell;
+ ihaskell = pkgs.ihaskell.override {
+ packages = self: cfg.extraPackages self;
+ };
+
+in
+
+{
+ options = {
+ services.ihaskell = {
+ enable = mkOption {
+ default = false;
+ description = "Autostart an IHaskell notebook service.";
+ };
+
+ extraPackages = mkOption {
+ default = self: [];
+ example = literalExample ''
+ haskellPackages: [
+ haskellPackages.wreq
+ haskellPackages.lens
+ ]
+ '';
+ description = ''
+ Extra packages available to ghc when running ihaskell. The
+ value must be a function which receives the attrset defined
+ in <varname>haskellPackages</varname> as the sole argument.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users.ihaskell = {
+ group = config.users.groups.ihaskell.name;
+ description = "IHaskell user";
+ home = "/var/lib/ihaskell";
+ createHome = true;
+ uid = config.ids.uids.ihaskell;
+ };
+
+ users.groups.ihaskell.gid = config.ids.gids.ihaskell;
+
+ systemd.services.ihaskell = {
+ description = "IHaskell notebook instance";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ User = config.users.users.ihaskell.name;
+ Group = config.users.groups.ihaskell.name;
+ ExecStart = "${pkgs.runtimeShell} -c \"cd $HOME;${ihaskell}/bin/ihaskell-notebook\"";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/irkerd.nix b/nixpkgs/nixos/modules/services/misc/irkerd.nix
new file mode 100644
index 00000000000..993d77ba424
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/irkerd.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.irkerd;
+ ports = [ 6659 ];
+in
+{
+ options.services.irkerd = {
+ enable = mkOption {
+ description = "Whether to enable irker, an IRC notification daemon.";
+ default = false;
+ type = types.bool;
+ };
+
+ openPorts = mkOption {
+ description = "Open ports in the firewall for irkerd";
+ default = false;
+ type = types.bool;
+ };
+
+ listenAddress = mkOption {
+ default = "localhost";
+ example = "0.0.0.0";
+ type = types.str;
+ description = ''
+ Specifies the bind address on which the irker daemon listens.
+ The default is localhost.
+
+ Irker authors strongly warn about the risks of running this on
+ a publicly accessible interface, so change this with caution.
+ '';
+ };
+
+ nick = mkOption {
+ default = "irker";
+ type = types.str;
+ description = "Nick to use for irker";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.irkerd = {
+ description = "Internet Relay Chat (IRC) notification daemon";
+ documentation = [ "man:irkerd(8)" "man:irkerhook(1)" "man:irk(1)" ];
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.irker}/bin/irkerd -H ${cfg.listenAddress} -n ${cfg.nick}";
+ User = "irkerd";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.irker ];
+
+ users.users.irkerd = {
+ description = "Irker daemon user";
+ isSystemUser = true;
+ group = "irkerd";
+ };
+ users.groups.irkerd = {};
+
+ networking.firewall.allowedTCPPorts = mkIf cfg.openPorts ports;
+ networking.firewall.allowedUDPPorts = mkIf cfg.openPorts ports;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/jackett.nix b/nixpkgs/nixos/modules/services/misc/jackett.nix
new file mode 100644
index 00000000000..f2dc6635df9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/jackett.nix
@@ -0,0 +1,82 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.jackett;
+
+in
+{
+ options = {
+ services.jackett = {
+ enable = mkEnableOption "Jackett";
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/jackett/.config/Jackett";
+ description = "The directory where Jackett stores its data files.";
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Open ports in the firewall for the Jackett web interface.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "jackett";
+ description = "User account under which Jackett runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "jackett";
+ description = "Group under which Jackett runs.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.jackett;
+ defaultText = "pkgs.jackett";
+ description = "Jackett package to use.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.jackett = {
+ description = "Jackett";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${cfg.package}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'";
+ Restart = "on-failure";
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ 9117 ];
+ };
+
+ users.users = mkIf (cfg.user == "jackett") {
+ jackett = {
+ group = cfg.group;
+ home = cfg.dataDir;
+ uid = config.ids.uids.jackett;
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "jackett") {
+ jackett.gid = config.ids.gids.jackett;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/jellyfin.nix b/nixpkgs/nixos/modules/services/misc/jellyfin.nix
new file mode 100644
index 00000000000..55559206568
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/jellyfin.nix
@@ -0,0 +1,54 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.jellyfin;
+in
+{
+ options = {
+ services.jellyfin = {
+ enable = mkEnableOption "Jellyfin Media Server";
+
+ user = mkOption {
+ type = types.str;
+ default = "jellyfin";
+ description = "User account under which Jellyfin runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "jellyfin";
+ description = "Group under which jellyfin runs.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.jellyfin = {
+ description = "Jellyfin Media Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = rec {
+ User = cfg.user;
+ Group = cfg.group;
+ StateDirectory = "jellyfin";
+ CacheDirectory = "jellyfin";
+ ExecStart = "${pkgs.jellyfin}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
+ Restart = "on-failure";
+ };
+ };
+
+ users.users = mkIf (cfg.user == "jellyfin") {
+ jellyfin.group = cfg.group;
+ };
+
+ users.groups = mkIf (cfg.group == "jellyfin") {
+ jellyfin = {};
+ };
+
+ };
+
+ meta.maintainers = with lib.maintainers; [ minijackson ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/leaps.nix b/nixpkgs/nixos/modules/services/misc/leaps.nix
new file mode 100644
index 00000000000..d4e88ecbebd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/leaps.nix
@@ -0,0 +1,62 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.leaps;
+ stateDir = "/var/lib/leaps/";
+in
+{
+ options = {
+ services.leaps = {
+ enable = mkEnableOption "leaps";
+ port = mkOption {
+ type = types.int;
+ default = 8080;
+ description = "A port where leaps listens for incoming http requests";
+ };
+ address = mkOption {
+ default = "";
+ type = types.str;
+ example = "127.0.0.1";
+ description = "Hostname or IP-address to listen to. By default it will listen on all interfaces.";
+ };
+ path = mkOption {
+ default = "/";
+ type = types.path;
+ description = "Subdirectory used for reverse proxy setups";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users = {
+ users.leaps = {
+ uid = config.ids.uids.leaps;
+ description = "Leaps server user";
+ group = "leaps";
+ home = stateDir;
+ createHome = true;
+ };
+
+ groups.leaps = {
+ gid = config.ids.gids.leaps;
+ };
+ };
+
+ systemd.services.leaps = {
+ description = "leaps service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ User = "leaps";
+ Group = "leaps";
+ Restart = "on-failure";
+ WorkingDirectory = stateDir;
+ PrivateTmp = true;
+ ExecStart = "${pkgs.leaps.bin}/bin/leaps -path ${toString cfg.path} -address ${cfg.address}:${toString cfg.port}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/lidarr.nix b/nixpkgs/nixos/modules/services/misc/lidarr.nix
new file mode 100644
index 00000000000..40755c16217
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/lidarr.nix
@@ -0,0 +1,82 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.lidarr;
+in
+{
+ options = {
+ services.lidarr = {
+ enable = mkEnableOption "Lidarr";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.lidarr;
+ defaultText = "pkgs.lidarr";
+ description = "The Lidarr package to use";
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for Lidarr
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "lidarr";
+ description = ''
+ User account under which Lidarr runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "lidarr";
+ description = ''
+ Group under which Lidarr runs.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.lidarr = {
+ description = "Lidarr";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${cfg.package}/bin/Lidarr";
+ Restart = "on-failure";
+
+ StateDirectory = "lidarr";
+ StateDirectoryMode = "0770";
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ 8686 ];
+ };
+
+ users.users = mkIf (cfg.user == "lidarr") {
+ lidarr = {
+ group = cfg.group;
+ home = "/var/lib/lidarr";
+ uid = config.ids.uids.lidarr;
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "lidarr") {
+ lidarr = {
+ gid = config.ids.gids.lidarr;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/logkeys.nix b/nixpkgs/nixos/modules/services/misc/logkeys.nix
new file mode 100644
index 00000000000..0082db63a06
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/logkeys.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.logkeys;
+in {
+ options.services.logkeys = {
+ enable = mkEnableOption "logkeys service";
+
+ device = mkOption {
+ description = "Use the given device as keyboard input event device instead of /dev/input/eventX default.";
+ default = null;
+ type = types.nullOr types.str;
+ example = "/dev/input/event15";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.logkeys = {
+ description = "LogKeys Keylogger Daemon";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.logkeys}/bin/logkeys -s${lib.optionalString (cfg.device != null) " -d ${cfg.device}"}";
+ ExecStop = "${pkgs.logkeys}/bin/logkeys -k";
+ Type = "forking";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mathics.nix b/nixpkgs/nixos/modules/services/misc/mathics.nix
new file mode 100644
index 00000000000..c588a30d76c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mathics.nix
@@ -0,0 +1,54 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+ cfg = config.services.mathics;
+
+in {
+ options = {
+ services.mathics = {
+ enable = mkEnableOption "Mathics notebook service";
+
+ external = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Listen on all interfaces, rather than just localhost?";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8000;
+ description = "TCP port to listen on.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users.mathics = {
+ group = config.users.groups.mathics.name;
+ description = "Mathics user";
+ home = "/var/lib/mathics";
+ createHome = true;
+ uid = config.ids.uids.mathics;
+ };
+
+ users.groups.mathics.gid = config.ids.gids.mathics;
+
+ systemd.services.mathics = {
+ description = "Mathics notebook server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ User = config.users.users.mathics.name;
+ Group = config.users.groups.mathics.name;
+ ExecStart = concatStringsSep " " [
+ "${pkgs.mathics}/bin/mathicsserver"
+ "--port" (toString cfg.port)
+ (if cfg.external then "--external" else "")
+ ];
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml b/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml
new file mode 100644
index 00000000000..d85bdd1208f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml
@@ -0,0 +1,25 @@
+version: 1
+
+# In systemd's journal, loglevel is implicitly stored, so let's omit it
+# from the message text.
+formatters:
+ journal_fmt:
+ format: '%(name)s: [%(request)s] %(message)s'
+
+filters:
+ context:
+ (): synapse.util.logcontext.LoggingContextFilter
+ request: ""
+
+handlers:
+ journal:
+ class: systemd.journal.JournalHandler
+ formatter: journal_fmt
+ filters: [context]
+ SYSLOG_IDENTIFIER: synapse
+
+root:
+ level: INFO
+ handlers: [journal]
+
+disable_existing_loggers: False
diff --git a/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix b/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix
new file mode 100644
index 00000000000..018fac38616
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix
@@ -0,0 +1,702 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.matrix-synapse;
+ pg = config.services.postgresql;
+ usePostgresql = cfg.database_type == "psycopg2";
+ logConfigFile = pkgs.writeText "log_config.yaml" cfg.logConfig;
+ mkResource = r: ''{names: ${builtins.toJSON r.names}, compress: ${boolToString r.compress}}'';
+ mkListener = l: ''{port: ${toString l.port}, bind_address: "${l.bind_address}", type: ${l.type}, tls: ${boolToString l.tls}, x_forwarded: ${boolToString l.x_forwarded}, resources: [${concatStringsSep "," (map mkResource l.resources)}]}'';
+ configFile = pkgs.writeText "homeserver.yaml" ''
+${optionalString (cfg.tls_certificate_path != null) ''
+tls_certificate_path: "${cfg.tls_certificate_path}"
+''}
+${optionalString (cfg.tls_private_key_path != null) ''
+tls_private_key_path: "${cfg.tls_private_key_path}"
+''}
+${optionalString (cfg.tls_dh_params_path != null) ''
+tls_dh_params_path: "${cfg.tls_dh_params_path}"
+''}
+no_tls: ${boolToString cfg.no_tls}
+${optionalString (cfg.bind_port != null) ''
+bind_port: ${toString cfg.bind_port}
+''}
+${optionalString (cfg.unsecure_port != null) ''
+unsecure_port: ${toString cfg.unsecure_port}
+''}
+${optionalString (cfg.bind_host != null) ''
+bind_host: "${cfg.bind_host}"
+''}
+server_name: "${cfg.server_name}"
+pid_file: "/run/matrix-synapse.pid"
+web_client: ${boolToString cfg.web_client}
+${optionalString (cfg.public_baseurl != null) ''
+public_baseurl: "${cfg.public_baseurl}"
+''}
+listeners: [${concatStringsSep "," (map mkListener cfg.listeners)}]
+database: {
+ name: "${cfg.database_type}",
+ args: {
+ ${concatStringsSep ",\n " (
+ mapAttrsToList (n: v: "\"${n}\": ${builtins.toJSON v}") cfg.database_args
+ )}
+ }
+}
+event_cache_size: "${cfg.event_cache_size}"
+verbose: ${cfg.verbose}
+log_config: "${logConfigFile}"
+rc_messages_per_second: ${cfg.rc_messages_per_second}
+rc_message_burst_count: ${cfg.rc_message_burst_count}
+federation_rc_window_size: ${cfg.federation_rc_window_size}
+federation_rc_sleep_limit: ${cfg.federation_rc_sleep_limit}
+federation_rc_sleep_delay: ${cfg.federation_rc_sleep_delay}
+federation_rc_reject_limit: ${cfg.federation_rc_reject_limit}
+federation_rc_concurrent: ${cfg.federation_rc_concurrent}
+media_store_path: "${cfg.dataDir}/media"
+uploads_path: "${cfg.dataDir}/uploads"
+max_upload_size: "${cfg.max_upload_size}"
+max_image_pixels: "${cfg.max_image_pixels}"
+dynamic_thumbnails: ${boolToString cfg.dynamic_thumbnails}
+url_preview_enabled: ${boolToString cfg.url_preview_enabled}
+${optionalString (cfg.url_preview_enabled == true) ''
+url_preview_ip_range_blacklist: ${builtins.toJSON cfg.url_preview_ip_range_blacklist}
+url_preview_ip_range_whitelist: ${builtins.toJSON cfg.url_preview_ip_range_whitelist}
+url_preview_url_blacklist: ${builtins.toJSON cfg.url_preview_url_blacklist}
+''}
+recaptcha_private_key: "${cfg.recaptcha_private_key}"
+recaptcha_public_key: "${cfg.recaptcha_public_key}"
+enable_registration_captcha: ${boolToString cfg.enable_registration_captcha}
+turn_uris: ${builtins.toJSON cfg.turn_uris}
+turn_shared_secret: "${cfg.turn_shared_secret}"
+enable_registration: ${boolToString cfg.enable_registration}
+${optionalString (cfg.registration_shared_secret != null) ''
+registration_shared_secret: "${cfg.registration_shared_secret}"
+''}
+recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify"
+turn_user_lifetime: "${cfg.turn_user_lifetime}"
+user_creation_max_duration: ${cfg.user_creation_max_duration}
+bcrypt_rounds: ${cfg.bcrypt_rounds}
+allow_guest_access: ${boolToString cfg.allow_guest_access}
+trusted_third_party_id_servers: ${builtins.toJSON cfg.trusted_third_party_id_servers}
+room_invite_state_types: ${builtins.toJSON cfg.room_invite_state_types}
+${optionalString (cfg.macaroon_secret_key != null) ''
+ macaroon_secret_key: "${cfg.macaroon_secret_key}"
+''}
+expire_access_token: ${boolToString cfg.expire_access_token}
+enable_metrics: ${boolToString cfg.enable_metrics}
+report_stats: ${boolToString cfg.report_stats}
+signing_key_path: "${cfg.dataDir}/homeserver.signing.key"
+key_refresh_interval: "${cfg.key_refresh_interval}"
+perspectives:
+ servers: {
+ ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
+ "${n}": {
+ "verify_keys": {
+ ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
+ "${n}": {
+ "key": "${v}"
+ }'') v)}
+ }
+ '') cfg.servers)}
+ }
+ }
+app_service_config_files: ${builtins.toJSON cfg.app_service_config_files}
+
+${cfg.extraConfig}
+'';
+in {
+ options = {
+ services.matrix-synapse = {
+ enable = mkEnableOption "matrix.org synapse";
+ package = mkOption {
+ type = types.package;
+ default = pkgs.matrix-synapse;
+ defaultText = "pkgs.matrix-synapse";
+ description = ''
+ Overridable attribute of the matrix synapse server package to use.
+ '';
+ };
+ no_tls = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Don't bind to the https port
+ '';
+ };
+ bind_port = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 8448;
+ description = ''
+ DEPRECATED: Use listeners instead.
+ The port to listen for HTTPS requests on.
+ For when matrix traffic is sent directly to synapse.
+ '';
+ };
+ unsecure_port = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 8008;
+ description = ''
+ DEPRECATED: Use listeners instead.
+ The port to listen for HTTP requests on.
+ For when matrix traffic passes through loadbalancer that unwraps TLS.
+ '';
+ };
+ bind_host = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ DEPRECATED: Use listeners instead.
+ Local interface to listen on.
+ The empty string will cause synapse to listen on all interfaces.
+ '';
+ };
+ tls_certificate_path = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "${cfg.dataDir}/homeserver.tls.crt";
+ description = ''
+ PEM encoded X509 certificate for TLS.
+ You can replace the self-signed certificate that synapse
+ autogenerates on launch with your own SSL certificate + key pair
+ if you like. Any required intermediary certificates can be
+ appended after the primary certificate in hierarchical order.
+ '';
+ };
+ tls_private_key_path = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "${cfg.dataDir}/homeserver.tls.key";
+ description = ''
+ PEM encoded private key for TLS. Specify null if synapse is not
+ speaking TLS directly.
+ '';
+ };
+ tls_dh_params_path = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "${cfg.dataDir}/homeserver.tls.dh";
+ description = ''
+ PEM dh parameters for ephemeral keys
+ '';
+ };
+ server_name = mkOption {
+ type = types.str;
+ example = "example.com";
+ default = config.networking.hostName;
+ description = ''
+ The domain name of the server, with optional explicit port.
+ This is used by remote servers to connect to this server,
+ e.g. matrix.org, localhost:8080, etc.
+ This is also the last part of your UserID.
+ '';
+ };
+ web_client = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to serve a web client from the HTTP/HTTPS root resource.
+ '';
+ };
+ public_baseurl = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "https://example.com:8448/";
+ description = ''
+ The public-facing base URL for the client API (not including _matrix/...)
+ '';
+ };
+ listeners = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ port = mkOption {
+ type = types.int;
+ example = 8448;
+ description = ''
+ The port to listen for HTTP(S) requests on.
+ '';
+ };
+ bind_address = mkOption {
+ type = types.str;
+ default = "";
+ example = "203.0.113.42";
+ description = ''
+ Local interface to listen on.
+ The empty string will cause synapse to listen on all interfaces.
+ '';
+ };
+ type = mkOption {
+ type = types.str;
+ default = "http";
+ description = ''
+ Type of listener.
+ '';
+ };
+ tls = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to listen for HTTPS connections rather than HTTP.
+ '';
+ };
+ x_forwarded = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Use the X-Forwarded-For (XFF) header as the client IP and not the
+ actual client IP.
+ '';
+ };
+ resources = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ names = mkOption {
+ type = types.listOf types.str;
+ description = ''
+ List of resources to host on this listener.
+ '';
+ example = ["client" "webclient" "federation"];
+ };
+ compress = mkOption {
+ type = types.bool;
+ description = ''
+ Should synapse compress HTTP responses to clients that support it?
+ This should be disabled if running synapse behind a load balancer
+ that can do automatic compression.
+ '';
+ };
+ };
+ });
+ description = ''
+ List of HTTP resources to serve on this listener.
+ '';
+ };
+ };
+ });
+ default = [{
+ port = 8448;
+ bind_address = "";
+ type = "http";
+ tls = true;
+ x_forwarded = false;
+ resources = [
+ { names = ["client" "webclient"]; compress = true; }
+ { names = ["federation"]; compress = false; }
+ ];
+ }];
+ description = ''
+ List of ports that Synapse should listen on, their purpose and their configuration.
+ '';
+ };
+ verbose = mkOption {
+ type = types.str;
+ default = "0";
+ description = "Logging verbosity level.";
+ };
+ rc_messages_per_second = mkOption {
+ type = types.str;
+ default = "0.2";
+ description = "Number of messages a client can send per second";
+ };
+ rc_message_burst_count = mkOption {
+ type = types.str;
+ default = "10.0";
+ description = "Number of message a client can send before being throttled";
+ };
+ federation_rc_window_size = mkOption {
+ type = types.str;
+ default = "1000";
+ description = "The federation window size in milliseconds";
+ };
+ federation_rc_sleep_limit = mkOption {
+ type = types.str;
+ default = "10";
+ description = ''
+ The number of federation requests from a single server in a window
+ before the server will delay processing the request.
+ '';
+ };
+ federation_rc_sleep_delay = mkOption {
+ type = types.str;
+ default = "500";
+ description = ''
+ The duration in milliseconds to delay processing events from
+ remote servers by if they go over the sleep limit.
+ '';
+ };
+ federation_rc_reject_limit = mkOption {
+ type = types.str;
+ default = "50";
+ description = ''
+ The maximum number of concurrent federation requests allowed
+ from a single server
+ '';
+ };
+ federation_rc_concurrent = mkOption {
+ type = types.str;
+ default = "3";
+ description = "The number of federation requests to concurrently process from a single server";
+ };
+ database_type = mkOption {
+ type = types.enum [ "sqlite3" "psycopg2" ];
+ default = if versionAtLeast config.system.stateVersion "18.03"
+ then "psycopg2"
+ else "sqlite3";
+ description = ''
+ The database engine name. Can be sqlite or psycopg2.
+ '';
+ };
+ create_local_database = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to create a local database automatically.
+ '';
+ };
+ database_name = mkOption {
+ type = types.str;
+ default = "matrix-synapse";
+ description = "Database name.";
+ };
+ database_user = mkOption {
+ type = types.str;
+ default = "matrix-synapse";
+ description = "Database user name.";
+ };
+ database_args = mkOption {
+ type = types.attrs;
+ default = {
+ sqlite3 = { database = "${cfg.dataDir}/homeserver.db"; };
+ psycopg2 = {
+ user = cfg.database_user;
+ database = cfg.database_name;
+ };
+ }.${cfg.database_type};
+ description = ''
+ Arguments to pass to the engine.
+ '';
+ };
+ event_cache_size = mkOption {
+ type = types.str;
+ default = "10K";
+ description = "Number of events to cache in memory.";
+ };
+ url_preview_enabled = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Is the preview URL API enabled? If enabled, you *must* specify an
+ explicit url_preview_ip_range_blacklist of IPs that the spider is
+ denied from accessing.
+ '';
+ };
+ url_preview_ip_range_blacklist = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "127.0.0.0/8"
+ "10.0.0.0/8"
+ "172.16.0.0/12"
+ "192.168.0.0/16"
+ "100.64.0.0/10"
+ "169.254.0.0/16"
+ ];
+ description = ''
+ List of IP address CIDR ranges that the URL preview spider is denied
+ from accessing.
+ '';
+ };
+ url_preview_ip_range_whitelist = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ List of IP address CIDR ranges that the URL preview spider is allowed
+ to access even if they are specified in
+ url_preview_ip_range_blacklist.
+ '';
+ };
+ url_preview_url_blacklist = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Optional list of URL matches that the URL preview spider is
+ denied from accessing.
+ '';
+ };
+ recaptcha_private_key = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ This Home Server's ReCAPTCHA private key.
+ '';
+ };
+ recaptcha_public_key = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ This Home Server's ReCAPTCHA public key.
+ '';
+ };
+ enable_registration_captcha = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables ReCaptcha checks when registering, preventing signup
+ unless a captcha is answered. Requires a valid ReCaptcha
+ public/private key.
+ '';
+ };
+ turn_uris = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ The public URIs of the TURN server to give to clients
+ '';
+ };
+ turn_shared_secret = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ The shared secret used to compute passwords for the TURN server
+ '';
+ };
+ turn_user_lifetime = mkOption {
+ type = types.str;
+ default = "1h";
+ description = "How long generated TURN credentials last";
+ };
+ enable_registration = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable registration for new users.
+ '';
+ };
+ registration_shared_secret = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ If set, allows registration by anyone who also has the shared
+ secret, even if registration is otherwise disabled.
+ '';
+ };
+ enable_metrics = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable collection and rendering of performance metrics
+ '';
+ };
+ report_stats = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ '';
+ };
+ servers = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = {
+ "matrix.org" = {
+ "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
+ };
+ };
+ description = ''
+ The trusted servers to download signing keys from.
+ '';
+ };
+ max_upload_size = mkOption {
+ type = types.str;
+ default = "10M";
+ description = "The largest allowed upload size in bytes";
+ };
+ max_image_pixels = mkOption {
+ type = types.str;
+ default = "32M";
+ description = "Maximum number of pixels that will be thumbnailed";
+ };
+ dynamic_thumbnails = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to generate new thumbnails on the fly to precisely match
+ the resolution requested by the client. If true then whenever
+ a new resolution is requested by the client the server will
+ generate a new thumbnail. If false the server will pick a thumbnail
+ from a precalculated list.
+ '';
+ };
+ user_creation_max_duration = mkOption {
+ type = types.str;
+ default = "1209600000";
+ description = ''
+ Sets the expiry for the short term user creation in
+ milliseconds. The default value is two weeks.
+ '';
+ };
+ bcrypt_rounds = mkOption {
+ type = types.str;
+ default = "12";
+ description = ''
+ Set the number of bcrypt rounds used to generate password hash.
+ Larger numbers increase the work factor needed to generate the hash.
+ '';
+ };
+ allow_guest_access = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allows users to register as guests without a password/email/etc, and
+ participate in rooms hosted on this server which have been made
+ accessible to anonymous users.
+ '';
+ };
+ trusted_third_party_id_servers = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "matrix.org"
+ "vector.im"
+ ];
+ description = ''
+ The list of identity servers trusted to verify third party identifiers by this server.
+ '';
+ };
+ room_invite_state_types = mkOption {
+ type = types.listOf types.str;
+ default = ["m.room.join_rules" "m.room.canonical_alias" "m.room.avatar" "m.room.name"];
+ description = ''
+ A list of event types that will be included in the room_invite_state
+ '';
+ };
+ macaroon_secret_key = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Secret key for authentication tokens
+ '';
+ };
+ expire_access_token = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable access token expiration.
+ '';
+ };
+ key_refresh_interval = mkOption {
+ type = types.str;
+ default = "1d";
+ description = ''
+ How long key response published by this server is valid for.
+ Used to set the valid_until_ts in /key/v2 APIs.
+ Determines how quickly servers will query to check which keys
+ are still valid.
+ '';
+ };
+ app_service_config_files = mkOption {
+ type = types.listOf types.path;
+ default = [ ];
+ description = ''
+ A list of application service config file to use
+ '';
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra config options for matrix-synapse.
+ '';
+ };
+ extraConfigFiles = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ Extra config files to include.
+
+ The configuration files will be included based on the command line
+ argument --config-path. This allows to configure secrets without
+ having to go through the Nix store, e.g. based on deployment keys if
+ NixOPS is in use.
+ '';
+ };
+ logConfig = mkOption {
+ type = types.lines;
+ default = readFile ./matrix-synapse-log_config.yaml;
+ description = ''
+ A yaml python logging config file
+ '';
+ };
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/matrix-synapse";
+ description = ''
+ The directory where matrix-synapse stores its stateful data such as
+ certificates, media and uploads.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users = [
+ { name = "matrix-synapse";
+ group = "matrix-synapse";
+ home = cfg.dataDir;
+ createHome = true;
+ shell = "${pkgs.bash}/bin/bash";
+ uid = config.ids.uids.matrix-synapse;
+ } ];
+
+ users.groups = [
+ { name = "matrix-synapse";
+ gid = config.ids.gids.matrix-synapse;
+ } ];
+
+ services.postgresql.enable = mkIf usePostgresql (mkDefault true);
+
+ systemd.services.matrix-synapse = {
+ description = "Synapse Matrix homeserver";
+ after = [ "network.target" "postgresql.service" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ ${cfg.package}/bin/homeserver \
+ --config-path ${configFile} \
+ --keys-directory ${cfg.dataDir} \
+ --generate-keys
+ '' + optionalString (usePostgresql && cfg.create_local_database) ''
+ if ! test -e "${cfg.dataDir}/db-created"; then
+ ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \
+ ${pg.package}/bin/createuser \
+ --login \
+ --no-createdb \
+ --no-createrole \
+ --encrypted \
+ ${cfg.database_user}
+ ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \
+ ${pg.package}/bin/createdb \
+ --owner=${cfg.database_user} \
+ --encoding=UTF8 \
+ --lc-collate=C \
+ --lc-ctype=C \
+ --template=template0 \
+ ${cfg.database_name}
+ touch "${cfg.dataDir}/db-created"
+ fi
+ '';
+ serviceConfig = {
+ Type = "notify";
+ User = "matrix-synapse";
+ Group = "matrix-synapse";
+ WorkingDirectory = cfg.dataDir;
+ PermissionsStartOnly = true;
+ ExecStart = ''
+ ${cfg.package}/bin/homeserver \
+ ${ concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
+ --keys-directory ${cfg.dataDir}
+ '';
+ ExecReload = "${pkgs.utillinux}/bin/kill -HUP $MAINPID";
+ Restart = "on-failure";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mbpfan.nix b/nixpkgs/nixos/modules/services/misc/mbpfan.nix
new file mode 100644
index 00000000000..e22d1ed61f9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mbpfan.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.mbpfan;
+ verbose = if cfg.verbose then "v" else "";
+
+in {
+ options.services.mbpfan = {
+ enable = mkEnableOption "mbpfan, fan controller daemon for Apple Macs and MacBooks";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.mbpfan;
+ defaultText = "pkgs.mbpfan";
+ description = ''
+ The package used for the mbpfan daemon.
+ '';
+ };
+
+ minFanSpeed = mkOption {
+ type = types.int;
+ default = 2000;
+ description = ''
+ The minimum fan speed.
+ '';
+ };
+
+ maxFanSpeed = mkOption {
+ type = types.int;
+ default = 6200;
+ description = ''
+ The maximum fan speed.
+ '';
+ };
+
+ lowTemp = mkOption {
+ type = types.int;
+ default = 63;
+ description = ''
+ The low temperature.
+ '';
+ };
+
+ highTemp = mkOption {
+ type = types.int;
+ default = 66;
+ description = ''
+ The high temperature.
+ '';
+ };
+
+ maxTemp = mkOption {
+ type = types.int;
+ default = 86;
+ description = ''
+ The maximum temperature.
+ '';
+ };
+
+ pollingInterval = mkOption {
+ type = types.int;
+ default = 7;
+ description = ''
+ The polling interval.
+ '';
+ };
+
+ verbose = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If true, sets the log level to verbose.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ boot.kernelModules = [ "coretemp" "applesmc" ];
+
+ environment = {
+ etc."mbpfan.conf".text = ''
+ [general]
+ min_fan_speed = ${toString cfg.minFanSpeed}
+ max_fan_speed = ${toString cfg.maxFanSpeed}
+ low_temp = ${toString cfg.lowTemp}
+ high_temp = ${toString cfg.highTemp}
+ max_temp = ${toString cfg.maxTemp}
+ polling_interval = ${toString cfg.pollingInterval}
+ '';
+ systemPackages = [ cfg.package ];
+ };
+
+ systemd.services.mbpfan = {
+ description = "A fan manager daemon for MacBook Pro";
+ wantedBy = [ "sysinit.target" ];
+ after = [ "syslog.target" "sysinit.target" ];
+ restartTriggers = [ config.environment.etc."mbpfan.conf".source ];
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${cfg.package}/bin/mbpfan -f${verbose}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ PIDFile = "/run/mbpfan.pid";
+ Restart = "always";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mediatomb.nix b/nixpkgs/nixos/modules/services/misc/mediatomb.nix
new file mode 100644
index 00000000000..107fb57fe1c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mediatomb.nix
@@ -0,0 +1,288 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ gid = config.ids.gids.mediatomb;
+ cfg = config.services.mediatomb;
+
+ mtConf = pkgs.writeText "config.xml" ''
+ <?xml version="1.0" encoding="UTF-8"?>
+ <config version="2" xmlns="http://mediatomb.cc/config/2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://mediatomb.cc/config/2 http://mediatomb.cc/config/2.xsd">
+ <server>
+ <ui enabled="yes" show-tooltips="yes">
+ <accounts enabled="no" session-timeout="30">
+ <account user="mediatomb" password="mediatomb"/>
+ </accounts>
+ </ui>
+ <name>${cfg.serverName}</name>
+ <udn>uuid:${cfg.uuid}</udn>
+ <home>${cfg.dataDir}</home>
+ <webroot>${pkgs.mediatomb}/share/mediatomb/web</webroot>
+ <storage>
+ <sqlite3 enabled="yes">
+ <database-file>mediatomb.db</database-file>
+ </sqlite3>
+ </storage>
+ <protocolInfo extend="${if cfg.ps3Support then "yes" else "no"}"/>
+ ${if cfg.dsmSupport then ''
+ <custom-http-headers>
+ <add header="X-User-Agent: redsonic"/>
+ </custom-http-headers>
+
+ <manufacturerURL>redsonic.com</manufacturerURL>
+ <modelNumber>105</modelNumber>
+ '' else ""}
+ ${if cfg.tg100Support then ''
+ <upnp-string-limit>101</upnp-string-limit>
+ '' else ""}
+ <extended-runtime-options>
+ <mark-played-items enabled="yes" suppress-cds-updates="yes">
+ <string mode="prepend">*</string>
+ <mark>
+ <content>video</content>
+ </mark>
+ </mark-played-items>
+ </extended-runtime-options>
+ </server>
+ <import hidden-files="no">
+ <scripting script-charset="UTF-8">
+ <common-script>${pkgs.mediatomb}/share/mediatomb/js/common.js</common-script>
+ <playlist-script>${pkgs.mediatomb}/share/mediatomb/js/playlists.js</playlist-script>
+ <virtual-layout type="builtin">
+ <import-script>${pkgs.mediatomb}/share/mediatomb/js/import.js</import-script>
+ </virtual-layout>
+ </scripting>
+ <mappings>
+ <extension-mimetype ignore-unknown="no">
+ <map from="mp3" to="audio/mpeg"/>
+ <map from="ogx" to="application/ogg"/>
+ <map from="ogv" to="video/ogg"/>
+ <map from="oga" to="audio/ogg"/>
+ <map from="ogg" to="audio/ogg"/>
+ <map from="ogm" to="video/ogg"/>
+ <map from="asf" to="video/x-ms-asf"/>
+ <map from="asx" to="video/x-ms-asf"/>
+ <map from="wma" to="audio/x-ms-wma"/>
+ <map from="wax" to="audio/x-ms-wax"/>
+ <map from="wmv" to="video/x-ms-wmv"/>
+ <map from="wvx" to="video/x-ms-wvx"/>
+ <map from="wm" to="video/x-ms-wm"/>
+ <map from="wmx" to="video/x-ms-wmx"/>
+ <map from="m3u" to="audio/x-mpegurl"/>
+ <map from="pls" to="audio/x-scpls"/>
+ <map from="flv" to="video/x-flv"/>
+ <map from="mkv" to="video/x-matroska"/>
+ <map from="mka" to="audio/x-matroska"/>
+ ${if cfg.ps3Support then ''
+ <map from="avi" to="video/divx"/>
+ '' else ""}
+ ${if cfg.dsmSupport then ''
+ <map from="avi" to="video/avi"/>
+ '' else ""}
+ </extension-mimetype>
+ <mimetype-upnpclass>
+ <map from="audio/*" to="object.item.audioItem.musicTrack"/>
+ <map from="video/*" to="object.item.videoItem"/>
+ <map from="image/*" to="object.item.imageItem"/>
+ </mimetype-upnpclass>
+ <mimetype-contenttype>
+ <treat mimetype="audio/mpeg" as="mp3"/>
+ <treat mimetype="application/ogg" as="ogg"/>
+ <treat mimetype="audio/ogg" as="ogg"/>
+ <treat mimetype="audio/x-flac" as="flac"/>
+ <treat mimetype="audio/x-ms-wma" as="wma"/>
+ <treat mimetype="audio/x-wavpack" as="wv"/>
+ <treat mimetype="image/jpeg" as="jpg"/>
+ <treat mimetype="audio/x-mpegurl" as="playlist"/>
+ <treat mimetype="audio/x-scpls" as="playlist"/>
+ <treat mimetype="audio/x-wav" as="pcm"/>
+ <treat mimetype="audio/L16" as="pcm"/>
+ <treat mimetype="video/x-msvideo" as="avi"/>
+ <treat mimetype="video/mp4" as="mp4"/>
+ <treat mimetype="audio/mp4" as="mp4"/>
+ <treat mimetype="application/x-iso9660" as="dvd"/>
+ <treat mimetype="application/x-iso9660-image" as="dvd"/>
+ </mimetype-contenttype>
+ </mappings>
+ <online-content>
+ <YouTube enabled="no" refresh="28800" update-at-start="no" purge-after="604800" racy-content="exclude" format="mp4" hd="no">
+ <favorites user="mediatomb"/>
+ <standardfeed feed="most_viewed" time-range="today"/>
+ <playlists user="mediatomb"/>
+ <uploads user="mediatomb"/>
+ <standardfeed feed="recently_featured" time-range="today"/>
+ </YouTube>
+ </online-content>
+ </import>
+ <transcoding enabled="${if cfg.transcoding then "yes" else "no"}">
+ <mimetype-profile-mappings>
+ <transcode mimetype="video/x-flv" using="vlcmpeg"/>
+ <transcode mimetype="application/ogg" using="vlcmpeg"/>
+ <transcode mimetype="application/ogg" using="oggflac2raw"/>
+ <transcode mimetype="audio/x-flac" using="oggflac2raw"/>
+ </mimetype-profile-mappings>
+ <profiles>
+ <profile name="oggflac2raw" enabled="no" type="external">
+ <mimetype>audio/L16</mimetype>
+ <accept-url>no</accept-url>
+ <first-resource>yes</first-resource>
+ <accept-ogg-theora>no</accept-ogg-theora>
+ <agent command="ogg123" arguments="-d raw -o byteorder:big -f %out %in"/>
+ <buffer size="1048576" chunk-size="131072" fill-size="262144"/>
+ </profile>
+ <profile name="vlcmpeg" enabled="no" type="external">
+ <mimetype>video/mpeg</mimetype>
+ <accept-url>yes</accept-url>
+ <first-resource>yes</first-resource>
+ <accept-ogg-theora>yes</accept-ogg-theora>
+ <agent command="vlc" arguments="-I dummy %in --sout #transcode{venc=ffmpeg,vcodec=mp2v,vb=4096,fps=25,aenc=ffmpeg,acodec=mpga,ab=192,samplerate=44100,channels=2}:standard{access=file,mux=ps,dst=%out} vlc:quit"/>
+ <buffer size="14400000" chunk-size="512000" fill-size="120000"/>
+ </profile>
+ </profiles>
+ </transcoding>
+ </config>
+ '';
+
+in {
+
+
+ ###### interface
+
+ options = {
+
+ services.mediatomb = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the mediatomb DLNA server.
+ '';
+ };
+
+ serverName = mkOption {
+ type = types.str;
+ default = "mediatomb";
+ description = ''
+ How to identify the server on the network.
+ '';
+ };
+
+ ps3Support = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable ps3 specific tweaks.
+ WARNING: incompatible with DSM 320 support.
+ '';
+ };
+
+ dsmSupport = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable D-Link DSM 320 specific tweaks.
+ WARNING: incompatible with ps3 support.
+ '';
+ };
+
+ tg100Support = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Telegent TG100 specific tweaks.
+ '';
+ };
+
+ transcoding = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable transcoding.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/mediatomb";
+ description = ''
+ The directory where mediatomb stores its state, data, etc.
+ '';
+ };
+
+ user = mkOption {
+ default = "mediatomb";
+ description = "User account under which mediatomb runs.";
+ };
+
+ group = mkOption {
+ default = "mediatomb";
+ description = "Group account under which mediatomb runs.";
+ };
+
+ port = mkOption {
+ default = 49152;
+ description = ''
+ The network port to listen on.
+ '';
+ };
+
+ interface = mkOption {
+ default = "";
+ description = ''
+ A specific interface to bind to.
+ '';
+ };
+
+ uuid = mkOption {
+ default = "fdfc8a4e-a3ad-4c1d-b43d-a2eedb03a687";
+ description = ''
+ A unique (on your network) to identify the server by.
+ '';
+ };
+
+ customCfg = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow mediatomb to create and use its own config file inside ${cfg.dataDir}.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.mediatomb = {
+ description = "MediaTomb media Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.mediatomb ];
+ serviceConfig.ExecStart = "${pkgs.mediatomb}/bin/mediatomb -p ${toString cfg.port} ${if cfg.interface!="" then "-e ${cfg.interface}" else ""} ${if cfg.customCfg then "" else "-c ${mtConf}"} -m ${cfg.dataDir}";
+ serviceConfig.User = "${cfg.user}";
+ };
+
+ users.groups = optionalAttrs (cfg.group == "mediatomb") (singleton {
+ name = "mediatomb";
+ gid = gid;
+ });
+
+ users.users = optionalAttrs (cfg.user == "mediatomb") (singleton {
+ name = "mediatomb";
+ isSystemUser = true;
+ group = cfg.group;
+ home = "${cfg.dataDir}";
+ createHome = true;
+ description = "Mediatomb DLNA Server User";
+ });
+
+ networking.firewall = {
+ allowedUDPPorts = [ 1900 cfg.port ];
+ allowedTCPPorts = [ cfg.port ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mesos-master.nix b/nixpkgs/nixos/modules/services/misc/mesos-master.nix
new file mode 100644
index 00000000000..572a9847e46
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mesos-master.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.mesos.master;
+
+in {
+
+ options.services.mesos = {
+
+ master = {
+ enable = mkOption {
+ description = "Whether to enable the Mesos Master.";
+ default = false;
+ type = types.bool;
+ };
+
+ ip = mkOption {
+ description = "IP address to listen on.";
+ default = "0.0.0.0";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Mesos Master port";
+ default = 5050;
+ type = types.int;
+ };
+
+ advertiseIp = mkOption {
+ description = "IP address advertised to reach this master.";
+ default = null;
+ type = types.nullOr types.str;
+ };
+
+ advertisePort = mkOption {
+ description = "Port advertised to reach this Mesos master.";
+ default = null;
+ type = types.nullOr types.int;
+ };
+
+ zk = mkOption {
+ description = ''
+ ZooKeeper URL (used for leader election amongst masters).
+ May be one of:
+ zk://host1:port1,host2:port2,.../mesos
+ zk://username:password@host1:port1,host2:port2,.../mesos
+ '';
+ type = types.str;
+ };
+
+ workDir = mkOption {
+ description = "The Mesos work directory.";
+ default = "/var/lib/mesos/master";
+ type = types.str;
+ };
+
+ extraCmdLineOptions = mkOption {
+ description = ''
+ Extra command line options for Mesos Master.
+
+ See https://mesos.apache.org/documentation/latest/configuration/
+ '';
+ default = [ "" ];
+ type = types.listOf types.str;
+ example = [ "--credentials=VALUE" ];
+ };
+
+ quorum = mkOption {
+ description = ''
+ The size of the quorum of replicas when using 'replicated_log' based
+ registry. It is imperative to set this value to be a majority of
+ masters i.e., quorum > (number of masters)/2.
+
+ If 0 will fall back to --registry=in_memory.
+ '';
+ default = 0;
+ type = types.int;
+ };
+
+ logLevel = mkOption {
+ description = ''
+ The logging level used. Possible values:
+ 'INFO', 'WARNING', 'ERROR'
+ '';
+ default = "INFO";
+ type = types.str;
+ };
+
+ };
+
+
+ };
+
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.workDir}' 0700 - - - -"
+ ];
+ systemd.services.mesos-master = {
+ description = "Mesos Master";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.mesos}/bin/mesos-master \
+ --ip=${cfg.ip} \
+ --port=${toString cfg.port} \
+ ${optionalString (cfg.advertiseIp != null) "--advertise_ip=${cfg.advertiseIp}"} \
+ ${optionalString (cfg.advertisePort != null) "--advertise_port=${toString cfg.advertisePort}"} \
+ ${if cfg.quorum == 0
+ then "--registry=in_memory"
+ else "--zk=${cfg.zk} --registry=replicated_log --quorum=${toString cfg.quorum}"} \
+ --work_dir=${cfg.workDir} \
+ --logging_level=${cfg.logLevel} \
+ ${toString cfg.extraCmdLineOptions}
+ '';
+ Restart = "on-failure";
+ };
+ };
+ };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/misc/mesos-slave.nix b/nixpkgs/nixos/modules/services/misc/mesos-slave.nix
new file mode 100644
index 00000000000..170065d0065
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mesos-slave.nix
@@ -0,0 +1,220 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.mesos.slave;
+
+ mkAttributes =
+ attrs: concatStringsSep ";" (mapAttrsToList
+ (k: v: "${k}:${v}")
+ (filterAttrs (k: v: v != null) attrs));
+ attribsArg = optionalString (cfg.attributes != {})
+ "--attributes=${mkAttributes cfg.attributes}";
+
+ containerizersArg = concatStringsSep "," (
+ lib.unique (
+ cfg.containerizers ++ (optional cfg.withDocker "docker")
+ )
+ );
+
+ imageProvidersArg = concatStringsSep "," (
+ lib.unique (
+ cfg.imageProviders ++ (optional cfg.withDocker "docker")
+ )
+ );
+
+ isolationArg = concatStringsSep "," (
+ lib.unique (
+ cfg.isolation ++ (optionals cfg.withDocker [ "filesystem/linux" "docker/runtime"])
+ )
+ );
+
+in {
+
+ options.services.mesos = {
+ slave = {
+ enable = mkOption {
+ description = "Whether to enable the Mesos Slave.";
+ default = false;
+ type = types.bool;
+ };
+
+ ip = mkOption {
+ description = "IP address to listen on.";
+ default = "0.0.0.0";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Port to listen on.";
+ default = 5051;
+ type = types.int;
+ };
+
+ advertiseIp = mkOption {
+ description = "IP address advertised to reach this agent.";
+ default = null;
+ type = types.nullOr types.str;
+ };
+
+ advertisePort = mkOption {
+ description = "Port advertised to reach this agent.";
+ default = null;
+ type = types.nullOr types.int;
+ };
+
+ containerizers = mkOption {
+ description = ''
+ List of containerizer implementations to compose in order to provide
+ containerization. Available options are mesos and docker.
+ The order the containerizers are specified is the order they are tried.
+ '';
+ default = [ "mesos" ];
+ type = types.listOf types.str;
+ };
+
+ imageProviders = mkOption {
+ description = "List of supported image providers, e.g., APPC,DOCKER.";
+ default = [ ];
+ type = types.listOf types.str;
+ };
+
+ imageProvisionerBackend = mkOption {
+ description = ''
+ Strategy for provisioning container rootfs from images,
+ e.g., aufs, bind, copy, overlay.
+ '';
+ default = "copy";
+ type = types.str;
+ };
+
+ isolation = mkOption {
+ description = ''
+ Isolation mechanisms to use, e.g., posix/cpu,posix/mem, or
+ cgroups/cpu,cgroups/mem, or network/port_mapping, or `gpu/nvidia` for nvidia
+ specific gpu isolation.
+ '';
+ default = [ "posix/cpu" "posix/mem" ];
+ type = types.listOf types.str;
+ };
+
+ master = mkOption {
+ description = ''
+ May be one of:
+ zk://host1:port1,host2:port2,.../path
+ zk://username:password@host1:port1,host2:port2,.../path
+ '';
+ type = types.str;
+ };
+
+ withHadoop = mkOption {
+ description = "Add the HADOOP_HOME to the slave.";
+ default = false;
+ type = types.bool;
+ };
+
+ withDocker = mkOption {
+ description = "Enable the docker containerizer.";
+ default = config.virtualisation.docker.enable;
+ type = types.bool;
+ };
+
+ dockerRegistry = mkOption {
+ description = ''
+ The default url for pulling Docker images.
+ It could either be a Docker registry server url,
+ or a local path in which Docker image archives are stored.
+ '';
+ default = null;
+ type = types.nullOr (types.either types.str types.path);
+ };
+
+ workDir = mkOption {
+ description = "The Mesos work directory.";
+ default = "/var/lib/mesos/slave";
+ type = types.str;
+ };
+
+ extraCmdLineOptions = mkOption {
+ description = ''
+ Extra command line options for Mesos Slave.
+
+ See https://mesos.apache.org/documentation/latest/configuration/
+ '';
+ default = [ "" ];
+ type = types.listOf types.str;
+ example = [ "--gc_delay=3days" ];
+ };
+
+ logLevel = mkOption {
+ description = ''
+ The logging level used. Possible values:
+ 'INFO', 'WARNING', 'ERROR'
+ '';
+ default = "INFO";
+ type = types.str;
+ };
+
+ attributes = mkOption {
+ description = ''
+ Machine attributes for the slave instance.
+
+ Use caution when changing this; you may need to manually reset slave
+ metadata before the slave can re-register.
+ '';
+ default = {};
+ type = types.attrsOf types.str;
+ example = { rack = "aa";
+ host = "aabc123";
+ os = "nixos"; };
+ };
+
+ executorEnvironmentVariables = mkOption {
+ description = ''
+ The environment variables that should be passed to the executor, and thus subsequently task(s).
+ '';
+ default = {
+ PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
+ };
+ type = types.attrsOf types.str;
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.workDir}' 0701 - - - -"
+ ];
+ systemd.services.mesos-slave = {
+ description = "Mesos Slave";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ] ++ optionals cfg.withDocker [ "docker.service" ] ;
+ path = [ pkgs.runtimeShellPackage ];
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.mesos}/bin/mesos-slave \
+ --containerizers=${containerizersArg} \
+ --image_providers=${imageProvidersArg} \
+ --image_provisioner_backend=${cfg.imageProvisionerBackend} \
+ --isolation=${isolationArg} \
+ --ip=${cfg.ip} \
+ --port=${toString cfg.port} \
+ ${optionalString (cfg.advertiseIp != null) "--advertise_ip=${cfg.advertiseIp}"} \
+ ${optionalString (cfg.advertisePort != null) "--advertise_port=${toString cfg.advertisePort}"} \
+ --master=${cfg.master} \
+ --work_dir=${cfg.workDir} \
+ --logging_level=${cfg.logLevel} \
+ ${attribsArg} \
+ ${optionalString cfg.withHadoop "--hadoop-home=${pkgs.hadoop}"} \
+ ${optionalString cfg.withDocker "--docker=${pkgs.docker}/libexec/docker/docker"} \
+ ${optionalString (cfg.dockerRegistry != null) "--docker_registry=${cfg.dockerRegistry}"} \
+ --executor_environment_variables=${lib.escapeShellArg (builtins.toJSON cfg.executorEnvironmentVariables)} \
+ ${toString cfg.extraCmdLineOptions}
+ '';
+ };
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/metabase.nix b/nixpkgs/nixos/modules/services/misc/metabase.nix
new file mode 100644
index 00000000000..e78100a046a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/metabase.nix
@@ -0,0 +1,103 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.metabase;
+
+ inherit (lib) mkEnableOption mkIf mkOption;
+ inherit (lib) optional optionalAttrs types;
+
+ dataDir = "/var/lib/metabase";
+
+in {
+
+ options = {
+
+ services.metabase = {
+ enable = mkEnableOption "Metabase service";
+
+ listen = {
+ ip = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ IP address that Metabase should listen on.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 3000;
+ description = ''
+ Listen port for Metabase.
+ '';
+ };
+ };
+
+ ssl = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable SSL (https) support.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 8443;
+ description = ''
+ Listen port over SSL (https) for Metabase.
+ '';
+ };
+
+ keystore = mkOption {
+ type = types.nullOr types.path;
+ default = "${dataDir}/metabase.jks";
+ example = "/etc/secrets/keystore.jks";
+ description = ''
+ <link xlink:href="https://www.digitalocean.com/community/tutorials/java-keytool-essentials-working-with-java-keystores">Java KeyStore</link> file containing the certificates.
+ '';
+ };
+
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for Metabase.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.metabase = {
+ description = "Metabase server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target" ];
+ environment = {
+ MB_PLUGINS_DIR = "${dataDir}/plugins";
+ MB_DB_FILE = "${dataDir}/metabase.db";
+ MB_JETTY_HOST = cfg.listen.ip;
+ MB_JETTY_PORT = toString cfg.listen.port;
+ } // optionalAttrs (cfg.ssl.enable) {
+ MB_JETTY_SSL = true;
+ MB_JETTY_SSL_PORT = toString cfg.ssl.port;
+ MB_JETTY_SSL_KEYSTORE = cfg.ssl.keystore;
+ };
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = baseNameOf dataDir;
+ ExecStart = "${pkgs.metabase}/bin/metabase";
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.listen.port ] ++ optional cfg.ssl.enable cfg.ssl.port;
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mwlib.nix b/nixpkgs/nixos/modules/services/misc/mwlib.nix
new file mode 100644
index 00000000000..6b41b552a86
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mwlib.nix
@@ -0,0 +1,258 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.mwlib;
+ pypkgs = pkgs.python27Packages;
+
+ inherit (pypkgs) python mwlib;
+
+ user = mkOption {
+ default = "nobody";
+ type = types.str;
+ description = "User to run as.";
+ };
+
+in
+{
+
+ options.services.mwlib = {
+
+ nserve = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable nserve. Nserve is a HTTP
+ server. The Collection extension is talking to
+ that program directly. Nserve uses at least
+ one qserve instance in order to distribute
+ and manage jobs.
+ '';
+ }; # nserve.enable
+
+ port = mkOption {
+ default = 8899;
+ type = types.int;
+ description = "Specify port to listen on.";
+ }; # nserve.port
+
+ address = mkOption {
+ default = "127.0.0.1";
+ type = types.str;
+ description = "Specify network interface to listen on.";
+ }; # nserve.address
+
+ qserve = mkOption {
+ default = [ "${cfg.qserve.address}:${toString cfg.qserve.port}" ];
+ type = types.listOf types.str;
+ description = "Register qserve instance.";
+ }; # nserve.qserve
+
+ inherit user;
+ }; # nserve
+
+ qserve = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ A job queue server used to distribute and manage
+ jobs. You should start one qserve instance
+ for each machine that is supposed to render pdf
+ files. Unless you’re operating the Wikipedia
+ installation, one machine should suffice.
+ '';
+ }; # qserve.enable
+
+ port = mkOption {
+ default = 14311;
+ type = types.int;
+ description = "Specify port to listen on.";
+ }; # qserve.port
+
+ address = mkOption {
+ default = "127.0.0.1";
+ type = types.str;
+ description = "Specify network interface to listen on.";
+ }; # qserve.address
+
+ datadir = mkOption {
+ default = "/var/lib/mwlib-qserve";
+ type = types.path;
+ description = "qserve data directory (FIXME: unused?)";
+ }; # qserve.datadir
+
+ allow = mkOption {
+ default = [ "127.0.0.1" ];
+ type = types.listOf types.str;
+ description = "List of allowed client IPs. Empty means any.";
+ }; # qserve.allow
+
+ inherit user;
+ }; # qserve
+
+ nslave = {
+ enable = mkOption {
+ default = cfg.qserve.enable;
+ type = types.bool;
+ description = ''
+ Pulls new jobs from exactly one qserve instance
+ and calls the zip and render programs
+ in order to download article collections and
+ convert them to different output formats. Nslave
+ uses a cache directory to store the generated
+ documents. Nslave also starts an internal http
+ server serving the content of the cache directory.
+ '';
+ }; # nslave.enable
+
+ cachedir = mkOption {
+ default = "/var/cache/mwlib-nslave";
+ type = types.path;
+ description = "Directory to store generated documents.";
+ }; # nslave.cachedir
+
+ numprocs = mkOption {
+ default = 10;
+ type = types.int;
+ description = "Number of parallel jobs to be executed.";
+ }; # nslave.numprocs
+
+ http = mkOption {
+ default = {};
+ description = ''
+ Internal http server serving the content of the cache directory.
+ You have to enable it, or use your own way for serving files
+ and set the http.url option accordingly.
+ '';
+ type = types.submodule ({
+ options = {
+ enable = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Enable internal http server.";
+ }; # nslave.http.enable
+
+ port = mkOption {
+ default = 8898;
+ type = types.int;
+ description = "Port to listen to when serving files from cache.";
+ }; # nslave.http.port
+
+ address = mkOption {
+ default = "127.0.0.1";
+ type = types.str;
+ description = "Specify network interface to listen on.";
+ }; # nslave.http.address
+
+ url = mkOption {
+ default = "http://localhost:${toString cfg.nslave.http.port}/cache";
+ type = types.str;
+ description = ''
+ Specify URL for accessing generated files from cache.
+ The Collection extension of Mediawiki won't be able to
+ download files without it.
+ '';
+ }; # nslave.http.url
+ };
+ }); # types.submodule
+ }; # nslave.http
+
+ inherit user;
+ }; # nslave
+
+ }; # options.services
+
+ config = {
+
+ systemd.services.mwlib-nserve = mkIf cfg.nserve.enable
+ {
+ description = "mwlib network interface";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "mwlib-qserve.service" ];
+
+ serviceConfig = {
+ ExecStart = concatStringsSep " " (
+ [
+ "${mwlib}/bin/nserve"
+ "--port ${toString cfg.nserve.port}"
+ "--interface ${cfg.nserve.address}"
+ ] ++ cfg.nserve.qserve
+ );
+ User = cfg.nserve.user;
+ };
+ }; # systemd.services.mwlib-nserve
+
+ systemd.services.mwlib-qserve = mkIf cfg.qserve.enable
+ {
+ description = "mwlib job queue server";
+
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ mkdir -pv '${cfg.qserve.datadir}'
+ chown -Rc ${cfg.qserve.user}:`id -ng ${cfg.qserve.user}` '${cfg.qserve.datadir}'
+ chmod -Rc u=rwX,go= '${cfg.qserve.datadir}'
+ '';
+
+ serviceConfig = {
+ ExecStart = concatStringsSep " " (
+ [
+ "${mwlib}/bin/mw-qserve"
+ "-p ${toString cfg.qserve.port}"
+ "-i ${cfg.qserve.address}"
+ "-d ${cfg.qserve.datadir}"
+ ] ++ map (a: "-a ${a}") cfg.qserve.allow
+ );
+ User = cfg.qserve.user;
+ PermissionsStartOnly = true;
+ };
+ }; # systemd.services.mwlib-qserve
+
+ systemd.services.mwlib-nslave = mkIf cfg.nslave.enable
+ {
+ description = "mwlib worker";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ preStart = ''
+ mkdir -pv '${cfg.nslave.cachedir}'
+ chown -Rc ${cfg.nslave.user}:`id -ng ${cfg.nslave.user}` '${cfg.nslave.cachedir}'
+ chmod -Rc u=rwX,go= '${cfg.nslave.cachedir}'
+ '';
+
+ path = with pkgs; [ imagemagick pdftk ];
+ environment = {
+ PYTHONPATH = concatMapStringsSep ":"
+ (m: "${pypkgs.${m}}/lib/${python.libPrefix}/site-packages")
+ [ "mwlib-rl" "mwlib-ext" "pygments" "pyfribidi" ];
+ };
+
+ serviceConfig = {
+ ExecStart = concatStringsSep " " (
+ [
+ "${mwlib}/bin/nslave"
+ "--cachedir ${cfg.nslave.cachedir}"
+ "--numprocs ${toString cfg.nslave.numprocs}"
+ "--url ${cfg.nslave.http.url}"
+ ] ++ (
+ if cfg.nslave.http.enable then
+ [
+ "--serve-files-port ${toString cfg.nslave.http.port}"
+ "--serve-files-address ${cfg.nslave.http.address}"
+ ] else
+ [
+ "--no-serve-files"
+ ]
+ ));
+ User = cfg.nslave.user;
+ PermissionsStartOnly = true;
+ };
+ }; # systemd.services.mwlib-nslave
+
+ }; # config
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nix-daemon.nix b/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
new file mode 100644
index 00000000000..3826f728afd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
@@ -0,0 +1,515 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.nix;
+
+ nix = cfg.package.out;
+
+ nixVersion = getVersion nix;
+
+ isNix20 = versionAtLeast nixVersion "2.0pre";
+
+ makeNixBuildUser = nr:
+ { name = "nixbld${toString nr}";
+ description = "Nix build user ${toString nr}";
+
+ /* For consistency with the setgid(2), setuid(2), and setgroups(2)
+ calls in `libstore/build.cc', don't add any supplementary group
+ here except "nixbld". */
+ uid = builtins.add config.ids.uids.nixbld nr;
+ group = "nixbld";
+ extraGroups = [ "nixbld" ];
+ };
+
+ nixbldUsers = map makeNixBuildUser (range 1 cfg.nrBuildUsers);
+
+ nixConf =
+ let
+ # In Nix < 2.0, If we're using sandbox for builds, then provide
+ # /bin/sh in the sandbox as a bind-mount to bash. This means we
+ # also need to include the entire closure of bash. Nix >= 2.0
+ # provides a /bin/sh by default.
+ sh = pkgs.runtimeShell;
+ binshDeps = pkgs.writeReferencesToFile sh;
+ in
+ pkgs.runCommand "nix.conf" { preferLocalBuild = true; extraOptions = cfg.extraOptions; } (''
+ ${optionalString (!isNix20) ''
+ extraPaths=$(for i in $(cat ${binshDeps}); do if test -d $i; then echo $i; fi; done)
+ ''}
+ cat > $out <<END
+ # WARNING: this file is generated from the nix.* options in
+ # your NixOS configuration, typically
+ # /etc/nixos/configuration.nix. Do not edit it!
+ build-users-group = nixbld
+ ${if isNix20 then "max-jobs" else "build-max-jobs"} = ${toString (cfg.maxJobs)}
+ ${if isNix20 then "cores" else "build-cores"} = ${toString (cfg.buildCores)}
+ ${if isNix20 then "sandbox" else "build-use-sandbox"} = ${if (builtins.isBool cfg.useSandbox) then boolToString cfg.useSandbox else cfg.useSandbox}
+ ${if isNix20 then "extra-sandbox-paths" else "build-sandbox-paths"} = ${toString cfg.sandboxPaths} ${optionalString (!isNix20) "/bin/sh=${sh} $(echo $extraPaths)"}
+ ${if isNix20 then "substituters" else "binary-caches"} = ${toString cfg.binaryCaches}
+ ${if isNix20 then "trusted-substituters" else "trusted-binary-caches"} = ${toString cfg.trustedBinaryCaches}
+ ${if isNix20 then "trusted-public-keys" else "binary-cache-public-keys"} = ${toString cfg.binaryCachePublicKeys}
+ auto-optimise-store = ${boolToString cfg.autoOptimiseStore}
+ ${if isNix20 then ''
+ require-sigs = ${if cfg.requireSignedBinaryCaches then "true" else "false"}
+ '' else ''
+ signed-binary-caches = ${if cfg.requireSignedBinaryCaches then "*" else ""}
+ ''}
+ trusted-users = ${toString cfg.trustedUsers}
+ allowed-users = ${toString cfg.allowedUsers}
+ ${optionalString (isNix20 && !cfg.distributedBuilds) ''
+ builders =
+ ''}
+ system-features = ${toString cfg.systemFeatures}
+ ${optionalString (versionAtLeast nixVersion "2.3pre") ''
+ sandbox-fallback = false
+ ''}
+ $extraOptions
+ END
+ '' + optionalString cfg.checkConfig (
+ if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then ''
+ echo "Ignore nix.checkConfig when cross-compiling"
+ '' else ''
+ echo "Checking that Nix can read nix.conf..."
+ ln -s $out ./nix.conf
+ NIX_CONF_DIR=$PWD ${cfg.package}/bin/nix show-config >/dev/null
+ '')
+ );
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ nix = {
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.nix;
+ defaultText = "pkgs.nix";
+ description = ''
+ This option specifies the Nix package instance to use throughout the system.
+ '';
+ };
+
+ maxJobs = mkOption {
+ type = types.either types.int (types.enum ["auto"]);
+ default = 1;
+ example = 64;
+ description = ''
+ This option defines the maximum number of jobs that Nix will try
+ to build in parallel. The default is 1. You should generally
+ set it to the total number of logical cores in your system (e.g., 16
+ for two CPUs with 4 cores each and hyper-threading).
+ '';
+ };
+
+ autoOptimiseStore = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ If set to true, Nix automatically detects files in the store that have
+ identical contents, and replaces them with hard links to a single copy.
+ This saves disk space. If set to false (the default), you can still run
+ nix-store --optimise to get rid of duplicate files.
+ '';
+ };
+
+ buildCores = mkOption {
+ type = types.int;
+ default = 0;
+ example = 64;
+ description = ''
+ This option defines the maximum number of concurrent tasks during
+ one build. It affects, e.g., -j option for make.
+ The special value 0 means that the builder should use all
+ available CPU cores in the system. Some builds may become
+ non-deterministic with this option; use with care! Packages will
+ only be affected if enableParallelBuilding is set for them.
+ '';
+ };
+
+ useSandbox = mkOption {
+ type = types.either types.bool (types.enum ["relaxed"]);
+ default = true;
+ description = "
+ If set, Nix will perform builds in a sandboxed environment that it
+ will set up automatically for each build. This prevents impurities
+ in builds by disallowing access to dependencies outside of the Nix
+ store by using network and mount namespaces in a chroot environment.
+ This is enabled by default even though it has a possible performance
+ impact due to the initial setup time of a sandbox for each build. It
+ doesn't affect derivation hashes, so changing this option will not
+ trigger a rebuild of packages.
+ ";
+ };
+
+ sandboxPaths = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "/dev" "/proc" ];
+ description =
+ ''
+ Directories from the host filesystem to be included
+ in the sandbox.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ gc-keep-outputs = true
+ gc-keep-derivations = true
+ '';
+ description = "Additional text appended to <filename>nix.conf</filename>.";
+ };
+
+ distributedBuilds = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to distribute builds to the machines listed in
+ <option>nix.buildMachines</option>.
+ '';
+ };
+
+ daemonNiceLevel = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Nix daemon process priority. This priority propagates to build processes.
+ 0 is the default Unix process priority, 19 is the lowest.
+ '';
+ };
+
+ daemonIONiceLevel = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Nix daemon process I/O priority. This priority propagates to build processes.
+ 0 is the default Unix process I/O priority, 7 is the lowest.
+ '';
+ };
+
+ buildMachines = mkOption {
+ type = types.listOf types.attrs;
+ default = [];
+ example = literalExample ''
+ [ { hostName = "voila.labs.cs.uu.nl";
+ sshUser = "nix";
+ sshKey = "/root/.ssh/id_buildfarm";
+ system = "powerpc-darwin";
+ maxJobs = 1;
+ }
+ { hostName = "linux64.example.org";
+ sshUser = "buildfarm";
+ sshKey = "/root/.ssh/id_buildfarm";
+ system = "x86_64-linux";
+ maxJobs = 2;
+ speedFactor = 2;
+ supportedFeatures = [ "kvm" ];
+ mandatoryFeatures = [ "perf" ];
+ }
+ ]
+ '';
+ description = ''
+ This option lists the machines to be used if distributed
+ builds are enabled (see
+ <option>nix.distributedBuilds</option>). Nix will perform
+ derivations on those machines via SSH by copying the inputs
+ to the Nix store on the remote machine, starting the build,
+ then copying the output back to the local Nix store. Each
+ element of the list should be an attribute set containing
+ the machine's host name (<varname>hostname</varname>), the
+ user name to be used for the SSH connection
+ (<varname>sshUser</varname>), the Nix system type
+ (<varname>system</varname>, e.g.,
+ <literal>"i686-linux"</literal>), the maximum number of
+ jobs to be run in parallel on that machine
+ (<varname>maxJobs</varname>), the path to the SSH private
+ key to be used to connect (<varname>sshKey</varname>), a
+ list of supported features of the machine
+ (<varname>supportedFeatures</varname>) and a list of
+ mandatory features of the machine
+ (<varname>mandatoryFeatures</varname>). The SSH private key
+ should not have a passphrase, and the corresponding public
+ key should be added to
+ <filename>~<replaceable>sshUser</replaceable>/authorized_keys</filename>
+ on the remote machine.
+ '';
+ };
+
+ # Environment variables for running Nix.
+ envVars = mkOption {
+ type = types.attrs;
+ internal = true;
+ default = {};
+ description = "Environment variables used by Nix.";
+ };
+
+ nrBuildUsers = mkOption {
+ type = types.int;
+ description = ''
+ Number of <literal>nixbld</literal> user accounts created to
+ perform secure concurrent builds. If you receive an error
+ message saying that “all build users are currently in use”,
+ you should increase this value.
+ '';
+ };
+
+ readOnlyStore = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If set, NixOS will enforce the immutability of the Nix store
+ by making <filename>/nix/store</filename> a read-only bind
+ mount. Nix will automatically make the store writable when
+ needed.
+ '';
+ };
+
+ binaryCaches = mkOption {
+ type = types.listOf types.str;
+ description = ''
+ List of binary cache URLs used to obtain pre-built binaries
+ of Nix packages.
+
+ By default https://cache.nixos.org/ is added,
+ to override it use <literal>lib.mkForce []</literal>.
+ '';
+ };
+
+ trustedBinaryCaches = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ http://hydra.nixos.org/ ];
+ description = ''
+ List of binary cache URLs that non-root users can use (in
+ addition to those specified using
+ <option>nix.binaryCaches</option>) by passing
+ <literal>--option binary-caches</literal> to Nix commands.
+ '';
+ };
+
+ requireSignedBinaryCaches = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If enabled (the default), Nix will only download binaries from binary caches if
+ they are cryptographically signed with any of the keys listed in
+ <option>nix.binaryCachePublicKeys</option>. If disabled, signatures are neither
+ required nor checked, so it's strongly recommended that you use only
+ trustworthy caches and https to prevent man-in-the-middle attacks.
+ '';
+ };
+
+ binaryCachePublicKeys = mkOption {
+ type = types.listOf types.str;
+ example = [ "hydra.nixos.org-1:CNHJZBh9K4tP3EKF6FkkgeVYsS3ohTl+oS0Qa8bezVs=" ];
+ description = ''
+ List of public keys used to sign binary caches. If
+ <option>nix.requireSignedBinaryCaches</option> is enabled,
+ then Nix will use a binary from a binary cache if and only
+ if it is signed by <emphasis>any</emphasis> of the keys
+ listed here. By default, only the key for
+ <uri>cache.nixos.org</uri> is included.
+ '';
+ };
+
+ trustedUsers = mkOption {
+ type = types.listOf types.str;
+ default = [ "root" ];
+ example = [ "root" "alice" "@wheel" ];
+ description = ''
+ A list of names of users that have additional rights when
+ connecting to the Nix daemon, such as the ability to specify
+ additional binary caches, or to import unsigned NARs. You
+ can also specify groups by prefixing them with
+ <literal>@</literal>; for instance,
+ <literal>@wheel</literal> means all users in the wheel
+ group.
+ '';
+ };
+
+ allowedUsers = mkOption {
+ type = types.listOf types.str;
+ default = [ "*" ];
+ example = [ "@wheel" "@builders" "alice" "bob" ];
+ description = ''
+ A list of names of users (separated by whitespace) that are
+ allowed to connect to the Nix daemon. As with
+ <option>nix.trustedUsers</option>, you can specify groups by
+ prefixing them with <literal>@</literal>. Also, you can
+ allow all users by specifying <literal>*</literal>. The
+ default is <literal>*</literal>. Note that trusted users are
+ always allowed to connect.
+ '';
+ };
+
+ nixPath = mkOption {
+ type = types.listOf types.str;
+ default =
+ [
+ "nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
+ "nixos-config=/etc/nixos/configuration.nix"
+ "/nix/var/nix/profiles/per-user/root/channels"
+ ];
+ description = ''
+ The default Nix expression search path, used by the Nix
+ evaluator to look up paths enclosed in angle brackets
+ (e.g. <literal>&lt;nixpkgs&gt;</literal>).
+ '';
+ };
+
+ systemFeatures = mkOption {
+ type = types.listOf types.str;
+ example = [ "kvm" "big-parallel" "gccarch-skylake" ];
+ description = ''
+ The supported features of a machine
+ '';
+ };
+
+ checkConfig = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If enabled (the default), checks that Nix can parse the generated nix.conf.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
+ nix.binaryCaches = [ "https://cache.nixos.org/" ];
+
+ environment.etc."nix/nix.conf".source = nixConf;
+
+ # List of machines for distributed Nix builds in the format
+ # expected by build-remote.pl.
+ environment.etc."nix/machines" =
+ { enable = cfg.buildMachines != [];
+ text =
+ concatMapStrings (machine:
+ "${if machine ? sshUser then "${machine.sshUser}@" else ""}${machine.hostName} "
+ + machine.system or (concatStringsSep "," machine.systems)
+ + " ${machine.sshKey or "-"} ${toString machine.maxJobs or 1} "
+ + toString (machine.speedFactor or 1)
+ + " "
+ + concatStringsSep "," (machine.mandatoryFeatures or [] ++ machine.supportedFeatures or [])
+ + " "
+ + concatStringsSep "," machine.mandatoryFeatures or []
+ + "\n"
+ ) cfg.buildMachines;
+ };
+
+ systemd.packages = [ nix ];
+
+ systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];
+
+ systemd.services.nix-daemon =
+ { path = [ nix pkgs.utillinux config.programs.ssh.package ]
+ ++ optionals cfg.distributedBuilds [ pkgs.gzip ]
+ ++ optionals (!isNix20) [ pkgs.openssl.bin ];
+
+ environment = cfg.envVars
+ // { CURL_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; }
+ // config.networking.proxy.envVars;
+
+ unitConfig.RequiresMountsFor = "/nix/store";
+
+ serviceConfig =
+ { Nice = cfg.daemonNiceLevel;
+ IOSchedulingPriority = cfg.daemonIONiceLevel;
+ LimitNOFILE = 4096;
+ };
+
+ restartTriggers = [ nixConf ];
+ };
+
+ nix.envVars =
+ optionalAttrs (!isNix20) {
+ NIX_CONF_DIR = "/etc/nix";
+
+ # Enable the copy-from-other-stores substituter, which allows
+ # builds to be sped up by copying build results from remote
+ # Nix stores. To do this, mount the remote file system on a
+ # subdirectory of /run/nix/remote-stores.
+ NIX_OTHER_STORES = "/run/nix/remote-stores/*/nix";
+ }
+
+ // optionalAttrs (cfg.distributedBuilds && !isNix20) {
+ NIX_BUILD_HOOK = "${nix}/libexec/nix/build-remote.pl";
+ };
+
+ # Set up the environment variables for running Nix.
+ environment.sessionVariables = cfg.envVars //
+ { NIX_PATH = cfg.nixPath;
+ };
+
+ environment.extraInit = optionalString (!isNix20)
+ ''
+ # Set up secure multi-user builds: non-root users build through the
+ # Nix daemon.
+ if [ "$USER" != root -o ! -w /nix/var/nix/db ]; then
+ export NIX_REMOTE=daemon
+ fi
+ '' + ''
+ if [ -e "$HOME/.nix-defexpr/channels" ]; then
+ export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}"
+ fi
+ '';
+
+ nix.nrBuildUsers = mkDefault (lib.max 32 (if cfg.maxJobs == "auto" then 0 else cfg.maxJobs));
+
+ users.users = nixbldUsers;
+
+ services.xserver.displayManager.hiddenUsers = map ({ name, ... }: name) nixbldUsers;
+
+ # FIXME: use systemd-tmpfiles to create Nix directories.
+ system.activationScripts.nix = stringAfter [ "etc" "users" ]
+ ''
+ # Nix initialisation.
+ install -m 0755 -d \
+ /nix/var/nix/gcroots \
+ /nix/var/nix/temproots \
+ /nix/var/nix/userpool \
+ /nix/var/nix/profiles \
+ /nix/var/nix/db \
+ /nix/var/log/nix/drvs
+ install -m 1777 -d \
+ /nix/var/nix/gcroots/per-user \
+ /nix/var/nix/profiles/per-user \
+ /nix/var/nix/gcroots/tmp
+ '';
+
+ nix.systemFeatures = mkDefault (
+ [ "nixos-test" "benchmark" "big-parallel" "kvm" ] ++
+ optionals (pkgs.stdenv.isx86_64 && pkgs.hostPlatform.platform ? gcc.arch) (
+ # a x86_64 builder can run code for `platform.gcc.arch` and minor architectures:
+ [ "gccarch-${pkgs.hostPlatform.platform.gcc.arch}" ] ++ {
+ sandybridge = [ "gccarch-westmere" ];
+ ivybridge = [ "gccarch-westmere" "gccarch-sandybridge" ];
+ haswell = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" ];
+ broadwell = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" "gccarch-haswell" ];
+ skylake = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" "gccarch-haswell" "gccarch-broadwell" ];
+ skylake-avx512 = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" "gccarch-haswell" "gccarch-broadwell" "gccarch-skylake" ];
+ }.${pkgs.hostPlatform.platform.gcc.arch} or []
+ )
+ );
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nix-gc.nix b/nixpkgs/nixos/modules/services/misc/nix-gc.nix
new file mode 100644
index 00000000000..12bed05757a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nix-gc.nix
@@ -0,0 +1,61 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.nix.gc;
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ nix.gc = {
+
+ automatic = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Automatically run the garbage collector at a specific time.";
+ };
+
+ dates = mkOption {
+ default = "03:15";
+ type = types.str;
+ description = ''
+ Specification (in the format described by
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>) of the time at
+ which the garbage collector will run.
+ '';
+ };
+
+ options = mkOption {
+ default = "";
+ example = "--max-freed $((64 * 1024**3))";
+ type = types.str;
+ description = ''
+ Options given to <filename>nix-collect-garbage</filename> when the
+ garbage collector is run automatically.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ systemd.services.nix-gc =
+ { description = "Nix Garbage Collector";
+ script = "exec ${config.nix.package.out}/bin/nix-collect-garbage ${cfg.options}";
+ startAt = optional cfg.automatic cfg.dates;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nix-optimise.nix b/nixpkgs/nixos/modules/services/misc/nix-optimise.nix
new file mode 100644
index 00000000000..416529f690e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nix-optimise.nix
@@ -0,0 +1,51 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.nix.optimise;
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ nix.optimise = {
+
+ automatic = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Automatically run the nix store optimiser at a specific time.";
+ };
+
+ dates = mkOption {
+ default = ["03:45"];
+ type = types.listOf types.str;
+ description = ''
+ Specification (in the format described by
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>) of the time at
+ which the optimiser will run.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ systemd.services.nix-optimise =
+ { description = "Nix Store Optimiser";
+ # No point 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/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix b/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix
new file mode 100644
index 00000000000..7ce3841be2f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix
@@ -0,0 +1,61 @@
+{ config, lib, ... }:
+
+with lib;
+let cfg = config.nix.sshServe;
+ command =
+ if cfg.protocol == "ssh"
+ then "nix-store --serve"
+ else "nix-daemon --stdio";
+in {
+ options = {
+
+ nix.sshServe = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable serving the Nix store as a remote store via SSH.";
+ };
+
+ keys = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "ssh-dss AAAAB3NzaC1k... alice@example.org" ];
+ description = "A list of SSH public keys allowed to access the binary cache via SSH.";
+ };
+
+ protocol = mkOption {
+ type = types.enum [ "ssh" "ssh-ng" ];
+ default = "ssh";
+ description = "The specific Nix-over-SSH protocol to use.";
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users.nix-ssh = {
+ description = "Nix SSH store user";
+ uid = config.ids.uids.nix-ssh;
+ useDefaultShell = true;
+ };
+
+ services.openssh.enable = true;
+
+ services.openssh.extraConfig = ''
+ Match User nix-ssh
+ AllowAgentForwarding no
+ AllowTcpForwarding no
+ PermitTTY no
+ PermitTunnel no
+ X11Forwarding no
+ ForceCommand ${config.nix.package.out}/bin/${command}
+ Match All
+ '';
+
+ users.users.nix-ssh.openssh.authorizedKeys.keys = cfg.keys;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nixos-manual.nix b/nixpkgs/nixos/modules/services/misc/nixos-manual.nix
new file mode 100644
index 00000000000..20ba3d8ef0b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nixos-manual.nix
@@ -0,0 +1,73 @@
+# 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, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nixosManual;
+ cfgd = config.documentation;
+in
+
+{
+
+ options = {
+
+ # TODO(@oxij): rename this to `.enable` eventually.
+ services.nixosManual.showManual = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to show the NixOS manual on one of the virtual
+ consoles.
+ '';
+ };
+
+ services.nixosManual.ttyNumber = mkOption {
+ type = types.int;
+ default = 8;
+ description = ''
+ Virtual console on which to show the manual.
+ '';
+ };
+
+ services.nixosManual.browser = mkOption {
+ type = types.path;
+ default = "${pkgs.w3m-nographics}/bin/w3m";
+ description = ''
+ Browser used to show the manual.
+ '';
+ };
+
+ };
+
+
+ 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";
+ };
+ };
+ })
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/novacomd.nix b/nixpkgs/nixos/modules/services/misc/novacomd.nix
new file mode 100644
index 00000000000..7cfc68d2b67
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/novacomd.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.novacomd;
+
+in {
+
+ options = {
+ services.novacomd = {
+ enable = mkEnableOption "Novacom service for connecting to WebOS devices";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.webos.novacom ];
+
+ systemd.services.novacomd = {
+ description = "Novacom WebOS daemon";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.webos.novacomd}/sbin/novacomd";
+ };
+ };
+ };
+
+ meta.maintainers = with maintainers; [ dtzWill ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nzbget.nix b/nixpkgs/nixos/modules/services/misc/nzbget.nix
new file mode 100644
index 00000000000..eb7b4c05d82
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nzbget.nix
@@ -0,0 +1,93 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nzbget;
+ pkg = pkgs.nzbget;
+ stateDir = "/var/lib/nzbget";
+ configFile = "${stateDir}/nzbget.conf";
+ configOpts = concatStringsSep " " (mapAttrsToList (name: value: "-o ${name}=${value}") nixosOpts);
+
+ nixosOpts = {
+ # allows nzbget to run as a "simple" service
+ OutputMode = "loggable";
+ # use journald for logging
+ WriteLog = "none";
+ ErrorTarget = "screen";
+ WarningTarget = "screen";
+ InfoTarget = "screen";
+ DetailTarget = "screen";
+ # required paths
+ ConfigTemplate = "${pkg}/share/nzbget/nzbget.conf";
+ WebDir = "${pkg}/share/nzbget/webui";
+ # nixos handles package updates
+ UpdateCheck = "none";
+ };
+
+in
+{
+ # interface
+
+ options = {
+ services.nzbget = {
+ enable = mkEnableOption "NZBGet";
+
+ user = mkOption {
+ type = types.str;
+ default = "nzbget";
+ description = "User account under which NZBGet runs";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "nzbget";
+ description = "Group under which NZBGet runs";
+ };
+ };
+ };
+
+ # implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.nzbget = {
+ description = "NZBGet Daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [
+ unrar
+ p7zip
+ ];
+ preStart = ''
+ if [ ! -f ${configFile} ]; then
+ ${pkgs.coreutils}/bin/install -m 0700 ${pkg}/share/nzbget/nzbget.conf ${configFile}
+ fi
+ '';
+
+ serviceConfig = {
+ StateDirectory = "nzbget";
+ StateDirectoryMode = "0750";
+ User = cfg.user;
+ Group = cfg.group;
+ UMask = "0002";
+ Restart = "on-failure";
+ ExecStart = "${pkg}/bin/nzbget --server --configfile ${stateDir}/nzbget.conf ${configOpts}";
+ ExecStop = "${pkg}/bin/nzbget --quit";
+ };
+ };
+
+ users.users = mkIf (cfg.user == "nzbget") {
+ nzbget = {
+ home = stateDir;
+ group = cfg.group;
+ uid = config.ids.uids.nzbget;
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "nzbget") {
+ nzbget = {
+ gid = config.ids.gids.nzbget;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/octoprint.nix b/nixpkgs/nixos/modules/services/misc/octoprint.nix
new file mode 100644
index 00000000000..8950010773c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/octoprint.nix
@@ -0,0 +1,130 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.octoprint;
+
+ baseConfig = {
+ plugins.curalegacy.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine";
+ server.host = cfg.host;
+ server.port = cfg.port;
+ webcam.ffmpeg = "${pkgs.ffmpeg.bin}/bin/ffmpeg";
+ };
+
+ fullConfig = recursiveUpdate cfg.extraConfig baseConfig;
+
+ cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON fullConfig);
+
+ pluginsEnv = pkgs.python.buildEnv.override {
+ extraLibs = cfg.plugins pkgs.octoprint-plugins;
+ };
+
+in
+{
+ ##### interface
+
+ options = {
+
+ services.octoprint = {
+
+ enable = mkEnableOption "OctoPrint, web interface for 3D printers";
+
+ host = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ Host to bind OctoPrint to.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 5000;
+ description = ''
+ Port to bind OctoPrint to.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "octoprint";
+ description = "User for the daemon.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "octoprint";
+ description = "Group for the daemon.";
+ };
+
+ stateDir = mkOption {
+ type = types.path;
+ default = "/var/lib/octoprint";
+ description = "State directory of the daemon.";
+ };
+
+ plugins = mkOption {
+ default = plugins: [];
+ defaultText = "plugins: []";
+ example = literalExample "plugins: [ m3d-fio ]";
+ description = "Additional plugins.";
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ description = "Extra options which are added to OctoPrint's YAML configuration file.";
+ };
+
+ };
+
+ };
+
+ ##### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = optionalAttrs (cfg.user == "octoprint") (singleton
+ { name = "octoprint";
+ group = cfg.group;
+ uid = config.ids.uids.octoprint;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "octoprint") (singleton
+ { name = "octoprint";
+ gid = config.ids.gids.octoprint;
+ });
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.octoprint = {
+ description = "OctoPrint, web interface for 3D printers";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = [ pluginsEnv ];
+ environment.PYTHONPATH = makeSearchPathOutput "lib" pkgs.python.sitePackages [ pluginsEnv ];
+
+ preStart = ''
+ if [ -e "${cfg.stateDir}/config.yaml" ]; then
+ ${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${cfgUpdate}" > "${cfg.stateDir}/config.yaml.tmp"
+ mv "${cfg.stateDir}/config.yaml.tmp" "${cfg.stateDir}/config.yaml"
+ else
+ cp "${cfgUpdate}" "${cfg.stateDir}/config.yaml"
+ chmod 600 "${cfg.stateDir}/config.yaml"
+ fi
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkgs.octoprint}/bin/octoprint serve -b ${cfg.stateDir}";
+ User = cfg.user;
+ Group = cfg.group;
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/osrm.nix b/nixpkgs/nixos/modules/services/misc/osrm.nix
new file mode 100644
index 00000000000..f89f37ccd9d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/osrm.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.osrm;
+in
+
+{
+ options.services.osrm = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the OSRM service.";
+ };
+
+ address = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = "IP address on which the web server will listen.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 5000;
+ description = "Port on which the web server will run.";
+ };
+
+ threads = mkOption {
+ type = types.int;
+ default = 4;
+ description = "Number of threads to use.";
+ };
+
+ algorithm = mkOption {
+ type = types.enum [ "CH" "CoreCH" "MLD" ];
+ default = "MLD";
+ description = "Algorithm to use for the data. Must be one of CH, CoreCH, MLD";
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "--max-table-size 1000" "--max-matching-size 1000" ];
+ description = "Extra command line arguments passed to osrm-routed";
+ };
+
+ dataFile = mkOption {
+ type = types.path;
+ example = "/var/lib/osrm/berlin-latest.osrm";
+ description = "Data file location";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users.osrm = {
+ group = config.users.users.osrm.name;
+ description = "OSRM user";
+ createHome = false;
+ };
+
+ users.groups.osrm = { };
+
+ systemd.services.osrm = {
+ description = "OSRM service";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = config.users.users.osrm.name;
+ ExecStart = ''
+ ${pkgs.osrm-backend}/bin/osrm-routed \
+ --ip ${cfg.address} \
+ --port ${toString cfg.port} \
+ --threads ${toString cfg.threads} \
+ --algorithm ${cfg.algorithm} \
+ ${toString cfg.extraFlags} \
+ ${cfg.dataFile}
+ '';
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/packagekit.nix b/nixpkgs/nixos/modules/services/misc/packagekit.nix
new file mode 100644
index 00000000000..325c4e84e0d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/packagekit.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.packagekit;
+
+ packagekitConf = ''
+ [Daemon]
+ DefaultBackend=${cfg.backend}
+ KeepCache=false
+ '';
+
+ vendorConf = ''
+ [PackagesNotFound]
+ DefaultUrl=https://github.com/NixOS/nixpkgs
+ CodecUrl=https://github.com/NixOS/nixpkgs
+ HardwareUrl=https://github.com/NixOS/nixpkgs
+ FontUrl=https://github.com/NixOS/nixpkgs
+ MimeUrl=https://github.com/NixOS/nixpkgs
+ '';
+
+in
+
+{
+
+ options = {
+
+ services.packagekit = {
+ enable = mkEnableOption
+ ''
+ PackageKit provides a cross-platform D-Bus abstraction layer for
+ installing software. Software utilizing PackageKit can install
+ software regardless of the package manager.
+ '';
+
+ # TODO: integrate with PolicyKit if the nix backend matures to the point
+ # where it will require elevated permissions
+ backend = mkOption {
+ type = types.enum [ "test_nop" ];
+ default = "test_nop";
+ description = ''
+ PackageKit supports multiple different backends and <literal>auto</literal> which
+ should do the right thing.
+ </para>
+ <para>
+ On NixOS however, we do not have a backend compatible with nix 2.0
+ (refer to <link xlink:href="https://github.com/NixOS/nix/issues/233">this issue</link> so we have to force
+ it to <literal>test_nop</literal> for now.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ services.dbus.packages = with pkgs; [ packagekit ];
+
+ systemd.packages = with pkgs; [ packagekit ];
+
+ environment.etc."PackageKit/PackageKit.conf".text = packagekitConf;
+ environment.etc."PackageKit/Vendor.conf".text = vendorConf;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/paperless.nix b/nixpkgs/nixos/modules/services/misc/paperless.nix
new file mode 100644
index 00000000000..3985dc0b303
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/paperless.nix
@@ -0,0 +1,185 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+ cfg = config.services.paperless;
+
+ defaultUser = "paperless";
+
+ manage = cfg.package.withConfig {
+ config = {
+ PAPERLESS_CONSUMPTION_DIR = cfg.consumptionDir;
+ PAPERLESS_INLINE_DOC = "true";
+ PAPERLESS_DISABLE_LOGIN = "true";
+ } // cfg.extraConfig;
+ inherit (cfg) dataDir ocrLanguages;
+ paperlessPkg = cfg.package;
+ };
+in
+{
+ options.services.paperless = {
+ enable = mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Enable Paperless.
+
+ When started, the Paperless database is automatically created if it doesn't
+ exist and updated if the Paperless package has changed.
+ Both tasks are achieved by running a Django migration.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/paperless";
+ description = "Directory to store the Paperless data.";
+ };
+
+ consumptionDir = mkOption {
+ type = types.str;
+ default = "${cfg.dataDir}/consume";
+ defaultText = "\${dataDir}/consume";
+ description = "Directory from which new documents are imported.";
+ };
+
+ consumptionDirIsPublic = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether all users can write to the consumption dir.";
+ };
+
+ ocrLanguages = mkOption {
+ type = with types; nullOr (listOf str);
+ default = null;
+ description = ''
+ Languages available for OCR via Tesseract, specified as
+ <literal>ISO 639-2/T</literal> language codes.
+ If unset, defaults to all available languages.
+ '';
+ example = [ "eng" "spa" "jpn" ];
+ };
+
+ address = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Server listening address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 28981;
+ description = "Server port to listen on.";
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ description = ''
+ Extra paperless config options.
+
+ The config values are evaluated as double-quoted Bash string literals.
+
+ See <literal>paperless-src/paperless.conf.example</literal> for available options.
+
+ To enable user authentication, set <literal>PAPERLESS_DISABLE_LOGIN = "false"</literal>
+ and run the shell command <literal>$dataDir/paperless-manage createsuperuser</literal>.
+
+ To define secret options without storing them in /nix/store, use the following pattern:
+ <literal>PAPERLESS_PASSPHRASE = "$(&lt; /etc/my_passphrase_file)"</literal>
+ '';
+ example = literalExample ''
+ {
+ PAPERLESS_OCR_LANGUAGE = "deu";
+ }
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = defaultUser;
+ description = "User under which Paperless runs.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.paperless;
+ defaultText = "pkgs.paperless";
+ description = "The Paperless package to use.";
+ };
+
+ manage = mkOption {
+ type = types.package;
+ readOnly = true;
+ default = manage;
+ description = ''
+ A script to manage the Paperless instance.
+ It wraps Django's manage.py and is also available at
+ <literal>$dataDir/manage-paperless</literal>
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - ${cfg.user} ${cfg.user} - -"
+ ] ++ (optional cfg.consumptionDirIsPublic
+ "d '${cfg.consumptionDir}' 777 ${cfg.user} ${cfg.user} - -"
+ # If the consumption dir is not created here, it's automatically created by
+ # 'manage' with the default permissions.
+ );
+
+ systemd.services.paperless-consumer = {
+ description = "Paperless document consumer";
+ serviceConfig = {
+ User = cfg.user;
+ ExecStart = "${manage} document_consumer";
+ Restart = "always";
+ };
+ after = [ "systemd-tmpfiles-setup.service" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ if [[ $(readlink ${cfg.dataDir}/paperless-manage) != ${manage} ]]; then
+ ln -sf ${manage} ${cfg.dataDir}/paperless-manage
+ fi
+
+ ${manage.setupEnv}
+ # Auto-migrate on first run or if the package has changed
+ versionFile="$PAPERLESS_DBDIR/src-version"
+ if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
+ python $paperlessSrc/manage.py migrate
+ echo ${cfg.package} > "$versionFile"
+ fi
+ '';
+ };
+
+ systemd.services.paperless-server = {
+ description = "Paperless document server";
+ serviceConfig = {
+ User = cfg.user;
+ ExecStart = "${manage} runserver --noreload ${cfg.address}:${toString cfg.port}";
+ Restart = "always";
+ };
+ # Bind to `paperless-consumer` so that the server never runs
+ # during migrations
+ bindsTo = [ "paperless-consumer.service" ];
+ after = [ "paperless-consumer.service" ];
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ users = optionalAttrs (cfg.user == defaultUser) {
+ users = [{
+ name = defaultUser;
+ group = defaultUser;
+ uid = config.ids.uids.paperless;
+ home = cfg.dataDir;
+ }];
+
+ groups = [{
+ name = defaultUser;
+ gid = config.ids.gids.paperless;
+ }];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/parsoid.nix b/nixpkgs/nixos/modules/services/misc/parsoid.nix
new file mode 100644
index 00000000000..c757093e5c1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/parsoid.nix
@@ -0,0 +1,104 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.parsoid;
+
+ parsoid = pkgs.nodePackages."parsoid-git://github.com/abbradar/parsoid#stable";
+
+ confTree = {
+ worker_heartbeat_timeout = 300000;
+ logging = { level = "info"; };
+ services = [{
+ module = "lib/index.js";
+ entrypoint = "apiServiceWorker";
+ conf = {
+ mwApis = map (x: if isAttrs x then x else { uri = x; }) cfg.wikis;
+ serverInterface = cfg.interface;
+ serverPort = cfg.port;
+ };
+ }];
+ };
+
+ confFile = pkgs.writeText "config.yml" (builtins.toJSON (recursiveUpdate confTree cfg.extraConfig));
+
+in
+{
+ ##### interface
+
+ options = {
+
+ services.parsoid = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Parsoid -- bidirectional
+ wikitext parser.
+ '';
+ };
+
+ wikis = mkOption {
+ type = types.listOf (types.either types.str types.attrs);
+ example = [ "http://localhost/api.php" ];
+ description = ''
+ Used MediaWiki API endpoints.
+ '';
+ };
+
+ workers = mkOption {
+ type = types.int;
+ default = 2;
+ description = ''
+ Number of Parsoid workers.
+ '';
+ };
+
+ interface = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ Interface to listen on.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8000;
+ description = ''
+ Port to listen on.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ description = ''
+ Extra configuration to add to parsoid configuration.
+ '';
+ };
+
+ };
+
+ };
+
+ ##### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.parsoid = {
+ description = "Bidirectional wikitext parser";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ User = "nobody";
+ ExecStart = "${parsoid}/lib/node_modules/parsoid/bin/server.js -c ${confFile} -n ${toString cfg.workers}";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/plex.nix b/nixpkgs/nixos/modules/services/misc/plex.nix
new file mode 100644
index 00000000000..7efadf1b9bb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/plex.nix
@@ -0,0 +1,151 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.plex;
+in
+{
+ options = {
+ services.plex = {
+ enable = mkEnableOption "Plex Media Server";
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/plex";
+ description = ''
+ The directory where Plex stores its data files.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for the media server.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "plex";
+ description = ''
+ User account under which Plex runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "plex";
+ description = ''
+ Group under which Plex runs.
+ '';
+ };
+
+ managePlugins = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If set to true, this option will cause all of the symlinks in Plex's
+ plugin directory to be removed and symlinks for paths specified in
+ <option>extraPlugins</option> to be added.
+ '';
+ };
+
+ extraPlugins = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ A list of paths to extra plugin bundles to install in Plex's plugin
+ directory. Every time the systemd unit for Plex starts up, all of the
+ symlinks in Plex's plugin directory will be cleared and this module
+ will symlink all of the paths specified here to that directory. If
+ this behavior is undesired, set <option>managePlugins</option> to
+ false.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.plex;
+ defaultText = "pkgs.plex";
+ description = ''
+ The Plex package to use. Plex subscribers may wish to use their own
+ package here, pointing to subscriber-only server versions.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # Most of this is just copied from the RPM package's systemd service file.
+ systemd.services.plex = {
+ description = "Plex Media Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+
+ # Run the pre-start script with full permissions (the "!" prefix) so it
+ # can create the data directory if necessary.
+ ExecStartPre = let
+ preStartScript = pkgs.writeScript "plex-run-prestart" ''
+ #!${pkgs.bash}/bin/bash
+
+ # Create data directory if it doesn't exist
+ if ! test -d "$PLEX_DATADIR"; then
+ echo "Creating initial Plex data directory in: $PLEX_DATADIR"
+ install -d -m 0755 -o "${cfg.user}" -g "${cfg.group}" "$PLEX_DATADIR"
+ fi
+ '';
+ in
+ "!${preStartScript}";
+
+ ExecStart = "${cfg.package}/bin/plexmediaserver";
+ KillSignal = "SIGQUIT";
+ Restart = "on-failure";
+ };
+
+ environment = {
+ # Configuration for our FHS userenv script
+ PLEX_DATADIR=cfg.dataDir;
+ PLEX_PLUGINS=concatMapStringsSep ":" builtins.toString cfg.extraPlugins;
+
+ # The following variables should be set by the FHS userenv script:
+ # PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR
+ # PLEX_MEDIA_SERVER_HOME
+
+ # Allow access to GPU acceleration; the Plex LD_LIBRARY_PATH is added
+ # by the FHS userenv script.
+ LD_LIBRARY_PATH="/run/opengl-driver/lib";
+
+ PLEX_MEDIA_SERVER_MAX_PLUGIN_PROCS="6";
+ PLEX_MEDIA_SERVER_TMPDIR="/tmp";
+ PLEX_MEDIA_SERVER_USE_SYSLOG="true";
+ LC_ALL="en_US.UTF-8";
+ LANG="en_US.UTF-8";
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ 32400 3005 8324 32469 ];
+ allowedUDPPorts = [ 1900 5353 32410 32412 32413 32414 ];
+ };
+
+ users.users = mkIf (cfg.user == "plex") {
+ plex = {
+ group = cfg.group;
+ uid = config.ids.uids.plex;
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "plex") {
+ plex = {
+ gid = config.ids.gids.plex;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/pykms.nix b/nixpkgs/nixos/modules/services/misc/pykms.nix
new file mode 100644
index 00000000000..e2d1254602b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/pykms.nix
@@ -0,0 +1,86 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.pykms;
+ libDir = "/var/lib/pykms";
+
+in {
+ meta.maintainers = with lib.maintainers; [ peterhoeg ];
+
+ options = {
+ services.pykms = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the PyKMS service.";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = "The IP address on which to listen.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 1688;
+ description = "The port on which to listen.";
+ };
+
+ openFirewallPort = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether the listening port should be opened automatically.";
+ };
+
+ memoryLimit = mkOption {
+ type = types.str;
+ default = "64M";
+ description = "How much memory to use at most.";
+ };
+
+ logLevel = mkOption {
+ type = types.enum [ "CRITICAL" "ERROR" "WARNING" "INFO" "DEBUG" "MINI" ];
+ default = "INFO";
+ description = "How much to log";
+ };
+
+ extraArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Additional arguments";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewallPort [ cfg.port ];
+
+ systemd.services.pykms = {
+ description = "Python KMS";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ # python programs with DynamicUser = true require HOME to be set
+ environment.HOME = libDir;
+ serviceConfig = with pkgs; {
+ DynamicUser = true;
+ StateDirectory = baseNameOf libDir;
+ ExecStartPre = "${getBin pykms}/libexec/create_pykms_db.sh ${libDir}/clients.db";
+ ExecStart = lib.concatStringsSep " " ([
+ "${getBin pykms}/bin/server"
+ "--logfile STDOUT"
+ "--loglevel ${cfg.logLevel}"
+ ] ++ cfg.extraArgs ++ [
+ cfg.listenAddress
+ (toString cfg.port)
+ ]);
+ ProtectHome = "tmpfs";
+ WorkingDirectory = libDir;
+ Restart = "on-failure";
+ MemoryLimit = cfg.memoryLimit;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/radarr.nix b/nixpkgs/nixos/modules/services/misc/radarr.nix
new file mode 100644
index 00000000000..74444e24043
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/radarr.nix
@@ -0,0 +1,75 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.radarr;
+
+in
+{
+ options = {
+ services.radarr = {
+ enable = mkEnableOption "Radarr";
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/radarr/.config/Radarr";
+ description = "The directory where Radarr stores its data files.";
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Open ports in the firewall for the Radarr web interface.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "radarr";
+ description = "User account under which Radarr runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "radarr";
+ description = "Group under which Radarr runs.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.radarr = {
+ description = "Radarr";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${pkgs.radarr}/bin/Radarr -nobrowser -data='${cfg.dataDir}'";
+ Restart = "on-failure";
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ 7878 ];
+ };
+
+ users.users = mkIf (cfg.user == "radarr") {
+ radarr = {
+ group = cfg.group;
+ home = cfg.dataDir;
+ uid = config.ids.uids.radarr;
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "radarr") {
+ radarr.gid = config.ids.gids.radarr;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/redmine.nix b/nixpkgs/nixos/modules/services/misc/redmine.nix
new file mode 100644
index 00000000000..24b9e27ac2d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/redmine.nix
@@ -0,0 +1,404 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkDefault mkEnableOption mkIf mkOption types;
+ inherit (lib) concatStringsSep literalExample mapAttrsToList;
+ inherit (lib) optional optionalAttrs optionalString singleton versionAtLeast;
+
+ cfg = config.services.redmine;
+
+ bundle = "${cfg.package}/share/redmine/bin/bundle";
+
+ databaseYml = pkgs.writeText "database.yml" ''
+ production:
+ adapter: ${cfg.database.type}
+ database: ${cfg.database.name}
+ host: ${if (cfg.database.type == "postgresql" && cfg.database.socket != null) then cfg.database.socket else cfg.database.host}
+ port: ${toString cfg.database.port}
+ username: ${cfg.database.user}
+ password: #dbpass#
+ ${optionalString (cfg.database.type == "mysql2" && cfg.database.socket != null) "socket: ${cfg.database.socket}"}
+ '';
+
+ configurationYml = pkgs.writeText "configuration.yml" ''
+ default:
+ scm_subversion_command: ${pkgs.subversion}/bin/svn
+ scm_mercurial_command: ${pkgs.mercurial}/bin/hg
+ scm_git_command: ${pkgs.gitAndTools.git}/bin/git
+ scm_cvs_command: ${pkgs.cvs}/bin/cvs
+ scm_bazaar_command: ${pkgs.bazaar}/bin/bzr
+ scm_darcs_command: ${pkgs.darcs}/bin/darcs
+
+ ${cfg.extraConfig}
+ '';
+
+ additionalEnvironment = pkgs.writeText "additional_environment.rb" ''
+ config.logger = Logger.new("${cfg.stateDir}/log/production.log", 14, 1048576)
+ config.logger.level = Logger::INFO
+
+ ${cfg.extraEnv}
+ '';
+
+ unpackTheme = unpack "theme";
+ unpackPlugin = unpack "plugin";
+ unpack = id: (name: source:
+ pkgs.stdenv.mkDerivation {
+ name = "redmine-${id}-${name}";
+ buildInputs = [ pkgs.unzip ];
+ buildCommand = ''
+ mkdir -p $out
+ cd $out
+ unpackFile ${source}
+ '';
+ });
+
+ mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql2";
+ pgsqlLocal = cfg.database.createLocally && cfg.database.type == "postgresql";
+
+in
+
+{
+ options = {
+ services.redmine = {
+ enable = mkEnableOption "Redmine";
+
+ # 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";
+ description = "User under which Redmine is ran.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "redmine";
+ description = "Group under which Redmine is ran.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 3000;
+ description = "Port on which Redmine is ran.";
+ };
+
+ stateDir = mkOption {
+ type = types.str;
+ default = "/var/lib/redmine";
+ description = "The state directory, logs and plugins are stored here.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration in configuration.yml.
+
+ See <link xlink:href="https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration"/>
+ for details.
+ '';
+ example = literalExample ''
+ email_delivery:
+ delivery_method: smtp
+ smtp_settings:
+ address: mail.example.com
+ port: 25
+ '';
+ };
+
+ extraEnv = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration in additional_environment.rb.
+
+ See <link xlink:href="https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example"/>
+ for details.
+ '';
+ example = literalExample ''
+ config.logger.level = Logger::DEBUG
+ '';
+ };
+
+ themes = mkOption {
+ type = types.attrsOf types.path;
+ default = {};
+ description = "Set of themes.";
+ example = literalExample ''
+ {
+ dkuk-redmine_alex_skin = builtins.fetchurl {
+ url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip;
+ sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
+ };
+ }
+ '';
+ };
+
+ plugins = mkOption {
+ type = types.attrsOf types.path;
+ default = {};
+ description = "Set of plugins.";
+ example = literalExample ''
+ {
+ redmine_env_auth = builtins.fetchurl {
+ url = https://github.com/Intera/redmine_env_auth/archive/0.6.zip;
+ sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak";
+ };
+ }
+ '';
+ };
+
+ database = {
+ type = mkOption {
+ type = types.enum [ "mysql2" "postgresql" ];
+ example = "postgresql";
+ default = "mysql2";
+ description = "Database engine to use.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = if cfg.database.type == "postgresql" then 5432 else 3306;
+ defaultText = "3306";
+ description = "Database host port.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "redmine";
+ description = "Database name.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "redmine";
+ description = "Database user.";
+ };
+
+ password = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ The password corresponding to <option>database.user</option>.
+ Warning: this is stored in cleartext in the Nix store!
+ Use <option>database.passwordFile</option> instead.
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/redmine-dbpassword";
+ description = ''
+ A file containing the password corresponding to
+ <option>database.user</option>.
+ '';
+ };
+
+ socket = mkOption {
+ type = types.nullOr types.path;
+ default =
+ if mysqlLocal then "/run/mysqld/mysqld.sock"
+ else if pgsqlLocal then "/run/postgresql"
+ else null;
+ defaultText = "/run/mysqld/mysqld.sock";
+ example = "/run/mysqld/mysqld.sock";
+ description = "Path to the unix socket file to use for authentication.";
+ };
+
+ createLocally = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Create the database and database user locally.";
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.database.passwordFile != null || cfg.database.password != "" || cfg.database.socket != null;
+ message = "one of services.redmine.database.socket, services.redmine.database.passwordFile, or services.redmine.database.password must be set";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
+ message = "services.redmine.database.user must be set to ${cfg.user} if services.redmine.database.createLocally is set true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.socket != null;
+ message = "services.redmine.database.socket must be set if services.redmine.database.createLocally is set to true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.host == "localhost";
+ message = "services.redmine.database.host must be set to localhost if services.redmine.database.createLocally is set to true";
+ }
+ ];
+
+ services.mysql = mkIf mysqlLocal {
+ enable = true;
+ package = mkDefault pkgs.mariadb;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ services.postgresql = mkIf pgsqlLocal {
+ enable = true;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ # create symlinks for the basic directory layout the redmine package expects
+ systemd.tmpfiles.rules = [
+ "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.stateDir}/cache' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.stateDir}/config' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.stateDir}/files' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.stateDir}/plugins' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.stateDir}/public' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.stateDir}/public/plugin_assets' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.stateDir}/public/themes' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.stateDir}/tmp' 0750 ${cfg.user} ${cfg.group} - -"
+
+ "d /run/redmine - - - - -"
+ "d /run/redmine/public - - - - -"
+ "L+ /run/redmine/config - - - - ${cfg.stateDir}/config"
+ "L+ /run/redmine/files - - - - ${cfg.stateDir}/files"
+ "L+ /run/redmine/log - - - - ${cfg.stateDir}/log"
+ "L+ /run/redmine/plugins - - - - ${cfg.stateDir}/plugins"
+ "L+ /run/redmine/public/plugin_assets - - - - ${cfg.stateDir}/public/plugin_assets"
+ "L+ /run/redmine/public/themes - - - - ${cfg.stateDir}/public/themes"
+ "L+ /run/redmine/tmp - - - - ${cfg.stateDir}/tmp"
+ ];
+
+ systemd.services.redmine = {
+ after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+ wantedBy = [ "multi-user.target" ];
+ environment.RAILS_ENV = "production";
+ environment.RAILS_CACHE = "${cfg.stateDir}/cache";
+ environment.REDMINE_LANG = "en";
+ environment.SCHEMA = "${cfg.stateDir}/cache/schema.db";
+ path = with pkgs; [
+ imagemagick
+ bazaar
+ cvs
+ darcs
+ gitAndTools.git
+ mercurial
+ subversion
+ ];
+ preStart = ''
+ rm -rf "${cfg.stateDir}/plugins/"*
+ rm -rf "${cfg.stateDir}/public/themes/"*
+
+ # start with a fresh config directory
+ # the config directory is copied instead of linked as some mutable data is stored in there
+ find "${cfg.stateDir}/config" ! -name "secret_token.rb" -type f -exec rm -f {} +
+ cp -r ${cfg.package}/share/redmine/config.dist/* "${cfg.stateDir}/config/"
+
+ chmod -R u+w "${cfg.stateDir}/config"
+
+ # link in the application configuration
+ ln -fs ${configurationYml} "${cfg.stateDir}/config/configuration.yml"
+
+ # link in the additional environment configuration
+ ln -fs ${additionalEnvironment} "${cfg.stateDir}/config/additional_environment.rb"
+
+
+ # link in all user specified themes
+ for theme in ${concatStringsSep " " (mapAttrsToList unpackTheme cfg.themes)}; do
+ ln -fs $theme/* "${cfg.stateDir}/public/themes"
+ done
+
+ # link in redmine provided themes
+ ln -sf ${cfg.package}/share/redmine/public/themes.dist/* "${cfg.stateDir}/public/themes/"
+
+
+ # link in all user specified plugins
+ for plugin in ${concatStringsSep " " (mapAttrsToList unpackPlugin cfg.plugins)}; do
+ ln -fs $plugin/* "${cfg.stateDir}/plugins/''${plugin##*-redmine-plugin-}"
+ done
+
+
+ # handle database.passwordFile & permissions
+ DBPASS=$(head -n1 ${cfg.database.passwordFile})
+ cp -f ${databaseYml} "${cfg.stateDir}/config/database.yml"
+ sed -e "s,#dbpass#,$DBPASS,g" -i "${cfg.stateDir}/config/database.yml"
+ chmod 440 "${cfg.stateDir}/config/database.yml"
+
+
+ # generate a secret token if required
+ if ! test -e "${cfg.stateDir}/config/initializers/secret_token.rb"; then
+ ${bundle} exec rake generate_secret_token
+ chmod 440 "${cfg.stateDir}/config/initializers/secret_token.rb"
+ fi
+
+ # execute redmine required commands prior to starting the application
+ ${bundle} exec rake db:migrate
+ ${bundle} exec rake redmine:plugins:migrate
+ ${bundle} exec rake redmine:load_default_data
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ TimeoutSec = "300";
+ WorkingDirectory = "${cfg.package}/share/redmine";
+ ExecStart="${bundle} exec rails server webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'";
+ };
+
+ };
+
+ users.users = optionalAttrs (cfg.user == "redmine") (singleton
+ { name = "redmine";
+ group = cfg.group;
+ home = cfg.stateDir;
+ uid = config.ids.uids.redmine;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "redmine") (singleton
+ { name = "redmine";
+ gid = config.ids.gids.redmine;
+ });
+
+ warnings = optional (cfg.database.password != "")
+ ''config.services.redmine.database.password will be stored as plaintext
+ in the Nix store. Use database.passwordFile instead.'';
+
+ # Create database passwordFile default when password is configured.
+ services.redmine.database.passwordFile =
+ (mkDefault (toString (pkgs.writeTextFile {
+ name = "redmine-database-password";
+ text = cfg.database.password;
+ })));
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix b/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix
new file mode 100644
index 00000000000..042b496d35e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.rippleDataApi;
+
+ deployment_env_config = builtins.toJSON {
+ production = {
+ port = toString cfg.port;
+ maxSockets = 150;
+ batchSize = 100;
+ startIndex = 32570;
+ rippleds = cfg.rippleds;
+ redis = {
+ enable = cfg.redis.enable;
+ host = cfg.redis.host;
+ port = cfg.redis.port;
+ options.auth_pass = null;
+ };
+ };
+ };
+
+ db_config = builtins.toJSON {
+ production = {
+ username = optional (cfg.couchdb.pass != "") cfg.couchdb.user;
+ password = optional (cfg.couchdb.pass != "") cfg.couchdb.pass;
+ host = cfg.couchdb.host;
+ port = cfg.couchdb.port;
+ database = cfg.couchdb.db;
+ protocol = "http";
+ };
+ };
+
+in {
+ options = {
+ services.rippleDataApi = {
+ enable = mkEnableOption "ripple data api";
+
+ port = mkOption {
+ description = "Ripple data api port";
+ default = 5993;
+ type = types.int;
+ };
+
+ importMode = mkOption {
+ description = "Ripple data api import mode.";
+ default = "liveOnly";
+ type = types.enum ["live" "liveOnly"];
+ };
+
+ minLedger = mkOption {
+ description = "Ripple data api minimal ledger to fetch.";
+ default = null;
+ type = types.nullOr types.int;
+ };
+
+ maxLedger = mkOption {
+ description = "Ripple data api maximal ledger to fetch.";
+ default = null;
+ type = types.nullOr types.int;
+ };
+
+ redis = {
+ enable = mkOption {
+ description = "Whether to enable caching of ripple data to redis.";
+ default = true;
+ type = types.bool;
+ };
+
+ host = mkOption {
+ description = "Ripple data api redis host.";
+ default = "localhost";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Ripple data api redis port.";
+ default = 5984;
+ type = types.int;
+ };
+ };
+
+ couchdb = {
+ host = mkOption {
+ description = "Ripple data api couchdb host.";
+ default = "localhost";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Ripple data api couchdb port.";
+ default = 5984;
+ type = types.int;
+ };
+
+ db = mkOption {
+ description = "Ripple data api couchdb database.";
+ default = "rippled";
+ type = types.str;
+ };
+
+ user = mkOption {
+ description = "Ripple data api couchdb username.";
+ default = "rippled";
+ type = types.str;
+ };
+
+ pass = mkOption {
+ description = "Ripple data api couchdb password.";
+ default = "";
+ type = types.str;
+ };
+
+ create = mkOption {
+ description = "Whether to create couchdb database needed by ripple data api.";
+ type = types.bool;
+ default = true;
+ };
+ };
+
+ rippleds = mkOption {
+ description = "List of rippleds to be used by ripple data api.";
+ default = [
+ "http://s_east.ripple.com:51234"
+ "http://s_west.ripple.com:51234"
+ ];
+ type = types.listOf types.str;
+ };
+ };
+ };
+
+ config = mkIf (cfg.enable) {
+ services.couchdb.enable = mkDefault true;
+ services.couchdb.bindAddress = mkDefault "0.0.0.0";
+ services.redis.enable = mkDefault true;
+
+ systemd.services.ripple-data-api = {
+ after = [ "couchdb.service" "redis.service" "ripple-data-api-importer.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ environment = {
+ NODE_ENV = "production";
+ DEPLOYMENT_ENVS_CONFIG = pkgs.writeText "deployment.environment.json" deployment_env_config;
+ DB_CONFIG = pkgs.writeText "db.config.json" db_config;
+ };
+
+ serviceConfig = {
+ ExecStart = "${pkgs.ripple-data-api}/bin/api";
+ Restart = "always";
+ User = "ripple-data-api";
+ };
+ };
+
+ systemd.services.ripple-data-importer = {
+ after = [ "couchdb.service" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.curl ];
+
+ environment = {
+ NODE_ENV = "production";
+ DEPLOYMENT_ENVS_CONFIG = pkgs.writeText "deployment.environment.json" deployment_env_config;
+ DB_CONFIG = pkgs.writeText "db.config.json" db_config;
+ LOG_FILE = "/dev/null";
+ };
+
+ serviceConfig = let
+ importMode =
+ if cfg.minLedger != null && cfg.maxLedger != null then
+ "${toString cfg.minLedger} ${toString cfg.maxLedger}"
+ else
+ cfg.importMode;
+ in {
+ ExecStart = "${pkgs.ripple-data-api}/bin/importer ${importMode} debug";
+ Restart = "always";
+ User = "ripple-data-api";
+ };
+
+ preStart = mkMerge [
+ (mkIf (cfg.couchdb.create) ''
+ HOST="http://${optionalString (cfg.couchdb.pass != "") "${cfg.couchdb.user}:${cfg.couchdb.pass}@"}${cfg.couchdb.host}:${toString cfg.couchdb.port}"
+ curl -X PUT $HOST/${cfg.couchdb.db} || true
+ '')
+ "${pkgs.ripple-data-api}/bin/update-views"
+ ];
+ };
+
+ users.users = singleton
+ { name = "ripple-data-api";
+ description = "Ripple data api user";
+ uid = config.ids.uids.ripple-data-api;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/rippled.nix b/nixpkgs/nixos/modules/services/misc/rippled.nix
new file mode 100644
index 00000000000..cdf61730de3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/rippled.nix
@@ -0,0 +1,432 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.rippled;
+
+ b2i = val: if val then "1" else "0";
+
+ dbCfg = db: ''
+ type=${db.type}
+ path=${db.path}
+ ${optionalString (db.compression != null) ("compression=${b2i db.compression}") }
+ ${optionalString (db.onlineDelete != null) ("online_delete=${toString db.onlineDelete}")}
+ ${optionalString (db.advisoryDelete != null) ("advisory_delete=${b2i db.advisoryDelete}")}
+ ${db.extraOpts}
+ '';
+
+ rippledCfg = ''
+ [server]
+ ${concatMapStringsSep "\n" (n: "port_${n}") (attrNames cfg.ports)}
+
+ ${concatMapStrings (p: ''
+ [port_${p.name}]
+ ip=${p.ip}
+ port=${toString p.port}
+ protocol=${concatStringsSep "," p.protocol}
+ ${optionalString (p.user != "") "user=${p.user}"}
+ ${optionalString (p.password != "") "user=${p.password}"}
+ admin=${concatStringsSep "," p.admin}
+ ${optionalString (p.ssl.key != null) "ssl_key=${p.ssl.key}"}
+ ${optionalString (p.ssl.cert != null) "ssl_cert=${p.ssl.cert}"}
+ ${optionalString (p.ssl.chain != null) "ssl_chain=${p.ssl.chain}"}
+ '') (attrValues cfg.ports)}
+
+ [database_path]
+ ${cfg.databasePath}
+
+ [node_db]
+ ${dbCfg cfg.nodeDb}
+
+ ${optionalString (cfg.tempDb != null) ''
+ [temp_db]
+ ${dbCfg cfg.tempDb}''}
+
+ ${optionalString (cfg.importDb != null) ''
+ [import_db]
+ ${dbCfg cfg.importDb}''}
+
+ [ips]
+ ${concatStringsSep "\n" cfg.ips}
+
+ [ips_fixed]
+ ${concatStringsSep "\n" cfg.ipsFixed}
+
+ [validators]
+ ${concatStringsSep "\n" cfg.validators}
+
+ [node_size]
+ ${cfg.nodeSize}
+
+ [ledger_history]
+ ${toString cfg.ledgerHistory}
+
+ [fetch_depth]
+ ${toString cfg.fetchDepth}
+
+ [validation_quorum]
+ ${toString cfg.validationQuorum}
+
+ [sntp_servers]
+ ${concatStringsSep "\n" cfg.sntpServers}
+
+ ${optionalString cfg.statsd.enable ''
+ [insight]
+ server=statsd
+ address=${cfg.statsd.address}
+ prefix=${cfg.statsd.prefix}
+ ''}
+
+ [rpc_startup]
+ { "command": "log_level", "severity": "${cfg.logLevel}" }
+ '' + cfg.extraConfig;
+
+ portOptions = { name, ...}: {
+ options = {
+ name = mkOption {
+ internal = true;
+ default = name;
+ };
+
+ ip = mkOption {
+ default = "127.0.0.1";
+ description = "Ip where rippled listens.";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Port where rippled listens.";
+ type = types.int;
+ };
+
+ protocol = mkOption {
+ description = "Protocols expose by rippled.";
+ type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]);
+ };
+
+ user = mkOption {
+ description = "When set, these credentials will be required on HTTP/S requests.";
+ type = types.str;
+ default = "";
+ };
+
+ password = mkOption {
+ description = "When set, these credentials will be required on HTTP/S requests.";
+ type = types.str;
+ default = "";
+ };
+
+ admin = mkOption {
+ description = "A comma-separated list of admin IP addresses.";
+ type = types.listOf types.str;
+ default = ["127.0.0.1"];
+ };
+
+ ssl = {
+ key = mkOption {
+ description = ''
+ Specifies the filename holding the SSL key in PEM format.
+ '';
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ cert = mkOption {
+ description = ''
+ Specifies the path to the SSL certificate file in PEM format.
+ This is not needed if the chain includes it.
+ '';
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ chain = mkOption {
+ description = ''
+ If you need a certificate chain, specify the path to the
+ certificate chain here. The chain may include the end certificate.
+ '';
+ default = null;
+ type = types.nullOr types.path;
+ };
+ };
+ };
+ };
+
+ dbOptions = {
+ options = {
+ type = mkOption {
+ description = "Rippled database type.";
+ type = types.enum ["rocksdb" "nudb"];
+ default = "rocksdb";
+ };
+
+ path = mkOption {
+ description = "Location to store the database.";
+ type = types.path;
+ default = cfg.databasePath;
+ };
+
+ compression = mkOption {
+ description = "Whether to enable snappy compression.";
+ type = types.nullOr types.bool;
+ default = null;
+ };
+
+ onlineDelete = mkOption {
+ description = "Enable automatic purging of older ledger information.";
+ type = types.nullOr (types.addCheck types.int (v: v > 256));
+ default = cfg.ledgerHistory;
+ };
+
+ advisoryDelete = mkOption {
+ description = ''
+ If set, then require administrative RPC call "can_delete"
+ to enable online deletion of ledger records.
+ '';
+ type = types.nullOr types.bool;
+ default = null;
+ };
+
+ extraOpts = mkOption {
+ description = "Extra database options.";
+ type = types.lines;
+ default = "";
+ };
+ };
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.rippled = {
+ enable = mkEnableOption "rippled";
+
+ package = mkOption {
+ description = "Which rippled package to use.";
+ type = types.package;
+ default = pkgs.rippled;
+ defaultText = "pkgs.rippled";
+ };
+
+ ports = mkOption {
+ description = "Ports exposed by rippled";
+ type = with types; attrsOf (submodule portOptions);
+ default = {
+ rpc = {
+ port = 5005;
+ admin = ["127.0.0.1"];
+ protocol = ["http"];
+ };
+
+ peer = {
+ port = 51235;
+ ip = "0.0.0.0";
+ protocol = ["peer"];
+ };
+
+ ws_public = {
+ port = 5006;
+ ip = "0.0.0.0";
+ protocol = ["ws" "wss"];
+ };
+ };
+ };
+
+ nodeDb = mkOption {
+ description = "Rippled main database options.";
+ type = with types; nullOr (submodule dbOptions);
+ default = {
+ type = "rocksdb";
+ extraOpts = ''
+ open_files=2000
+ filter_bits=12
+ cache_mb=256
+ file_size_pb=8
+ file_size_mult=2;
+ '';
+ };
+ };
+
+ tempDb = mkOption {
+ description = "Rippled temporary database options.";
+ type = with types; nullOr (submodule dbOptions);
+ default = null;
+ };
+
+ importDb = mkOption {
+ description = "Settings for performing a one-time import.";
+ type = with types; nullOr (submodule dbOptions);
+ default = null;
+ };
+
+ nodeSize = mkOption {
+ description = ''
+ Rippled size of the node you are running.
+ "tiny", "small", "medium", "large", and "huge"
+ '';
+ type = types.enum ["tiny" "small" "medium" "large" "huge"];
+ default = "small";
+ };
+
+ ips = mkOption {
+ description = ''
+ List of hostnames or ips where the Ripple protocol is served.
+ For a starter list, you can either copy entries from:
+ https://ripple.com/ripple.txt or if you prefer you can let it
+ default to r.ripple.com 51235
+
+ A port may optionally be specified after adding a space to the
+ address. By convention, if known, IPs are listed in from most
+ to least trusted.
+ '';
+ type = types.listOf types.str;
+ default = ["r.ripple.com 51235"];
+ };
+
+ ipsFixed = mkOption {
+ description = ''
+ List of IP addresses or hostnames to which rippled should always
+ attempt to maintain peer connections with. This is useful for
+ manually forming private networks, for example to configure a
+ validation server that connects to the Ripple network through a
+ public-facing server, or for building a set of cluster peers.
+
+ A port may optionally be specified after adding a space to the address
+ '';
+ type = types.listOf types.str;
+ default = [];
+ };
+
+ validators = mkOption {
+ description = ''
+ List of nodes to always accept as validators. Nodes are specified by domain
+ or public key.
+ '';
+ type = types.listOf types.str;
+ default = [
+ "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1"
+ "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2"
+ "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3"
+ "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4"
+ "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5"
+ ];
+ };
+
+ databasePath = mkOption {
+ description = ''
+ Path to the ripple database.
+ '';
+ type = types.path;
+ default = "/var/lib/rippled";
+ };
+
+ validationQuorum = mkOption {
+ description = ''
+ The minimum number of trusted validations a ledger must have before
+ the server considers it fully validated.
+ '';
+ type = types.int;
+ default = 3;
+ };
+
+ ledgerHistory = mkOption {
+ description = ''
+ The number of past ledgers to acquire on server startup and the minimum
+ to maintain while running.
+ '';
+ type = types.either types.int (types.enum ["full"]);
+ default = 1296000; # 1 month
+ };
+
+ fetchDepth = mkOption {
+ description = ''
+ The number of past ledgers to serve to other peers that request historical
+ ledger data (or "full" for no limit).
+ '';
+ type = types.either types.int (types.enum ["full"]);
+ default = "full";
+ };
+
+ sntpServers = mkOption {
+ description = ''
+ IP address or domain of NTP servers to use for time synchronization.;
+ '';
+ type = types.listOf types.str;
+ default = [
+ "time.windows.com"
+ "time.apple.com"
+ "time.nist.gov"
+ "pool.ntp.org"
+ ];
+ };
+
+ logLevel = mkOption {
+ description = "Logging verbosity.";
+ type = types.enum ["debug" "error" "info"];
+ default = "error";
+ };
+
+ statsd = {
+ enable = mkEnableOption "statsd monitoring for rippled";
+
+ address = mkOption {
+ description = "The UDP address and port of the listening StatsD server.";
+ default = "127.0.0.1:8125";
+ type = types.str;
+ };
+
+ prefix = mkOption {
+ description = "A string prepended to each collected metric.";
+ default = "";
+ type = types.str;
+ };
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ description = ''
+ Extra lines to be added verbatim to the rippled.cfg configuration file.
+ '';
+ };
+
+ config = mkOption {
+ internal = true;
+ default = pkgs.writeText "rippled.conf" rippledCfg;
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton
+ { name = "rippled";
+ description = "Ripple server user";
+ uid = config.ids.uids.rippled;
+ home = cfg.databasePath;
+ createHome = true;
+ };
+
+ systemd.services.rippled = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}";
+ User = "rippled";
+ Restart = "on-failure";
+ LimitNOFILE=10000;
+ };
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/rogue.nix b/nixpkgs/nixos/modules/services/misc/rogue.nix
new file mode 100644
index 00000000000..aae02e384c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/rogue.nix
@@ -0,0 +1,62 @@
+# Execute the game `rogue' on tty 9. Mostly used by the NixOS
+# installation CD.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.rogue;
+
+in
+
+{
+ ###### interface
+
+ options = {
+
+ services.rogue.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the Rogue game on one of the virtual
+ consoles.
+ '';
+ };
+
+ services.rogue.tty = mkOption {
+ type = types.str;
+ default = "tty9";
+ description = ''
+ Virtual console on which to run Rogue.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ boot.extraTTYs = [ cfg.tty ];
+
+ systemd.services.rogue =
+ { description = "Rogue dungeon crawling game";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig =
+ { ExecStart = "${pkgs.rogue}/bin/rogue";
+ StandardInput = "tty";
+ StandardOutput = "tty";
+ TTYPath = "/dev/${cfg.tty}";
+ TTYReset = true;
+ TTYVTDisallocate = true;
+ WorkingDirectory = "/tmp";
+ Restart = "always";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/safeeyes.nix b/nixpkgs/nixos/modules/services/misc/safeeyes.nix
new file mode 100644
index 00000000000..1a33971d922
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/safeeyes.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.safeeyes;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.safeeyes = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable the safeeyes OSGi service";
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.user.services.safeeyes = {
+ description = "Safeeyes";
+
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.safeeyes}/bin/safeeyes
+ '';
+ Restart = "on-failure";
+ RestartSec = 3;
+ StartLimitInterval = 350;
+ StartLimitBurst = 10;
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/serviio.nix b/nixpkgs/nixos/modules/services/misc/serviio.nix
new file mode 100644
index 00000000000..9868192724b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/serviio.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.serviio;
+
+ serviioStart = pkgs.writeScript "serviio.sh" ''
+ #!${pkgs.bash}/bin/sh
+
+ SERVIIO_HOME=${pkgs.serviio}
+
+ # Setup the classpath
+ SERVIIO_CLASS_PATH="$SERVIIO_HOME/lib/*:$SERVIIO_HOME/config"
+
+ # Setup Serviio specific properties
+ JAVA_OPTS="-Djava.net.preferIPv4Stack=true -Djava.awt.headless=true -Dorg.restlet.engine.loggerFacadeClass=org.restlet.ext.slf4j.Slf4jLoggerFacade
+ -Dderby.system.home=${cfg.dataDir}/library -Dserviio.home=${cfg.dataDir} -Dffmpeg.location=${pkgs.ffmpeg}/bin/ffmpeg -Ddcraw.location=${pkgs.dcraw}/bin/dcraw"
+
+ # Execute the JVM in the foreground
+ exec ${pkgs.jre}/bin/java -Xmx512M -Xms20M -XX:+UseG1GC -XX:GCTimeRatio=1 -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 $JAVA_OPTS -classpath "$SERVIIO_CLASS_PATH" org.serviio.MediaServer "$@"
+ '';
+
+in {
+
+ ###### interface
+ options = {
+ services.serviio = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the Serviio Media Server.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/serviio";
+ description = ''
+ The directory where serviio stores its state, data, etc.
+ '';
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.serviio = {
+ description = "Serviio Media Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.serviio ];
+ serviceConfig = {
+ User = "serviio";
+ Group = "serviio";
+ ExecStart = "${serviioStart}";
+ ExecStop = "${serviioStart} -stop";
+ };
+ };
+
+ users.users = [
+ {
+ name = "serviio";
+ group = "serviio";
+ home = cfg.dataDir;
+ description = "Serviio Media Server User";
+ createHome = true;
+ isSystemUser = true;
+ }
+ ];
+
+ users.groups = [
+ { name = "serviio";}
+ ];
+
+ networking.firewall = {
+ allowedTCPPorts = [
+ 8895 # serve UPnP responses
+ 23423 # console
+ 23424 # mediabrowser
+ ];
+ allowedUDPPorts = [
+ 1900 # UPnP service discovey
+ ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/sickbeard.nix b/nixpkgs/nixos/modules/services/misc/sickbeard.nix
new file mode 100644
index 00000000000..5cfbbe516ae
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sickbeard.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ name = "sickbeard";
+
+ cfg = config.services.sickbeard;
+ sickbeard = cfg.package;
+
+in
+{
+
+ ###### interface
+
+ options = {
+ services.sickbeard = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the sickbeard server.";
+ };
+ package = mkOption {
+ type = types.package;
+ default = pkgs.sickbeard;
+ example = literalExample "pkgs.sickrage";
+ description =''
+ Enable <literal>pkgs.sickrage</literal> or <literal>pkgs.sickgear</literal>
+ as an alternative to SickBeard
+ '';
+ };
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/${name}";
+ description = "Path where to store data files.";
+ };
+ configFile = mkOption {
+ type = types.path;
+ default = "${cfg.dataDir}/config.ini";
+ description = "Path to config file.";
+ };
+ port = mkOption {
+ type = types.ints.u16;
+ default = 8081;
+ description = "Port to bind to.";
+ };
+ user = mkOption {
+ type = types.str;
+ default = name;
+ description = "User to run the service as";
+ };
+ group = mkOption {
+ type = types.str;
+ default = name;
+ description = "Group to run the service as";
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = optionalAttrs (cfg.user == name) (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/nixpkgs/nixos/modules/services/misc/siproxd.nix b/nixpkgs/nixos/modules/services/misc/siproxd.nix
new file mode 100644
index 00000000000..dcaf73aca44
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/siproxd.nix
@@ -0,0 +1,180 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.siproxd;
+
+ conf = ''
+ daemonize = 0
+ rtp_proxy_enable = 1
+ user = siproxd
+ if_inbound = ${cfg.ifInbound}
+ if_outbound = ${cfg.ifOutbound}
+ sip_listen_port = ${toString cfg.sipListenPort}
+ rtp_port_low = ${toString cfg.rtpPortLow}
+ rtp_port_high = ${toString cfg.rtpPortHigh}
+ rtp_dscp = ${toString cfg.rtpDscp}
+ sip_dscp = ${toString cfg.sipDscp}
+ ${optionalString (cfg.hostsAllowReg != []) "hosts_allow_reg = ${concatStringsSep "," cfg.hostsAllowReg}"}
+ ${optionalString (cfg.hostsAllowSip != []) "hosts_allow_sip = ${concatStringsSep "," cfg.hostsAllowSip}"}
+ ${optionalString (cfg.hostsDenySip != []) "hosts_deny_sip = ${concatStringsSep "," cfg.hostsDenySip}"}
+ ${if (cfg.passwordFile != "") then "proxy_auth_pwfile = ${cfg.passwordFile}" else ""}
+ ${cfg.extraConfig}
+ '';
+
+ confFile = builtins.toFile "siproxd.conf" conf;
+
+in
+{
+ ##### interface
+
+ options = {
+
+ services.siproxd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the Siproxd SIP
+ proxy/masquerading daemon.
+ '';
+ };
+
+ ifInbound = mkOption {
+ type = types.str;
+ example = "eth0";
+ description = "Local network interface";
+ };
+
+ ifOutbound = mkOption {
+ type = types.str;
+ example = "ppp0";
+ description = "Public network interface";
+ };
+
+ hostsAllowReg = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "192.168.1.0/24" "192.168.2.0/24" ];
+ description = ''
+ Acess control list for incoming SIP registrations.
+ '';
+ };
+
+ hostsAllowSip = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "123.45.0.0/16" "123.46.0.0/16" ];
+ description = ''
+ Acess control list for incoming SIP traffic.
+ '';
+ };
+
+ hostsDenySip = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "10.0.0.0/8" "11.0.0.0/8" ];
+ description = ''
+ Acess control list for denying incoming
+ SIP registrations and traffic.
+ '';
+ };
+
+ sipListenPort = mkOption {
+ type = types.int;
+ default = 5060;
+ description = ''
+ Port to listen for incoming SIP messages.
+ '';
+ };
+
+ rtpPortLow = mkOption {
+ type = types.int;
+ default = 7070;
+ description = ''
+ Bottom of UDP port range for incoming and outgoing RTP traffic
+ '';
+ };
+
+ rtpPortHigh = mkOption {
+ type = types.int;
+ default = 7089;
+ description = ''
+ Top of UDP port range for incoming and outgoing RTP traffic
+ '';
+ };
+
+ rtpTimeout = mkOption {
+ type = types.int;
+ default = 300;
+ description = ''
+ Timeout for an RTP stream. If for the specified
+ number of seconds no data is relayed on an active
+ stream, it is considered dead and will be killed.
+ '';
+ };
+
+ rtpDscp = mkOption {
+ type = types.int;
+ default = 46;
+ description = ''
+ DSCP (differentiated services) value to be assigned
+ to RTP packets. Allows QOS aware routers to handle
+ different types traffic with different priorities.
+ '';
+ };
+
+ sipDscp = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ DSCP (differentiated services) value to be assigned
+ to SIP packets. Allows QOS aware routers to handle
+ different types traffic with different priorities.
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Path to per-user password file.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration to add to siproxd configuration.
+ '';
+ };
+
+ };
+
+ };
+
+ ##### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton {
+ name = "siproxyd";
+ uid = config.ids.uids.siproxd;
+ };
+
+ systemd.services.siproxd = {
+ description = "SIP proxy/masquerading daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.siproxd}/sbin/siproxd -c ${confFile}";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/snapper.nix b/nixpkgs/nixos/modules/services/misc/snapper.nix
new file mode 100644
index 00000000000..6f3aaa973a0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/snapper.nix
@@ -0,0 +1,152 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.snapper;
+in
+
+{
+ options.services.snapper = {
+
+ snapshotInterval = mkOption {
+ type = types.str;
+ default = "hourly";
+ description = ''
+ Snapshot interval.
+
+ The format is described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ '';
+ };
+
+ cleanupInterval = mkOption {
+ type = types.str;
+ default = "1d";
+ description = ''
+ Cleanup interval.
+
+ The format is described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ '';
+ };
+
+ filters = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Global display difference filter. See man:snapper(8) for more details.
+ '';
+ };
+
+ configs = mkOption {
+ default = { };
+ example = literalExample {
+ home = {
+ subvolume = "/home";
+ extraConfig = ''
+ ALLOW_USERS="alice"
+ '';
+ };
+ };
+
+ description = ''
+ Subvolume configuration
+ '';
+
+ type = types.attrsOf (types.submodule {
+ options = {
+ subvolume = mkOption {
+ type = types.path;
+ description = ''
+ Path of the subvolume or mount point.
+ This path is a subvolume and has to contain a subvolume named
+ .snapshots.
+ See also man:snapper(8) section PERMISSIONS.
+ '';
+ };
+
+ fstype = mkOption {
+ type = types.enum [ "btrfs" ];
+ default = "btrfs";
+ description = ''
+ Filesystem type. Only btrfs is stable and tested.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional configuration next to SUBVOLUME and FSTYPE.
+ See man:snapper-configs(5).
+ '';
+ };
+ };
+ });
+ };
+ };
+
+ config = mkIf (cfg.configs != {}) (let
+ documentation = [ "man:snapper(8)" "man:snapper-configs(5)" ];
+ in {
+
+ environment = {
+
+ systemPackages = [ pkgs.snapper ];
+
+ # Note: snapper/config-templates/default is only needed for create-config
+ # which is not the NixOS way to configure.
+ etc = {
+
+ "sysconfig/snapper".text = ''
+ SNAPPER_CONFIGS="${lib.concatStringsSep " " (builtins.attrNames cfg.configs)}"
+ '';
+
+ }
+ // (mapAttrs' (name: subvolume: nameValuePair "snapper/configs/${name}" ({
+ text = ''
+ ${subvolume.extraConfig}
+ FSTYPE="${subvolume.fstype}"
+ SUBVOLUME="${subvolume.subvolume}"
+ '';
+ })) cfg.configs)
+ // (lib.optionalAttrs (cfg.filters != null) {
+ "snapper/filters/default.txt".text = cfg.filters;
+ });
+
+ };
+
+ services.dbus.packages = [ pkgs.snapper ];
+
+ systemd.services.snapper-timeline = {
+ description = "Timeline of Snapper Snapshots";
+ inherit documentation;
+ serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --timeline";
+ };
+
+ systemd.timers.snapper-timeline = {
+ description = "Timeline of Snapper Snapshots";
+ inherit documentation;
+ wantedBy = [ "basic.target" ];
+ timerConfig.OnCalendar = cfg.snapshotInterval;
+ };
+
+ systemd.services.snapper-cleanup = {
+ description = "Cleanup of Snapper Snapshots";
+ inherit documentation;
+ serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --cleanup";
+ };
+
+ systemd.timers.snapper-cleanup = {
+ description = "Cleanup of Snapper Snapshots";
+ inherit documentation;
+ wantedBy = [ "basic.target" ];
+ timerConfig.OnBootSec = "10m";
+ timerConfig.OnUnitActiveSec = cfg.cleanupInterval;
+ };
+ });
+}
+
diff --git a/nixpkgs/nixos/modules/services/misc/sonarr.nix b/nixpkgs/nixos/modules/services/misc/sonarr.nix
new file mode 100644
index 00000000000..77c7f0582d0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sonarr.nix
@@ -0,0 +1,76 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.sonarr;
+in
+{
+ options = {
+ services.sonarr = {
+ enable = mkEnableOption "Sonarr";
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/sonarr/.config/NzbDrone";
+ description = "The directory where Sonarr stores its data files.";
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for the Sonarr web interface
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "sonarr";
+ description = "User account under which Sonaar runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "sonarr";
+ description = "Group under which Sonaar runs.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.sonarr = {
+ description = "Sonarr";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${pkgs.sonarr}/bin/NzbDrone -nobrowser -data='${cfg.dataDir}'";
+ Restart = "on-failure";
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ 8989 ];
+ };
+
+ users.users = mkIf (cfg.user == "sonarr") {
+ sonarr = {
+ group = cfg.group;
+ home = cfg.dataDir;
+ uid = config.ids.uids.sonarr;
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "sonarr") {
+ sonarr.gid = config.ids.gids.sonarr;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix b/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix
new file mode 100644
index 00000000000..2dd9fcf68ab
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix
@@ -0,0 +1,30 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+ cfg = config.services.spice-vdagentd;
+in
+{
+ options = {
+ services.spice-vdagentd = {
+ enable = mkEnableOption "Spice guest vdagent daemon";
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.spice-vdagent ];
+
+ systemd.services.spice-vdagentd = {
+ description = "spice-vdagent daemon";
+ wantedBy = [ "graphical.target" ];
+ preStart = ''
+ mkdir -p "/run/spice-vdagentd/"
+ '';
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.spice-vdagent}/bin/spice-vdagentd";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/ssm-agent.nix b/nixpkgs/nixos/modules/services/misc/ssm-agent.nix
new file mode 100644
index 00000000000..e951a4c7ffa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/ssm-agent.nix
@@ -0,0 +1,46 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+ cfg = config.services.ssm-agent;
+
+ # The SSM agent doesn't pay attention to our /etc/os-release yet, and the lsb-release tool
+ # in nixpkgs doesn't seem to work properly on NixOS, so let's just fake the two fields SSM
+ # looks for. See https://github.com/aws/amazon-ssm-agent/issues/38 for upstream fix.
+ fake-lsb-release = pkgs.writeScriptBin "lsb_release" ''
+ #!${pkgs.runtimeShell}
+
+ case "$1" in
+ -i) echo "nixos";;
+ -r) echo "${config.system.nixos.version}";;
+ esac
+ '';
+in {
+ options.services.ssm-agent = {
+ enable = mkEnableOption "AWS SSM agent";
+
+ package = mkOption {
+ type = types.path;
+ description = "The SSM agent package to use";
+ default = pkgs.ssm-agent;
+ defaultText = "pkgs.ssm-agent";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.ssm-agent = {
+ inherit (cfg.package.meta) description;
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ fake-lsb-release ];
+ serviceConfig = {
+ ExecStart = "${cfg.package.bin}/bin/agent";
+ KillMode = "process";
+ Restart = "on-failure";
+ RestartSec = "15min";
+ };
+ };
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/misc/sssd.nix b/nixpkgs/nixos/modules/services/misc/sssd.nix
new file mode 100644
index 00000000000..6b64045dde8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sssd.nix
@@ -0,0 +1,96 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.sssd;
+ nscd = config.services.nscd;
+in {
+ options = {
+ services.sssd = {
+ enable = mkEnableOption "the System Security Services Daemon";
+
+ config = mkOption {
+ type = types.lines;
+ description = "Contents of <filename>sssd.conf</filename>.";
+ default = ''
+ [sssd]
+ config_file_version = 2
+ services = nss, pam
+ domains = shadowutils
+
+ [nss]
+
+ [pam]
+
+ [domain/shadowutils]
+ id_provider = proxy
+ proxy_lib_name = files
+ auth_provider = proxy
+ proxy_pam_target = sssd-shadowutils
+ proxy_fast_alias = True
+ '';
+ };
+
+ sshAuthorizedKeysIntegration = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to make sshd look up authorized keys from SSS.
+ For this to work, the <literal>ssh</literal> SSS service must be enabled in the sssd configuration.
+ '';
+ };
+ };
+ };
+ config = mkMerge [
+ (mkIf cfg.enable {
+ assertions = singleton {
+ assertion = nscd.enable;
+ message = "nscd must be enabled through `services.nscd.enable` for SSSD to work.";
+ };
+
+ systemd.services.sssd = {
+ description = "System Security Services Daemon";
+ wantedBy = [ "multi-user.target" ];
+ before = [ "systemd-user-sessions.service" "nss-user-lookup.target" ];
+ after = [ "network-online.target" "nscd.service" ];
+ requires = [ "network-online.target" "nscd.service" ];
+ wants = [ "nss-user-lookup.target" ];
+ restartTriggers = [
+ config.environment.etc."nscd.conf".source
+ config.environment.etc."sssd/sssd.conf".source
+ ];
+ script = ''
+ export LDB_MODULES_PATH+="''${LDB_MODULES_PATH+:}${pkgs.ldb}/modules/ldb:${pkgs.sssd}/modules/ldb"
+ mkdir -p /var/lib/sss/{pubconf,db,mc,pipes,gpo_cache,secrets} /var/lib/sss/pipes/private /var/lib/sss/pubconf/krb5.include.d
+ ${pkgs.sssd}/bin/sssd -D
+ '';
+ serviceConfig = {
+ Type = "forking";
+ PIDFile = "/run/sssd.pid";
+ };
+ };
+
+ environment.etc."sssd/sssd.conf" = {
+ text = cfg.config;
+ mode = "0400";
+ };
+
+ system.nssModules = optional cfg.enable pkgs.sssd;
+ services.dbus.packages = [ pkgs.sssd ];
+ })
+
+ (mkIf cfg.sshAuthorizedKeysIntegration {
+ # Ugly: sshd refuses to start if a store path is given because /nix/store is group-writable.
+ # So indirect by a symlink.
+ environment.etc."ssh/authorized_keys_command" = {
+ mode = "0755";
+ text = ''
+ #!/bin/sh
+ exec ${pkgs.sssd}/bin/sss_ssh_authorizedkeys "$@"
+ '';
+ };
+ services.openssh.extraConfig = ''
+ AuthorizedKeysCommand /etc/ssh/authorized_keys_command
+ AuthorizedKeysCommandUser nobody
+ '';
+ })];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/subsonic.nix b/nixpkgs/nixos/modules/services/misc/subsonic.nix
new file mode 100644
index 00000000000..152917d345c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/subsonic.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.subsonic; in {
+ options = {
+ services.subsonic = {
+ enable = mkEnableOption "Subsonic daemon";
+
+ home = mkOption {
+ type = types.path;
+ default = "/var/lib/subsonic";
+ description = ''
+ The directory where Subsonic will create files.
+ Make sure it is writable.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ The host name or IP address on which to bind Subsonic.
+ Only relevant if you have multiple network interfaces and want
+ to make Subsonic available on only one of them. The default value
+ will bind Subsonic to all available network interfaces.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 4040;
+ description = ''
+ The port on which Subsonic will listen for
+ incoming HTTP traffic. Set to 0 to disable.
+ '';
+ };
+
+ httpsPort = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ The port on which Subsonic will listen for
+ incoming HTTPS traffic. Set to 0 to disable.
+ '';
+ };
+
+ contextPath = mkOption {
+ type = types.path;
+ default = "/";
+ description = ''
+ The context path, i.e., the last part of the Subsonic
+ URL. Typically '/' or '/subsonic'. Default '/'
+ '';
+ };
+
+ maxMemory = mkOption {
+ type = types.int;
+ default = 100;
+ description = ''
+ The memory limit (max Java heap size) in megabytes.
+ Default: 100
+ '';
+ };
+
+ defaultMusicFolder = mkOption {
+ type = types.path;
+ default = "/var/music";
+ description = ''
+ Configure Subsonic to use this folder for music. This option
+ only has effect the first time Subsonic is started.
+ '';
+ };
+
+ defaultPodcastFolder = mkOption {
+ type = types.path;
+ default = "/var/music/Podcast";
+ description = ''
+ Configure Subsonic to use this folder for Podcasts. This option
+ only has effect the first time Subsonic is started.
+ '';
+ };
+
+ defaultPlaylistFolder = mkOption {
+ type = types.path;
+ default = "/var/playlists";
+ description = ''
+ Configure Subsonic to use this folder for playlists. This option
+ only has effect the first time Subsonic is started.
+ '';
+ };
+
+ transcoders = mkOption {
+ type = types.listOf types.path;
+ default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
+ description = ''
+ List of paths to transcoder executables that should be accessible
+ from Subsonic. Symlinks will be created to each executable inside
+ ${cfg.home}/transcoders.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.subsonic = {
+ description = "Personal media streamer";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ script = ''
+ ${pkgs.jre}/bin/java -Xmx${toString cfg.maxMemory}m \
+ -Dsubsonic.home=${cfg.home} \
+ -Dsubsonic.host=${cfg.listenAddress} \
+ -Dsubsonic.port=${toString cfg.port} \
+ -Dsubsonic.httpsPort=${toString cfg.httpsPort} \
+ -Dsubsonic.contextPath=${cfg.contextPath} \
+ -Dsubsonic.defaultMusicFolder=${cfg.defaultMusicFolder} \
+ -Dsubsonic.defaultPodcastFolder=${cfg.defaultPodcastFolder} \
+ -Dsubsonic.defaultPlaylistFolder=${cfg.defaultPlaylistFolder} \
+ -Djava.awt.headless=true \
+ -verbose:gc \
+ -jar ${pkgs.subsonic}/subsonic-booter-jar-with-dependencies.jar
+ '';
+
+ preStart = ''
+ # Formerly this module set cfg.home to /var/subsonic. Try to move
+ # /var/subsonic to cfg.home.
+ oldHome="/var/subsonic"
+ if [ "${cfg.home}" != "$oldHome" ] &&
+ ! [ -e "${cfg.home}" ] &&
+ [ -d "$oldHome" ] &&
+ [ $(${pkgs.coreutils}/bin/stat -c %u "$oldHome") -eq \
+ ${toString config.users.users.subsonic.uid} ]; then
+ logger Moving "$oldHome" to "${cfg.home}"
+ ${pkgs.coreutils}/bin/mv -T "$oldHome" "${cfg.home}"
+ fi
+
+ # Install transcoders.
+ ${pkgs.coreutils}/bin/rm -rf ${cfg.home}/transcode ; \
+ ${pkgs.coreutils}/bin/mkdir -p ${cfg.home}/transcode ; \
+ ${pkgs.bash}/bin/bash -c ' \
+ for exe in "$@"; do \
+ ${pkgs.coreutils}/bin/ln -sf "$exe" ${cfg.home}/transcode; \
+ done' IGNORED_FIRST_ARG ${toString cfg.transcoders}
+ '';
+ serviceConfig = {
+ # Needed for Subsonic to find subsonic.war.
+ WorkingDirectory = "${pkgs.subsonic}";
+ Restart = "always";
+ User = "subsonic";
+ UMask = "0022";
+ };
+ };
+
+ users.users.subsonic = {
+ description = "Subsonic daemon user";
+ home = cfg.home;
+ createHome = true;
+ group = "subsonic";
+ uid = config.ids.uids.subsonic;
+ };
+
+ users.groups.subsonic.gid = config.ids.gids.subsonic;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/sundtek.nix b/nixpkgs/nixos/modules/services/misc/sundtek.nix
new file mode 100644
index 00000000000..e3234518c94
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sundtek.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.sundtek;
+
+in
+{
+ options.services.sundtek = {
+ enable = mkEnableOption "Sundtek driver";
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.sundtek ];
+
+ systemd.services.sundtek = {
+ description = "Sundtek driver";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = ''
+ ${pkgs.sundtek}/bin/mediasrv -d -v -p ${pkgs.sundtek}/bin ;\
+ ${pkgs.sundtek}/bin/mediaclient --start --wait-for-devices
+ '';
+ ExecStop = "${pkgs.sundtek}/bin/mediaclient --shutdown";
+ RemainAfterExit = true;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/svnserve.nix b/nixpkgs/nixos/modules/services/misc/svnserve.nix
new file mode 100644
index 00000000000..6292bc52b1e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/svnserve.nix
@@ -0,0 +1,44 @@
+# SVN server
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.svnserve;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.svnserve = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable svnserve to serve Subversion repositories through the SVN protocol.";
+ };
+
+ svnBaseDir = mkOption {
+ default = "/repos";
+ description = "Base directory from which Subversion repositories are accessed.";
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.svnserve = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = "mkdir -p ${cfg.svnBaseDir}";
+ script = "${pkgs.subversion.out}/bin/svnserve -r ${cfg.svnBaseDir} -d --foreground --pid-file=/run/svnserve.pid";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/synergy.nix b/nixpkgs/nixos/modules/services/misc/synergy.nix
new file mode 100644
index 00000000000..bfab8c534d8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/synergy.nix
@@ -0,0 +1,132 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfgC = config.services.synergy.client;
+ cfgS = config.services.synergy.server;
+
+in
+
+{
+ ###### interface
+
+ options = {
+
+ services.synergy = {
+
+ # !!! All these option descriptions needs to be cleaned up.
+
+ client = {
+ enable = mkOption {
+ default = false;
+ description = "
+ Whether to enable the Synergy client (receive keyboard and mouse events from a Synergy server).
+ ";
+ };
+ screenName = mkOption {
+ default = "";
+ description = ''
+ Use the given name instead of the hostname to identify
+ ourselves to the server.
+ '';
+ };
+ serverAddress = mkOption {
+ description = ''
+ The server address is of the form: [hostname][:port]. The
+ hostname must be the address or hostname of the server. The
+ port overrides the default port, 24800.
+ '';
+ };
+ autoStart = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether the Synergy client should be started automatically.";
+ };
+ };
+
+ server = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable the Synergy server (send keyboard and mouse events).
+ '';
+ };
+ configFile = mkOption {
+ default = "/etc/synergy-server.conf";
+ description = "The Synergy server configuration file.";
+ };
+ screenName = mkOption {
+ default = "";
+ description = ''
+ Use the given name instead of the hostname to identify
+ this screen in the configuration.
+ '';
+ };
+ address = mkOption {
+ default = "";
+ description = "Address on which to listen for clients.";
+ };
+ autoStart = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether the Synergy server should be started automatically.";
+ };
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkMerge [
+ (mkIf cfgC.enable {
+ systemd.user.services.synergy-client = {
+ after = [ "network.target" "graphical-session.target" ];
+ description = "Synergy client";
+ wantedBy = optional cfgC.autoStart "graphical-session.target";
+ path = [ pkgs.synergy ];
+ serviceConfig.ExecStart = ''${pkgs.synergy}/bin/synergyc -f ${optionalString (cfgC.screenName != "") "-n ${cfgC.screenName}"} ${cfgC.serverAddress}'';
+ serviceConfig.Restart = "on-failure";
+ };
+ })
+ (mkIf cfgS.enable {
+ systemd.user.services.synergy-server = {
+ after = [ "network.target" "graphical-session.target" ];
+ description = "Synergy server";
+ wantedBy = optional cfgS.autoStart "graphical-session.target";
+ path = [ pkgs.synergy ];
+ serviceConfig.ExecStart = ''${pkgs.synergy}/bin/synergys -c ${cfgS.configFile} -f ${optionalString (cfgS.address != "") "-a ${cfgS.address}"} ${optionalString (cfgS.screenName != "") "-n ${cfgS.screenName}" }'';
+ serviceConfig.Restart = "on-failure";
+ };
+ })
+ ];
+
+}
+
+/* SYNERGY SERVER example configuration file
+section: screens
+ laptop:
+ dm:
+ win:
+end
+section: aliases
+ laptop:
+ 192.168.5.5
+ dm:
+ 192.168.5.78
+ win:
+ 192.168.5.54
+end
+section: links
+ laptop:
+ left = dm
+ dm:
+ right = laptop
+ left = win
+ win:
+ right = dm
+end
+*/
diff --git a/nixpkgs/nixos/modules/services/misc/sysprof.nix b/nixpkgs/nixos/modules/services/misc/sysprof.nix
new file mode 100644
index 00000000000..ab91a8b586a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sysprof.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, ... }:
+
+{
+ options = {
+ services.sysprof = {
+ enable = lib.mkEnableOption "sysprof profiling daemon";
+ };
+ };
+
+ config = lib.mkIf config.services.sysprof.enable {
+ environment.systemPackages = [ pkgs.sysprof ];
+
+ services.dbus.packages = [ pkgs.sysprof ];
+
+ systemd.packages = [ pkgs.sysprof ];
+ };
+
+ meta.maintainers = pkgs.sysprof.meta.maintainers;
+}
diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/default.nix b/nixpkgs/nixos/modules/services/misc/taskserver/default.nix
new file mode 100644
index 00000000000..8a57277fafe
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/taskserver/default.nix
@@ -0,0 +1,568 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.taskserver;
+
+ taskd = "${pkgs.taskserver}/bin/taskd";
+
+ mkManualPkiOption = desc: mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = desc + ''
+ <note><para>
+ Setting this option will prevent automatic CA creation and handling.
+ </para></note>
+ '';
+ };
+
+ manualPkiOptions = {
+ ca.cert = mkManualPkiOption ''
+ Fully qualified path to the CA certificate.
+ '';
+
+ server.cert = mkManualPkiOption ''
+ Fully qualified path to the server certificate.
+ '';
+
+ server.crl = mkManualPkiOption ''
+ Fully qualified path to the server certificate revocation list.
+ '';
+
+ server.key = mkManualPkiOption ''
+ Fully qualified path to the server key.
+ '';
+ };
+
+ mkAutoDesc = preamble: ''
+ ${preamble}
+
+ <note><para>
+ This option is for the automatically handled CA and will be ignored if any
+ of the <option>services.taskserver.pki.manual.*</option> options are set.
+ </para></note>
+ '';
+
+ mkExpireOption = desc: mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 365;
+ apply = val: if val == null then -1 else val;
+ description = mkAutoDesc ''
+ The expiration time of ${desc} in days or <literal>null</literal> for no
+ expiration time.
+ '';
+ };
+
+ autoPkiOptions = {
+ bits = mkOption {
+ type = types.int;
+ default = 4096;
+ example = 2048;
+ description = mkAutoDesc "The bit size for generated keys.";
+ };
+
+ expiration = {
+ ca = mkExpireOption "the CA certificate";
+ server = mkExpireOption "the server certificate";
+ client = mkExpireOption "client certificates";
+ crl = mkExpireOption "the certificate revocation list (CRL)";
+ };
+ };
+
+ needToCreateCA = let
+ notFound = path: let
+ dotted = concatStringsSep "." path;
+ in throw "Can't find option definitions for path `${dotted}'.";
+ findPkiDefinitions = path: attrs: let
+ mkSublist = key: val: let
+ newPath = path ++ singleton key;
+ in if isOption val
+ then attrByPath newPath (notFound newPath) cfg.pki.manual
+ else findPkiDefinitions newPath val;
+ in flatten (mapAttrsToList mkSublist attrs);
+ in all (x: x == null) (findPkiDefinitions [] manualPkiOptions);
+
+ orgOptions = { ... }: {
+ options.users = mkOption {
+ type = types.uniq (types.listOf types.str);
+ default = [];
+ example = [ "alice" "bob" ];
+ description = ''
+ A list of user names that belong to the organization.
+ '';
+ };
+
+ options.groups = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "workers" "slackers" ];
+ description = ''
+ A list of group names that belong to the organization.
+ '';
+ };
+ };
+
+ certtool = "${pkgs.gnutls.bin}/bin/certtool";
+
+ nixos-taskserver = pkgs.pythonPackages.buildPythonApplication {
+ name = "nixos-taskserver";
+
+ src = pkgs.runCommand "nixos-taskserver-src" { preferLocalBuild = true; } ''
+ mkdir -p "$out"
+ cat "${pkgs.substituteAll {
+ src = ./helper-tool.py;
+ inherit taskd certtool;
+ inherit (cfg) dataDir user group fqdn;
+ certBits = cfg.pki.auto.bits;
+ clientExpiration = cfg.pki.auto.expiration.client;
+ crlExpiration = cfg.pki.auto.expiration.crl;
+ isAutoConfig = if needToCreateCA then "True" else "False";
+ }}" > "$out/main.py"
+ cat > "$out/setup.py" <<EOF
+ from setuptools import setup
+ setup(name="nixos-taskserver",
+ py_modules=["main"],
+ install_requires=["Click"],
+ entry_points="[console_scripts]\\nnixos-taskserver=main:cli")
+ EOF
+ '';
+
+ propagatedBuildInputs = [ pkgs.pythonPackages.click ];
+ };
+
+in {
+ options = {
+ services.taskserver = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the Taskwarrior server.
+
+ More instructions about NixOS in conjuction with Taskserver can be
+ found in the NixOS manual at
+ <olink targetdoc="manual" targetptr="module-taskserver"/>.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "taskd";
+ description = "User for Taskserver.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "taskd";
+ description = "Group for Taskserver.";
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/taskserver";
+ description = "Data directory for Taskserver.";
+ };
+
+ ciphers = mkOption {
+ type = types.nullOr (types.separatedString ":");
+ default = null;
+ example = "NORMAL:-VERS-SSL3.0";
+ description = let
+ url = "https://gnutls.org/manual/html_node/Priority-Strings.html";
+ in ''
+ List of GnuTLS ciphers to use. See the GnuTLS documentation about
+ priority strings at <link xlink:href="${url}"/> for full details.
+ '';
+ };
+
+ organisations = mkOption {
+ type = types.attrsOf (types.submodule orgOptions);
+ default = {};
+ example.myShinyOrganisation.users = [ "alice" "bob" ];
+ example.myShinyOrganisation.groups = [ "staff" "outsiders" ];
+ example.yetAnotherOrganisation.users = [ "foo" "bar" ];
+ description = ''
+ An attribute set where the keys name the organisation and the values
+ are a set of lists of <option>users</option> and
+ <option>groups</option>.
+ '';
+ };
+
+ confirmation = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Determines whether certain commands are confirmed.
+ '';
+ };
+
+ debug = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Logs debugging information.
+ '';
+ };
+
+ extensions = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Fully qualified path of the Taskserver extension scripts.
+ Currently there are none.
+ '';
+ };
+
+ ipLog = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Logs the IP addresses of incoming requests.
+ '';
+ };
+
+ queueSize = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ Size of the connection backlog, see <citerefentry>
+ <refentrytitle>listen</refentrytitle>
+ <manvolnum>2</manvolnum>
+ </citerefentry>.
+ '';
+ };
+
+ requestLimit = mkOption {
+ type = types.int;
+ default = 1048576;
+ description = ''
+ Size limit of incoming requests, in bytes.
+ '';
+ };
+
+ allowedClientIDs = mkOption {
+ type = with types; either str (listOf str);
+ default = [];
+ example = [ "[Tt]ask [2-9]+" ];
+ description = ''
+ A list of regular expressions that are matched against the reported
+ client id (such as <literal>task 2.3.0</literal>).
+
+ The values <literal>all</literal> or <literal>none</literal> have
+ special meaning. Overidden by any entry in the option
+ <option>services.taskserver.disallowedClientIDs</option>.
+ '';
+ };
+
+ disallowedClientIDs = mkOption {
+ type = with types; either str (listOf str);
+ default = [];
+ example = [ "[Tt]ask [2-9]+" ];
+ description = ''
+ A list of regular expressions that are matched against the reported
+ client id (such as <literal>task 2.3.0</literal>).
+
+ The values <literal>all</literal> or <literal>none</literal> have
+ special meaning. Any entry here overrides those in
+ <option>services.taskserver.allowedClientIDs</option>.
+ '';
+ };
+
+ listenHost = mkOption {
+ type = types.str;
+ default = "localhost";
+ example = "::";
+ description = ''
+ The address (IPv4, IPv6 or DNS) to listen on.
+
+ If the value is something else than <literal>localhost</literal> the
+ port defined by <option>listenPort</option> is automatically added to
+ <option>networking.firewall.allowedTCPPorts</option>.
+ '';
+ };
+
+ listenPort = mkOption {
+ type = types.int;
+ default = 53589;
+ description = ''
+ Port number of the Taskserver.
+ '';
+ };
+
+ fqdn = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = ''
+ The fully qualified domain name of this server, which is also used
+ as the common name in the certificates.
+ '';
+ };
+
+ trust = mkOption {
+ type = types.enum [ "allow all" "strict" ];
+ default = "strict";
+ description = ''
+ Determines how client certificates are validated.
+
+ The value <literal>allow all</literal> performs no client
+ certificate validation. This is not recommended. The value
+ <literal>strict</literal> causes the client certificate to be
+ validated against a CA.
+ '';
+ };
+
+ pki.manual = manualPkiOptions;
+ pki.auto = autoPkiOptions;
+
+ config = mkOption {
+ type = types.attrs;
+ example.client.cert = "/tmp/debugging.cert";
+ description = ''
+ Configuration options to pass to Taskserver.
+
+ The options here are the same as described in <citerefentry>
+ <refentrytitle>taskdrc</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>, but with one difference:
+
+ The <literal>server</literal> option is
+ <literal>server.listen</literal> here, because the
+ <literal>server</literal> option would collide with other options
+ like <literal>server.cert</literal> and we would run in a type error
+ (attribute set versus string).
+
+ Nix types like integers or booleans are automatically converted to
+ the right values Taskserver would expect.
+ '';
+ apply = let
+ mkKey = path: if path == ["server" "listen"] then "server"
+ else concatStringsSep "." path;
+ recurse = path: attrs: let
+ mapper = name: val: let
+ newPath = path ++ [ name ];
+ scalar = if val == true then "true"
+ else if val == false then "false"
+ else toString val;
+ in if isAttrs val then recurse newPath val
+ else [ "${mkKey newPath}=${scalar}" ];
+ in concatLists (mapAttrsToList mapper attrs);
+ in recurse [];
+ };
+ };
+ };
+
+ imports = [
+ (mkRemovedOptionModule ["services" "taskserver" "extraConfig"] ''
+ This option was removed in favor of `services.taskserver.config` with
+ different semantics (it's now a list of attributes instead of lines).
+
+ Please look up the documentation of `services.taskserver.config' to get
+ more information about the new way to pass additional configuration
+ options.
+ '')
+ ];
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ environment.systemPackages = [ nixos-taskserver ];
+
+ users.users = optional (cfg.user == "taskd") {
+ name = "taskd";
+ uid = config.ids.uids.taskd;
+ description = "Taskserver user";
+ group = cfg.group;
+ };
+
+ users.groups = optional (cfg.group == "taskd") {
+ name = "taskd";
+ gid = config.ids.gids.taskd;
+ };
+
+ services.taskserver.config = {
+ # systemd related
+ daemon = false;
+ log = "-";
+
+ # logging
+ debug = cfg.debug;
+ ip.log = cfg.ipLog;
+
+ # general
+ ciphers = cfg.ciphers;
+ confirmation = cfg.confirmation;
+ extensions = cfg.extensions;
+ queue.size = cfg.queueSize;
+ request.limit = cfg.requestLimit;
+
+ # client
+ client.allow = cfg.allowedClientIDs;
+ client.deny = cfg.disallowedClientIDs;
+
+ # server
+ trust = cfg.trust;
+ server = {
+ listen = "${cfg.listenHost}:${toString cfg.listenPort}";
+ } // (if needToCreateCA then {
+ cert = "${cfg.dataDir}/keys/server.cert";
+ key = "${cfg.dataDir}/keys/server.key";
+ crl = "${cfg.dataDir}/keys/server.crl";
+ } else {
+ cert = "${cfg.pki.manual.server.cert}";
+ key = "${cfg.pki.manual.server.key}";
+ ${mapNullable (_: "crl") cfg.pki.manual.server.crl} = "${cfg.pki.manual.server.crl}";
+ });
+
+ ca.cert = if needToCreateCA then "${cfg.dataDir}/keys/ca.cert"
+ else "${cfg.pki.manual.ca.cert}";
+ };
+
+ systemd.services.taskserver-init = {
+ wantedBy = [ "taskserver.service" ];
+ before = [ "taskserver.service" ];
+ description = "Initialize Taskserver Data Directory";
+
+ preStart = ''
+ mkdir -m 0770 -p "${cfg.dataDir}"
+ chown "${cfg.user}:${cfg.group}" "${cfg.dataDir}"
+ '';
+
+ script = ''
+ ${taskd} init
+ touch "${cfg.dataDir}/.is_initialized"
+ '';
+
+ environment.TASKDDATA = cfg.dataDir;
+
+ unitConfig.ConditionPathExists = "!${cfg.dataDir}/.is_initialized";
+
+ serviceConfig.Type = "oneshot";
+ serviceConfig.User = cfg.user;
+ serviceConfig.Group = cfg.group;
+ serviceConfig.PermissionsStartOnly = true;
+ serviceConfig.PrivateNetwork = true;
+ serviceConfig.PrivateDevices = true;
+ serviceConfig.PrivateTmp = true;
+ };
+
+ systemd.services.taskserver = {
+ description = "Taskwarrior Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ environment.TASKDDATA = cfg.dataDir;
+
+ preStart = let
+ jsonOrgs = builtins.toJSON cfg.organisations;
+ jsonFile = pkgs.writeText "orgs.json" jsonOrgs;
+ helperTool = "${nixos-taskserver}/bin/nixos-taskserver";
+ in "${helperTool} process-json '${jsonFile}'";
+
+ serviceConfig = {
+ ExecStart = let
+ mkCfgFlag = flag: escapeShellArg "--${flag}";
+ cfgFlags = concatMapStringsSep " " mkCfgFlag cfg.config;
+ in "@${taskd} taskd server ${cfgFlags}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID";
+ Restart = "on-failure";
+ PermissionsStartOnly = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ User = cfg.user;
+ Group = cfg.group;
+ };
+ };
+ })
+ (mkIf (cfg.enable && needToCreateCA) {
+ systemd.services.taskserver-ca = {
+ wantedBy = [ "taskserver.service" ];
+ after = [ "taskserver-init.service" ];
+ before = [ "taskserver.service" ];
+ description = "Initialize CA for TaskServer";
+ serviceConfig.Type = "oneshot";
+ serviceConfig.UMask = "0077";
+ serviceConfig.PrivateNetwork = true;
+ serviceConfig.PrivateTmp = true;
+
+ script = ''
+ silent_certtool() {
+ if ! output="$("${certtool}" "$@" 2>&1)"; then
+ echo "GNUTLS certtool invocation failed with output:" >&2
+ echo "$output" >&2
+ fi
+ }
+
+ mkdir -m 0700 -p "${cfg.dataDir}/keys"
+ chown root:root "${cfg.dataDir}/keys"
+
+ if [ ! -e "${cfg.dataDir}/keys/ca.key" ]; then
+ silent_certtool -p \
+ --bits ${toString cfg.pki.auto.bits} \
+ --outfile "${cfg.dataDir}/keys/ca.key"
+ silent_certtool -s \
+ --template "${pkgs.writeText "taskserver-ca.template" ''
+ cn = ${cfg.fqdn}
+ expiration_days = ${toString cfg.pki.auto.expiration.ca}
+ cert_signing_key
+ ca
+ ''}" \
+ --load-privkey "${cfg.dataDir}/keys/ca.key" \
+ --outfile "${cfg.dataDir}/keys/ca.cert"
+
+ chgrp "${cfg.group}" "${cfg.dataDir}/keys/ca.cert"
+ chmod g+r "${cfg.dataDir}/keys/ca.cert"
+ fi
+
+ if [ ! -e "${cfg.dataDir}/keys/server.key" ]; then
+ silent_certtool -p \
+ --bits ${toString cfg.pki.auto.bits} \
+ --outfile "${cfg.dataDir}/keys/server.key"
+
+ silent_certtool -c \
+ --template "${pkgs.writeText "taskserver-cert.template" ''
+ cn = ${cfg.fqdn}
+ expiration_days = ${toString cfg.pki.auto.expiration.server}
+ tls_www_server
+ encryption_key
+ signing_key
+ ''}" \
+ --load-ca-privkey "${cfg.dataDir}/keys/ca.key" \
+ --load-ca-certificate "${cfg.dataDir}/keys/ca.cert" \
+ --load-privkey "${cfg.dataDir}/keys/server.key" \
+ --outfile "${cfg.dataDir}/keys/server.cert"
+
+ chgrp "${cfg.group}" \
+ "${cfg.dataDir}/keys/server.key" \
+ "${cfg.dataDir}/keys/server.cert"
+
+ chmod g+r \
+ "${cfg.dataDir}/keys/server.key" \
+ "${cfg.dataDir}/keys/server.cert"
+ fi
+
+ if [ ! -e "${cfg.dataDir}/keys/server.crl" ]; then
+ silent_certtool --generate-crl \
+ --template "${pkgs.writeText "taskserver-crl.template" ''
+ expiration_days = ${toString cfg.pki.auto.expiration.crl}
+ ''}" \
+ --load-ca-privkey "${cfg.dataDir}/keys/ca.key" \
+ --load-ca-certificate "${cfg.dataDir}/keys/ca.cert" \
+ --outfile "${cfg.dataDir}/keys/server.crl"
+
+ chgrp "${cfg.group}" "${cfg.dataDir}/keys/server.crl"
+ chmod g+r "${cfg.dataDir}/keys/server.crl"
+ fi
+
+ chmod go+x "${cfg.dataDir}/keys"
+ '';
+ };
+ })
+ (mkIf (cfg.enable && cfg.listenHost != "localhost") {
+ networking.firewall.allowedTCPPorts = [ cfg.listenPort ];
+ })
+ ];
+
+ meta.doc = ./doc.xml;
+}
diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml b/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml
new file mode 100644
index 00000000000..5656bb85b37
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml
@@ -0,0 +1,135 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="5.0"
+ xml:id="module-taskserver">
+ <title>Taskserver</title>
+ <para>
+ Taskserver is the server component of
+ <link xlink:href="https://taskwarrior.org/">Taskwarrior</link>, a free and
+ open source todo list application.
+ </para>
+ <para>
+ <emphasis>Upstream documentation:</emphasis>
+ <link xlink:href="https://taskwarrior.org/docs/#taskd"/>
+ </para>
+ <section xml:id="module-services-taskserver-configuration">
+ <title>Configuration</title>
+
+ <para>
+ Taskserver does all of its authentication via TLS using client certificates,
+ so you either need to roll your own CA or purchase a certificate from a
+ known CA, which allows creation of client certificates. These certificates
+ are usually advertised as <quote>server certificates</quote>.
+ </para>
+
+ <para>
+ So in order to make it easier to handle your own CA, there is a helper tool
+ called <command>nixos-taskserver</command> which manages the custom CA along
+ with Taskserver organisations, users and groups.
+ </para>
+
+ <para>
+ While the client certificates in Taskserver only authenticate whether a user
+ is allowed to connect, every user has its own UUID which identifies it as an
+ entity.
+ </para>
+
+ <para>
+ With <command>nixos-taskserver</command> the client certificate is created
+ along with the UUID of the user, so it handles all of the credentials needed
+ in order to setup the Taskwarrior client to work with a Taskserver.
+ </para>
+ </section>
+ <section xml:id="module-services-taskserver-nixos-taskserver-tool">
+ <title>The nixos-taskserver tool</title>
+
+ <para>
+ Because Taskserver by default only provides scripts to setup users
+ imperatively, the <command>nixos-taskserver</command> tool is used for
+ addition and deletion of organisations along with users and groups defined
+ by <xref linkend="opt-services.taskserver.organisations"/> and as well for
+ imperative set up.
+ </para>
+
+ <para>
+ The tool is designed to not interfere if the command is used to manually set
+ up some organisations, users or groups.
+ </para>
+
+ <para>
+ For example if you add a new organisation using <command>nixos-taskserver
+ org add foo</command>, the organisation is not modified and deleted no
+ matter what you define in
+ <option>services.taskserver.organisations</option>, even if you're adding
+ the same organisation in that option.
+ </para>
+
+ <para>
+ The tool is modelled to imitate the official <command>taskd</command>
+ command, documentation for each subcommand can be shown by using the
+ <option>--help</option> switch.
+ </para>
+ </section>
+ <section xml:id="module-services-taskserver-declarative-ca-management">
+ <title>Declarative/automatic CA management</title>
+
+ <para>
+ Everything is done according to what you specify in the module options,
+ however in order to set up a Taskwarrior client for synchronisation with a
+ Taskserver instance, you have to transfer the keys and certificates to the
+ client machine.
+ </para>
+
+ <para>
+ This is done using <command>nixos-taskserver user export $orgname
+ $username</command> which is printing a shell script fragment to stdout
+ which can either be used verbatim or adjusted to import the user on the
+ client machine.
+ </para>
+
+ <para>
+ For example, let's say you have the following configuration:
+<screen>
+{
+ <xref linkend="opt-services.taskserver.enable"/> = true;
+ <xref linkend="opt-services.taskserver.fqdn"/> = "server";
+ <xref linkend="opt-services.taskserver.listenHost"/> = "::";
+ <link linkend="opt-services.taskserver.organisations._name_.users">services.taskserver.organisations.my-company.users</link> = [ "alice" ];
+}
+</screen>
+ This creates an organisation called <literal>my-company</literal> with the
+ user <literal>alice</literal>.
+ </para>
+
+ <para>
+ Now in order to import the <literal>alice</literal> user to another machine
+ <literal>alicebox</literal>, all we need to do is something like this:
+<screen>
+<prompt>$ </prompt>ssh server nixos-taskserver user export my-company alice | sh
+</screen>
+ Of course, if no SSH daemon is available on the server you can also copy
+ &amp; paste it directly into a shell.
+ </para>
+
+ <para>
+ After this step the user should be set up and you can start synchronising
+ your tasks for the first time with <command>task sync init</command> on
+ <literal>alicebox</literal>.
+ </para>
+
+ <para>
+ Subsequent synchronisation requests merely require the command <command>task
+ sync</command> after that stage.
+ </para>
+ </section>
+ <section xml:id="module-services-taskserver-manual-ca-management">
+ <title>Manual CA management</title>
+
+ <para>
+ If you set any options within
+ <link linkend="opt-services.taskserver.pki.manual.ca.cert">service.taskserver.pki.manual</link>.*,
+ <command>nixos-taskserver</command> won't issue certificates, but you can
+ still use it for adding or removing user accounts.
+ </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py b/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py
new file mode 100644
index 00000000000..22a3d8d5311
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py
@@ -0,0 +1,688 @@
+import grp
+import json
+import pwd
+import os
+import re
+import string
+import subprocess
+import sys
+
+from contextlib import contextmanager
+from shutil import rmtree
+from tempfile import NamedTemporaryFile
+
+import click
+
+IS_AUTO_CONFIG = @isAutoConfig@ # NOQA
+CERTTOOL_COMMAND = "@certtool@"
+CERT_BITS = "@certBits@"
+CLIENT_EXPIRATION = "@clientExpiration@"
+CRL_EXPIRATION = "@crlExpiration@"
+
+TASKD_COMMAND = "@taskd@"
+TASKD_DATA_DIR = "@dataDir@"
+TASKD_USER = "@user@"
+TASKD_GROUP = "@group@"
+FQDN = "@fqdn@"
+
+CA_KEY = os.path.join(TASKD_DATA_DIR, "keys", "ca.key")
+CA_CERT = os.path.join(TASKD_DATA_DIR, "keys", "ca.cert")
+CRL_FILE = os.path.join(TASKD_DATA_DIR, "keys", "server.crl")
+
+RE_CONFIGUSER = re.compile(r'^\s*user\s*=(.*)$')
+RE_USERKEY = re.compile(r'New user key: (.+)$', re.MULTILINE)
+
+
+def lazyprop(fun):
+ """
+ Decorator which only evaluates the specified function when accessed.
+ """
+ name = '_lazy_' + fun.__name__
+
+ @property
+ def _lazy(self):
+ val = getattr(self, name, None)
+ if val is None:
+ val = fun(self)
+ setattr(self, name, val)
+ return val
+
+ return _lazy
+
+
+class TaskdError(OSError):
+ pass
+
+
+def run_as_taskd_user():
+ uid = pwd.getpwnam(TASKD_USER).pw_uid
+ gid = grp.getgrnam(TASKD_GROUP).gr_gid
+ os.setgid(gid)
+ os.setuid(uid)
+
+
+def taskd_cmd(cmd, *args, **kwargs):
+ """
+ Invoke taskd with the specified command with the privileges of the 'taskd'
+ user and 'taskd' group.
+
+ If 'capture_stdout' is passed as a keyword argument with the value True,
+ the return value are the contents the command printed to stdout.
+ """
+ capture_stdout = kwargs.pop("capture_stdout", False)
+ fun = subprocess.check_output if capture_stdout else subprocess.check_call
+ return fun(
+ [TASKD_COMMAND, cmd, "--data", TASKD_DATA_DIR] + list(args),
+ preexec_fn=run_as_taskd_user,
+ **kwargs
+ )
+
+
+def certtool_cmd(*args, **kwargs):
+ """
+ Invoke certtool from GNUTLS and return the output of the command.
+
+ The provided arguments are added to the certtool command and keyword
+ arguments are added to subprocess.check_output().
+
+ Note that this will suppress all output of certtool and it will only be
+ printed whenever there is an unsuccessful return code.
+ """
+ return subprocess.check_output(
+ [CERTTOOL_COMMAND] + list(args),
+ preexec_fn=lambda: os.umask(0077),
+ stderr=subprocess.STDOUT,
+ **kwargs
+ )
+
+
+def label(msg):
+ if sys.stdout.isatty() or sys.stderr.isatty():
+ sys.stderr.write(msg + "\n")
+
+
+def mkpath(*args):
+ return os.path.join(TASKD_DATA_DIR, "orgs", *args)
+
+
+def mark_imperative(*path):
+ """
+ Mark the specified path as being imperatively managed by creating an empty
+ file called ".imperative", so that it doesn't interfere with the
+ declarative configuration.
+ """
+ open(os.path.join(mkpath(*path), ".imperative"), 'a').close()
+
+
+def is_imperative(*path):
+ """
+ Check whether the given path is marked as imperative, see mark_imperative()
+ for more information.
+ """
+ full_path = []
+ for component in path:
+ full_path.append(component)
+ if os.path.exists(os.path.join(mkpath(*full_path), ".imperative")):
+ return True
+ return False
+
+
+def fetch_username(org, key):
+ for line in open(mkpath(org, "users", key, "config"), "r"):
+ match = RE_CONFIGUSER.match(line)
+ if match is None:
+ continue
+ return match.group(1).strip()
+ return None
+
+
+@contextmanager
+def create_template(contents):
+ """
+ Generate a temporary file with the specified contents as a list of strings
+ and yield its path as the context.
+ """
+ template = NamedTemporaryFile(mode="w", prefix="certtool-template")
+ template.writelines(map(lambda l: l + "\n", contents))
+ template.flush()
+ yield template.name
+ template.close()
+
+
+def generate_key(org, user):
+ if not IS_AUTO_CONFIG:
+ msg = "Automatic PKI handling is disabled, you need to " \
+ "manually issue a client certificate for user {}.\n"
+ sys.stderr.write(msg.format(user))
+ return
+
+ basedir = os.path.join(TASKD_DATA_DIR, "keys", org, user)
+ if os.path.exists(basedir):
+ raise OSError("Keyfile directory for {} already exists.".format(user))
+
+ privkey = os.path.join(basedir, "private.key")
+ pubcert = os.path.join(basedir, "public.cert")
+
+ try:
+ os.makedirs(basedir, mode=0700)
+
+ certtool_cmd("-p", "--bits", CERT_BITS, "--outfile", privkey)
+
+ template_data = [
+ "organization = {0}".format(org),
+ "cn = {}".format(FQDN),
+ "expiration_days = {}".format(CLIENT_EXPIRATION),
+ "tls_www_client",
+ "encryption_key",
+ "signing_key"
+ ]
+
+ with create_template(template_data) as template:
+ certtool_cmd(
+ "-c",
+ "--load-privkey", privkey,
+ "--load-ca-privkey", CA_KEY,
+ "--load-ca-certificate", CA_CERT,
+ "--template", template,
+ "--outfile", pubcert
+ )
+ except:
+ rmtree(basedir)
+ raise
+
+
+def revoke_key(org, user):
+ basedir = os.path.join(TASKD_DATA_DIR, "keys", org, user)
+ if not os.path.exists(basedir):
+ raise OSError("Keyfile directory for {} doesn't exist.".format(user))
+
+ pubcert = os.path.join(basedir, "public.cert")
+
+ expiration = "expiration_days = {}".format(CRL_EXPIRATION)
+
+ with create_template([expiration]) as template:
+ oldcrl = NamedTemporaryFile(mode="wb", prefix="old-crl")
+ oldcrl.write(open(CRL_FILE, "rb").read())
+ oldcrl.flush()
+ certtool_cmd(
+ "--generate-crl",
+ "--load-crl", oldcrl.name,
+ "--load-ca-privkey", CA_KEY,
+ "--load-ca-certificate", CA_CERT,
+ "--load-certificate", pubcert,
+ "--template", template,
+ "--outfile", CRL_FILE
+ )
+ oldcrl.close()
+ rmtree(basedir)
+
+
+def is_key_line(line, match):
+ return line.startswith("---") and line.lstrip("- ").startswith(match)
+
+
+def getkey(*args):
+ path = os.path.join(TASKD_DATA_DIR, "keys", *args)
+ buf = []
+ for line in open(path, "r"):
+ if len(buf) == 0:
+ if is_key_line(line, "BEGIN"):
+ buf.append(line)
+ continue
+
+ buf.append(line)
+
+ if is_key_line(line, "END"):
+ return ''.join(buf)
+ raise IOError("Unable to get key from {}.".format(path))
+
+
+def mktaskkey(cfg, path, keydata):
+ heredoc = 'cat > "{}" <<EOF\n{}EOF'.format(path, keydata)
+ cmd = 'task config taskd.{} -- "{}"'.format(cfg, path)
+ return heredoc + "\n" + cmd
+
+
+class User(object):
+ def __init__(self, org, name, key):
+ self.__org = org
+ self.name = name
+ self.key = key
+
+ def export(self):
+ credentials = '/'.join([self.__org, self.name, self.key])
+ allow_unquoted = string.ascii_letters + string.digits + "/-_."
+ if not all((c in allow_unquoted) for c in credentials):
+ credentials = "'" + credentials.replace("'", r"'\''") + "'"
+
+ script = []
+
+ if IS_AUTO_CONFIG:
+ pubcert = getkey(self.__org, self.name, "public.cert")
+ privkey = getkey(self.__org, self.name, "private.key")
+ cacert = getkey("ca.cert")
+
+ keydir = "${TASKDATA:-$HOME/.task}/keys"
+
+ script += [
+ "umask 0077",
+ 'mkdir -p "{}"'.format(keydir),
+ mktaskkey("certificate", os.path.join(keydir, "public.cert"),
+ pubcert),
+ mktaskkey("key", os.path.join(keydir, "private.key"), privkey),
+ mktaskkey("ca", os.path.join(keydir, "ca.cert"), cacert)
+ ]
+
+ script.append(
+ "task config taskd.credentials -- {}".format(credentials)
+ )
+
+ return "\n".join(script) + "\n"
+
+
+class Group(object):
+ def __init__(self, org, name):
+ self.__org = org
+ self.name = name
+
+
+class Organisation(object):
+ def __init__(self, name, ignore_imperative):
+ self.name = name
+ self.ignore_imperative = ignore_imperative
+
+ def add_user(self, name):
+ """
+ Create a new user along with a certificate and key.
+
+ Returns a 'User' object or None if the user already exists.
+ """
+ if self.ignore_imperative and is_imperative(self.name):
+ return None
+ if name not in self.users.keys():
+ output = taskd_cmd("add", "user", self.name, name,
+ capture_stdout=True)
+ key = RE_USERKEY.search(output)
+ if key is None:
+ msg = "Unable to find key while creating user {}."
+ raise TaskdError(msg.format(name))
+
+ generate_key(self.name, name)
+ newuser = User(self.name, name, key.group(1))
+ self._lazy_users[name] = newuser
+ return newuser
+ return None
+
+ def del_user(self, name):
+ """
+ Delete a user and revoke its keys.
+ """
+ if name in self.users.keys():
+ user = self.get_user(name)
+ if self.ignore_imperative and \
+ is_imperative(self.name, "users", user.key):
+ return
+
+ # Work around https://bug.tasktools.org/browse/TD-40:
+ rmtree(mkpath(self.name, "users", user.key))
+
+ revoke_key(self.name, name)
+ del self._lazy_users[name]
+
+ def add_group(self, name):
+ """
+ Create a new group.
+
+ Returns a 'Group' object or None if the group already exists.
+ """
+ if self.ignore_imperative and is_imperative(self.name):
+ return None
+ if name not in self.groups.keys():
+ taskd_cmd("add", "group", self.name, name)
+ newgroup = Group(self.name, name)
+ self._lazy_groups[name] = newgroup
+ return newgroup
+ return None
+
+ def del_group(self, name):
+ """
+ Delete a group.
+ """
+ if name in self.users.keys():
+ if self.ignore_imperative and \
+ is_imperative(self.name, "groups", name):
+ return
+ taskd_cmd("remove", "group", self.name, name)
+ del self._lazy_groups[name]
+
+ def get_user(self, name):
+ return self.users.get(name)
+
+ @lazyprop
+ def users(self):
+ result = {}
+ for key in os.listdir(mkpath(self.name, "users")):
+ user = fetch_username(self.name, key)
+ if user is not None:
+ result[user] = User(self.name, user, key)
+ return result
+
+ def get_group(self, name):
+ return self.groups.get(name)
+
+ @lazyprop
+ def groups(self):
+ result = {}
+ for group in os.listdir(mkpath(self.name, "groups")):
+ result[group] = Group(self.name, group)
+ return result
+
+
+class Manager(object):
+ def __init__(self, ignore_imperative=False):
+ """
+ Instantiates an organisations manager.
+
+ If ignore_imperative is True, all actions that modify data are checked
+ whether they're created imperatively and if so, they will result in no
+ operation.
+ """
+ self.ignore_imperative = ignore_imperative
+
+ def add_org(self, name):
+ """
+ Create a new organisation.
+
+ Returns an 'Organisation' object or None if the organisation already
+ exists.
+ """
+ if name not in self.orgs.keys():
+ taskd_cmd("add", "org", name)
+ neworg = Organisation(name, self.ignore_imperative)
+ self._lazy_orgs[name] = neworg
+ return neworg
+ return None
+
+ def del_org(self, name):
+ """
+ Delete and revoke keys of an organisation with all its users and
+ groups.
+ """
+ org = self.get_org(name)
+ if org is not None:
+ if self.ignore_imperative and is_imperative(name):
+ return
+ for user in org.users.keys():
+ org.del_user(user)
+ for group in org.groups.keys():
+ org.del_group(group)
+ taskd_cmd("remove", "org", name)
+ del self._lazy_orgs[name]
+
+ def get_org(self, name):
+ return self.orgs.get(name)
+
+ @lazyprop
+ def orgs(self):
+ result = {}
+ for org in os.listdir(mkpath()):
+ result[org] = Organisation(org, self.ignore_imperative)
+ return result
+
+
+class OrganisationType(click.ParamType):
+ name = 'organisation'
+
+ def convert(self, value, param, ctx):
+ org = Manager().get_org(value)
+ if org is None:
+ self.fail("Organisation {} does not exist.".format(value))
+ return org
+
+ORGANISATION = OrganisationType()
+
+
+@click.group()
+@click.pass_context
+def cli(ctx):
+ """
+ Manage Taskserver users and certificates
+ """
+ if not IS_AUTO_CONFIG:
+ return
+ for path in (CA_KEY, CA_CERT, CRL_FILE):
+ if not os.path.exists(path):
+ msg = "CA setup not done or incomplete, missing file {}."
+ ctx.fail(msg.format(path))
+
+
+@cli.group("org")
+def org_cli():
+ """
+ Manage organisations
+ """
+ pass
+
+
+@cli.group("user")
+def user_cli():
+ """
+ Manage users
+ """
+ pass
+
+
+@cli.group("group")
+def group_cli():
+ """
+ Manage groups
+ """
+ pass
+
+
+@user_cli.command("list")
+@click.argument("organisation", type=ORGANISATION)
+def list_users(organisation):
+ """
+ List all users belonging to the specified organisation.
+ """
+ label("The following users exists for {}:".format(organisation.name))
+ for user in organisation.users.values():
+ sys.stdout.write(user.name + "\n")
+
+
+@group_cli.command("list")
+@click.argument("organisation", type=ORGANISATION)
+def list_groups(organisation):
+ """
+ List all users belonging to the specified organisation.
+ """
+ label("The following users exists for {}:".format(organisation.name))
+ for group in organisation.groups.values():
+ sys.stdout.write(group.name + "\n")
+
+
+@org_cli.command("list")
+def list_orgs():
+ """
+ List available organisations
+ """
+ label("The following organisations exist:")
+ for org in Manager().orgs:
+ sys.stdout.write(org.name + "\n")
+
+
+@user_cli.command("getkey")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("user")
+def get_uuid(organisation, user):
+ """
+ Get the UUID of the specified user belonging to the specified organisation.
+ """
+ userobj = organisation.get_user(user)
+ if userobj is None:
+ msg = "User {} doesn't exist in organisation {}."
+ sys.exit(msg.format(userobj.name, organisation.name))
+
+ label("User {} has the following UUID:".format(userobj.name))
+ sys.stdout.write(user.key + "\n")
+
+
+@user_cli.command("export")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("user")
+def export_user(organisation, user):
+ """
+ Export user of the specified organisation as a series of shell commands
+ that can be used on the client side to easily import the certificates.
+
+ Note that the private key will be exported as well, so use this with care!
+ """
+ userobj = organisation.get_user(user)
+ if userobj is None:
+ msg = "User {} doesn't exist in organisation {}."
+ sys.exit(msg.format(user, organisation.name))
+
+ sys.stdout.write(userobj.export())
+
+
+@org_cli.command("add")
+@click.argument("name")
+def add_org(name):
+ """
+ Create an organisation with the specified name.
+ """
+ if os.path.exists(mkpath(name)):
+ msg = "Organisation with name {} already exists."
+ sys.exit(msg.format(name))
+
+ taskd_cmd("add", "org", name)
+ mark_imperative(name)
+
+
+@org_cli.command("remove")
+@click.argument("name")
+def del_org(name):
+ """
+ Delete the organisation with the specified name.
+
+ All of the users and groups will be deleted as well and client certificates
+ will be revoked.
+ """
+ Manager().del_org(name)
+ msg = ("Organisation {} deleted. Be sure to restart the Taskserver"
+ " using 'systemctl restart taskserver.service' in order for"
+ " the certificate revocation to apply.")
+ click.echo(msg.format(name), err=True)
+
+
+@user_cli.command("add")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("user")
+def add_user(organisation, user):
+ """
+ Create a user for the given organisation along with a client certificate
+ and print the key of the new user.
+
+ The client certificate along with it's public key can be shown via the
+ 'user export' subcommand.
+ """
+ userobj = organisation.add_user(user)
+ if userobj is None:
+ msg = "User {} already exists in organisation {}."
+ sys.exit(msg.format(user, organisation))
+ else:
+ mark_imperative(organisation.name, "users", userobj.key)
+
+
+@user_cli.command("remove")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("user")
+def del_user(organisation, user):
+ """
+ Delete a user from the given organisation.
+
+ This will also revoke the client certificate of the given user.
+ """
+ organisation.del_user(user)
+ msg = ("User {} deleted. Be sure to restart the Taskserver using"
+ " 'systemctl restart taskserver.service' in order for the"
+ " certificate revocation to apply.")
+ click.echo(msg.format(user), err=True)
+
+
+@group_cli.command("add")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("group")
+def add_group(organisation, group):
+ """
+ Create a group for the given organisation.
+ """
+ groupobj = organisation.add_group(group)
+ if groupobj is None:
+ msg = "Group {} already exists in organisation {}."
+ sys.exit(msg.format(group, organisation))
+ else:
+ mark_imperative(organisation.name, "groups", groupobj.name)
+
+
+@group_cli.command("remove")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("group")
+def del_group(organisation, group):
+ """
+ Delete a group from the given organisation.
+ """
+ organisation.del_group(group)
+ click("Group {} deleted.".format(group), err=True)
+
+
+def add_or_delete(old, new, add_fun, del_fun):
+ """
+ Given an 'old' and 'new' list, figure out the intersections and invoke
+ 'add_fun' against every element that is not in the 'old' list and 'del_fun'
+ against every element that is not in the 'new' list.
+
+ Returns a tuple where the first element is the list of elements that were
+ added and the second element consisting of elements that were deleted.
+ """
+ old_set = set(old)
+ new_set = set(new)
+ to_delete = old_set - new_set
+ to_add = new_set - old_set
+ for elem in to_delete:
+ del_fun(elem)
+ for elem in to_add:
+ add_fun(elem)
+ return to_add, to_delete
+
+
+@cli.command("process-json")
+@click.argument('json-file', type=click.File('rb'))
+def process_json(json_file):
+ """
+ Create and delete users, groups and organisations based on a JSON file.
+
+ The structure of this file is exactly the same as the
+ 'services.taskserver.organisations' option of the NixOS module and is used
+ for declaratively adding and deleting users.
+
+ Hence this subcommand is not recommended outside of the scope of the NixOS
+ module.
+ """
+ data = json.load(json_file)
+
+ mgr = Manager(ignore_imperative=True)
+ add_or_delete(mgr.orgs.keys(), data.keys(), mgr.add_org, mgr.del_org)
+
+ for org in mgr.orgs.values():
+ if is_imperative(org.name):
+ continue
+ add_or_delete(org.users.keys(), data[org.name]['users'],
+ org.add_user, org.del_user)
+ add_or_delete(org.groups.keys(), data[org.name]['groups'],
+ org.add_group, org.del_group)
+
+
+if __name__ == '__main__':
+ cli()
diff --git a/nixpkgs/nixos/modules/services/misc/tautulli.nix b/nixpkgs/nixos/modules/services/misc/tautulli.nix
new file mode 100644
index 00000000000..50e45036647
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/tautulli.nix
@@ -0,0 +1,77 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.tautulli;
+in
+{
+ options = {
+ services.tautulli = {
+ enable = mkEnableOption "Tautulli Plex Monitor";
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/plexpy";
+ description = "The directory where Tautulli stores its data files.";
+ };
+
+ configFile = mkOption {
+ type = types.str;
+ default = "/var/lib/plexpy/config.ini";
+ description = "The location of Tautulli's config file.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8181;
+ description = "TCP port where Tautulli listens.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "plexpy";
+ description = "User account under which Tautulli runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "nogroup";
+ description = "Group under which Tautulli runs.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.tautulli;
+ defaultText = "pkgs.tautulli";
+ description = ''
+ The Tautulli package to use.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.tautulli = {
+ description = "Tautulli Plex Monitor";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ GuessMainPID = "false";
+ ExecStart = "${cfg.package}/bin/tautulli --datadir ${cfg.dataDir} --config ${cfg.configFile} --port ${toString cfg.port} --pidfile ${cfg.dataDir}/tautulli.pid --nolaunch";
+ Restart = "on-failure";
+ };
+ };
+
+ users.users = mkIf (cfg.user == "plexpy") {
+ plexpy = { group = cfg.group; uid = config.ids.uids.plexpy; };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix b/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix
new file mode 100644
index 00000000000..2adc08f6cfe
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.tiddlywiki;
+ listenParams = concatStrings (mapAttrsToList (n: v: " '${n}=${toString v}' ") cfg.listenOptions);
+ exe = "${pkgs.nodePackages.tiddlywiki}/lib/node_modules/.bin/tiddlywiki";
+ name = "tiddlywiki";
+ dataDir = "/var/lib/" + name;
+
+in {
+
+ options.services.tiddlywiki = {
+
+ enable = mkEnableOption "TiddlyWiki nodejs server";
+
+ listenOptions = mkOption {
+ type = types.attrs;
+ default = {};
+ example = {
+ credentials = "../credentials.csv";
+ readers="(authenticated)";
+ port = 3456;
+ };
+ description = ''
+ Parameters passed to <literal>--listen</literal> command.
+ Refer to <link xlink:href="https://tiddlywiki.com/#WebServer"/>
+ for details on supported values.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd = {
+ services.tiddlywiki = {
+ description = "TiddlyWiki nodejs server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "simple";
+ Restart = "on-failure";
+ DynamicUser = true;
+ StateDirectory = name;
+ ExecStartPre = "-${exe} ${dataDir} --init server";
+ ExecStart = "${exe} ${dataDir} --listen ${listenParams}";
+ };
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/tzupdate.nix b/nixpkgs/nixos/modules/services/misc/tzupdate.nix
new file mode 100644
index 00000000000..570982ced29
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/tzupdate.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.tzupdate;
+in {
+ options.services.tzupdate = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the tzupdate timezone updating service. This provides
+ a one-shot service which can be activated with systemctl to
+ update the timezone.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # We need to have imperative time zone management for this to work.
+ # This will give users an error if they have set an explicit time
+ # zone, which is better than silently overriding it.
+ time.timeZone = null;
+
+ # We provide a one-shot service which can be manually run. We could
+ # provide a service that runs on startup, but it's tricky to get
+ # a service to run after you have *internet* access.
+ systemd.services.tzupdate = {
+ description = "tzupdate timezone update service";
+ wants = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ # We could link directly into pkgs.tzdata, but at least timedatectl seems
+ # to expect the symlink to point directly to a file in etc.
+ # Setting the "debian timezone file" to point at /dev/null stops it doing anything.
+ ExecStart = "${pkgs.tzupdate}/bin/tzupdate -z /etc/zoneinfo -d /dev/null";
+ };
+ };
+ };
+
+ meta.maintainers = [ maintainers.michaelpj ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/uhub.nix b/nixpkgs/nixos/modules/services/misc/uhub.nix
new file mode 100644
index 00000000000..753580c3e40
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/uhub.nix
@@ -0,0 +1,186 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.uhub;
+
+ uhubPkg = pkgs.uhub.override { tlsSupport = cfg.enableTLS; };
+
+ pluginConfig = ""
+ + optionalString cfg.plugins.authSqlite.enable ''
+ plugin ${uhubPkg.mod_auth_sqlite}/mod_auth_sqlite.so "file=${cfg.plugins.authSqlite.file}"
+ ''
+ + optionalString cfg.plugins.logging.enable ''
+ plugin ${uhubPkg.mod_logging}/mod_logging.so ${if cfg.plugins.logging.syslog then "syslog=true" else "file=${cfg.plugins.logging.file}"}
+ ''
+ + optionalString cfg.plugins.welcome.enable ''
+ plugin ${uhubPkg.mod_welcome}/mod_welcome.so "motd=${pkgs.writeText "motd.txt" cfg.plugins.welcome.motd} rules=${pkgs.writeText "rules.txt" cfg.plugins.welcome.rules}"
+ ''
+ + optionalString cfg.plugins.history.enable ''
+ plugin ${uhubPkg.mod_chat_history}/mod_chat_history.so "history_max=${toString cfg.plugins.history.max} history_default=${toString cfg.plugins.history.default} history_connect=${toString cfg.plugins.history.connect}"
+ '';
+
+ uhubConfigFile = pkgs.writeText "uhub.conf" ''
+ file_acl=${pkgs.writeText "users.conf" cfg.aclConfig}
+ file_plugins=${pkgs.writeText "plugins.conf" pluginConfig}
+ server_bind_addr=${cfg.address}
+ server_port=${toString cfg.port}
+ ${lib.optionalString cfg.enableTLS "tls_enable=yes"}
+ ${cfg.hubConfig}
+ '';
+
+in
+
+{
+ options = {
+
+ services.uhub = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the uhub ADC hub.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 1511;
+ description = "TCP port to bind the hub to.";
+ };
+
+ address = mkOption {
+ type = types.str;
+ default = "any";
+ description = "Address to bind the hub to.";
+ };
+
+ enableTLS = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable TLS support.";
+ };
+
+ hubConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Contents of uhub configuration file.";
+ };
+
+ aclConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Contents of user ACL configuration file.";
+ };
+
+ plugins = {
+
+ authSqlite = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Sqlite authentication database plugin";
+ };
+ file = mkOption {
+ type = types.path;
+ example = "/var/db/uhub-users";
+ description = "Path to user database. Use the uhub-passwd utility to create the database and add/remove users.";
+ };
+ };
+
+ logging = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the logging plugin.";
+ };
+ file = mkOption {
+ type = types.str;
+ default = "";
+ description = "Path of log file.";
+ };
+ syslog = mkOption {
+ type = types.bool;
+ default = false;
+ description = "If true then the system log is used instead of writing to file.";
+ };
+ };
+
+ welcome = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the welcome plugin.";
+ };
+ motd = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Welcome message displayed to clients after connecting
+ and with the <literal>!motd</literal> command.
+ '';
+ };
+ rules = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Rules message, displayed to clients with the <literal>!rules</literal> command.
+ '';
+ };
+ };
+
+ history = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the history plugin.";
+ };
+ max = mkOption {
+ type = types.int;
+ default = 200;
+ description = "The maximum number of messages to keep in history";
+ };
+ default = mkOption {
+ type = types.int;
+ default = 10;
+ description = "When !history is provided without arguments, then this default number of messages are returned.";
+ };
+ connect = mkOption {
+ type = types.int;
+ default = 5;
+ description = "The number of chat history messages to send when users connect (0 = do not send any history).";
+ };
+ };
+
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ users = {
+ users = singleton {
+ name = "uhub";
+ uid = config.ids.uids.uhub;
+ };
+ groups = singleton {
+ name = "uhub";
+ gid = config.ids.gids.uhub;
+ };
+ };
+
+ systemd.services.uhub = {
+ description = "high performance peer-to-peer hub for the ADC network";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "notify";
+ ExecStart = "${uhubPkg}/bin/uhub -c ${uhubConfigFile} -u uhub -g uhub -L";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/weechat.nix b/nixpkgs/nixos/modules/services/misc/weechat.nix
new file mode 100644
index 00000000000..c6ff540ea12
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/weechat.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.weechat;
+in
+
+{
+ options.services.weechat = {
+ enable = mkEnableOption "weechat";
+ root = mkOption {
+ description = "Weechat state directory.";
+ type = types.str;
+ default = "/var/lib/weechat";
+ };
+ sessionName = mkOption {
+ description = "Name of the `screen' session for weechat.";
+ default = "weechat-screen";
+ type = types.str;
+ };
+ binary = mkOption {
+ description = "Binary to execute (by default \${weechat}/bin/weechat).";
+ example = literalExample ''
+ ''${pkgs.weechat}/bin/weechat-headless
+ '';
+ default = "${pkgs.weechat}/bin/weechat";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users = {
+ groups.weechat = {};
+ users.weechat = {
+ createHome = true;
+ group = "weechat";
+ home = cfg.root;
+ isSystemUser = true;
+ };
+ };
+
+ systemd.services.weechat = {
+ environment.WEECHAT_HOME = cfg.root;
+ serviceConfig = {
+ User = "weechat";
+ Group = "weechat";
+ RemainAfterExit = "yes";
+ };
+ script = "exec ${config.security.wrapperDir}/screen -Dm -S ${cfg.sessionName} ${cfg.binary}";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network.target" ];
+ };
+
+ security.wrappers.screen.source = "${pkgs.screen}/bin/screen";
+ };
+
+ meta.doc = ./weechat.xml;
+}
diff --git a/nixpkgs/nixos/modules/services/misc/weechat.xml b/nixpkgs/nixos/modules/services/misc/weechat.xml
new file mode 100644
index 00000000000..7255edfb9da
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/weechat.xml
@@ -0,0 +1,66 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="module-services-weechat">
+ <title>WeeChat</title>
+ <para>
+ <link xlink:href="https://weechat.org/">WeeChat</link> is a fast and
+ extensible IRC client.
+ </para>
+ <section xml:id="module-services-weechat-basic-usage">
+ <title>Basic Usage</title>
+
+ <para>
+ By default, the module creates a
+ <literal><link xlink:href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</link></literal>
+ unit which runs the chat client in a detached
+ <literal><link xlink:href="https://www.gnu.org/software/screen/">screen</link></literal>
+ session.
+ </para>
+
+ <para>
+ This can be done by enabling the <literal>weechat</literal> service:
+<programlisting>
+{ ... }:
+
+{
+ <link linkend="opt-services.weechat.enable">services.weechat.enable</link> = true;
+}
+</programlisting>
+ </para>
+
+ <para>
+ The service is managed by a dedicated user named <literal>weechat</literal>
+ in the state directory <literal>/var/lib/weechat</literal>.
+ </para>
+ </section>
+ <section xml:id="module-services-weechat-reattach">
+ <title>Re-attaching to WeeChat</title>
+
+ <para>
+ WeeChat runs in a screen session owned by a dedicated user. To explicitly
+ allow your another user to attach to this session, the
+ <literal>screenrc</literal> needs to be tweaked by adding
+ <link xlink:href="https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser">multiuser</link>
+ support:
+<programlisting>
+{
+ <link linkend="opt-programs.screen.screenrc">programs.screen.screenrc</link> = ''
+ multiuser on
+ acladd normal_user
+ '';
+}
+</programlisting>
+ Now, the session can be re-attached like this:
+<programlisting>
+screen -x weechat/weechat-screen
+</programlisting>
+ </para>
+
+ <para>
+ <emphasis>The session name can be changed using
+ <link linkend="opt-services.weechat.sessionName">services.weechat.sessionName.</link></emphasis>
+ </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/xmr-stak.nix b/nixpkgs/nixos/modules/services/misc/xmr-stak.nix
new file mode 100644
index 00000000000..a87878c31e0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/xmr-stak.nix
@@ -0,0 +1,93 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xmr-stak;
+
+ pkg = pkgs.xmr-stak.override {
+ inherit (cfg) openclSupport cudaSupport;
+ };
+
+in
+
+{
+ options = {
+ services.xmr-stak = {
+ enable = mkEnableOption "xmr-stak miner";
+ openclSupport = mkEnableOption "support for OpenCL (AMD/ATI graphics cards)";
+ cudaSupport = mkEnableOption "support for CUDA (NVidia graphics cards)";
+
+ extraArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "--noCPU" "--currency monero" ];
+ description = "List of parameters to pass to xmr-stak.";
+ };
+
+ configFiles = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = literalExample ''
+ {
+ "config.txt" = '''
+ "verbose_level" : 4,
+ "h_print_time" : 60,
+ "tls_secure_algo" : true,
+ ''';
+ "pools.txt" = '''
+ "currency" : "monero7",
+ "pool_list" :
+ [ { "pool_address" : "pool.supportxmr.com:443",
+ "wallet_address" : "my-wallet-address",
+ "rig_id" : "",
+ "pool_password" : "nixos",
+ "use_nicehash" : false,
+ "use_tls" : true,
+ "tls_fingerprint" : "",
+ "pool_weight" : 23
+ },
+ ],
+ ''';
+ }
+ '';
+ description = ''
+ Content of config files like config.txt, pools.txt or cpu.txt.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.xmr-stak = {
+ wantedBy = [ "multi-user.target" ];
+ bindsTo = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+ environment = mkIf cfg.cudaSupport {
+ LD_LIBRARY_PATH = "${pkgs.linuxPackages_latest.nvidia_x11}/lib";
+ };
+
+ preStart = concatStrings (flip mapAttrsToList cfg.configFiles (fn: content: ''
+ ln -sf '${pkgs.writeText "xmr-stak-${fn}" content}' '${fn}'
+ ''));
+
+ serviceConfig = let rootRequired = cfg.openclSupport || cfg.cudaSupport; in {
+ ExecStart = "${pkg}/bin/xmr-stak ${concatStringsSep " " cfg.extraArgs}";
+ # xmr-stak generates cpu and/or gpu configuration files
+ WorkingDirectory = "/tmp";
+ PrivateTmp = true;
+ DynamicUser = !rootRequired;
+ LimitMEMLOCK = toString (1024*1024);
+ };
+ };
+ };
+
+ imports = [
+ (mkRemovedOptionModule ["services" "xmr-stak" "configText"] ''
+ This option was removed in favour of `services.xmr-stak.configFiles`
+ because the new config file `pools.txt` was introduced. You are
+ now able to define all other config files like cpu.txt or amd.txt.
+ '')
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/zoneminder.nix b/nixpkgs/nixos/modules/services/misc/zoneminder.nix
new file mode 100644
index 00000000000..3bff04e7127
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/zoneminder.nix
@@ -0,0 +1,372 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.zoneminder;
+ fpm = config.services.phpfpm.pools.zoneminder;
+ pkg = pkgs.zoneminder;
+
+ dirName = pkg.dirName;
+
+ user = "zoneminder";
+ group = {
+ nginx = config.services.nginx.group;
+ none = user;
+ }.${cfg.webserver};
+
+ useNginx = cfg.webserver == "nginx";
+
+ defaultDir = "/var/lib/${user}";
+ home = if useCustomDir then cfg.storageDir else defaultDir;
+
+ useCustomDir = cfg.storageDir != null;
+
+ zms = "/cgi-bin/zms";
+
+ dirs = dirList: [ dirName ] ++ map (e: "${dirName}/${e}") dirList;
+
+ cacheDirs = [ "swap" ];
+ libDirs = [ "events" "exports" "images" "sounds" ];
+
+ dirStanzas = baseDir:
+ lib.concatStringsSep "\n" (map (e:
+ "ZM_DIR_${lib.toUpper e}=${baseDir}/${e}"
+ ) libDirs);
+
+ defaultsFile = pkgs.writeText "60-defaults.conf" ''
+ # 01-system-paths.conf
+ ${dirStanzas home}
+ ZM_PATH_ARP=${lib.getBin pkgs.nettools}/bin/arp
+ ZM_PATH_LOGS=/var/log/${dirName}
+ ZM_PATH_MAP=/dev/shm
+ ZM_PATH_SOCKS=/run/${dirName}
+ ZM_PATH_SWAP=/var/cache/${dirName}/swap
+ ZM_PATH_ZMS=${zms}
+
+ # 02-multiserver.conf
+ ZM_SERVER_HOST=
+
+ # Database
+ ZM_DB_TYPE=mysql
+ ZM_DB_HOST=${cfg.database.host}
+ ZM_DB_NAME=${cfg.database.name}
+ ZM_DB_USER=${cfg.database.username}
+ ZM_DB_PASS=${cfg.database.password}
+
+ # Web
+ ZM_WEB_USER=${user}
+ ZM_WEB_GROUP=${group}
+ '';
+
+ configFile = pkgs.writeText "80-nixos.conf" ''
+ # You can override defaults here
+
+ ${cfg.extraConfig}
+ '';
+
+ 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.
+ Not used if <literal>createLocally</literal> is set.
+ '';
+ };
+ };
+
+ cameras = mkOption {
+ type = types.int;
+ default = 1;
+ description = ''
+ Set this to the number of cameras you expect to support.
+ '';
+ };
+
+ storageDir = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/storage/tank";
+ description = ''
+ ZoneMinder can generate quite a lot of data, so in case you don't want
+ to use the default ${home}, you can override the path here.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional configuration added verbatim to the configuration file.
+ '';
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.database.createLocally -> cfg.database.username == user;
+ message = "services.zoneminder.database.username must be set to ${user} if services.zoneminder.database.createLocally is set true";
+ }
+ ];
+
+ environment.etc = {
+ "zoneminder/60-defaults.conf".source = defaultsFile;
+ "zoneminder/80-nixos.conf".source = configFile;
+ };
+
+ networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
+ cfg.port
+ 6802 # zmtrigger
+ ];
+
+ services = {
+ fcgiwrap = lib.mkIf useNginx {
+ enable = true;
+ preforkProcesses = cfg.cameras;
+ inherit user group;
+ };
+
+ mysql = lib.mkIf cfg.database.createLocally {
+ enable = true;
+ package = lib.mkDefault pkgs.mariadb;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [{
+ name = cfg.database.username;
+ ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+ }];
+ };
+
+ nginx = lib.mkIf useNginx {
+ enable = true;
+ virtualHosts = {
+ ${cfg.hostname} = {
+ default = true;
+ root = "${pkg}/share/zoneminder/www";
+ listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ];
+ extraConfig = let
+ fcgi = config.services.fcgiwrap;
+ in ''
+ index index.php;
+
+ location / {
+ try_files $uri $uri/ /index.php?$args =404;
+
+ rewrite ^/skins/.*/css/fonts/(.*)$ /fonts/$1 permanent;
+
+ location ~ /api/(css|img|ico) {
+ rewrite ^/api(.+)$ /api/app/webroot/$1 break;
+ try_files $uri $uri/ =404;
+ }
+
+ location ~ \.(gif|ico|jpg|jpeg|png)$ {
+ access_log off;
+ expires 30d;
+ }
+
+ location /api {
+ rewrite ^/api(.+)$ /api/app/webroot/index.php?p=$1 last;
+ }
+
+ location /cgi-bin {
+ gzip off;
+
+ include ${pkgs.nginx}/conf/fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME ${pkg}/libexec/zoneminder/${zms};
+ fastcgi_param HTTP_PROXY "";
+ fastcgi_intercept_errors on;
+
+ fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress};
+ }
+
+ location /cache/ {
+ alias /var/cache/${dirName};
+ }
+
+ location ~ \.php$ {
+ try_files $uri =404;
+ fastcgi_index index.php;
+
+ include ${pkgs.nginx}/conf/fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $request_filename;
+ fastcgi_param HTTP_PROXY "";
+
+ fastcgi_pass unix:${fpm.socket};
+ }
+ }
+ '';
+ };
+ };
+ };
+
+ phpfpm = lib.mkIf useNginx {
+ pools.zoneminder = {
+ inherit user group;
+ phpOptions = ''
+ date.timezone = "${config.time.timeZone}"
+
+ ${lib.concatStringsSep "\n" (map (e:
+ "extension=${e.pkg}/lib/php/extensions/${e.name}.so") phpExtensions)}
+ '';
+ settings = lib.mapAttrs (name: lib.mkDefault) {
+ "listen.owner" = user;
+ "listen.group" = group;
+ "listen.mode" = "0660";
+
+ "pm" = "dynamic";
+ "pm.start_servers" = 1;
+ "pm.min_spare_servers" = 1;
+ "pm.max_spare_servers" = 2;
+ "pm.max_requests" = 500;
+ "pm.max_children" = 5;
+ "pm.status_path" = "/$pool-status";
+ "ping.path" = "/$pool-ping";
+ };
+ };
+ };
+ };
+
+ systemd.services = {
+ zoneminder = with pkgs; {
+ inherit (zoneminder.meta) description;
+ documentation = [ "https://zoneminder.readthedocs.org/en/latest/" ];
+ path = [
+ coreutils
+ procps
+ psmisc
+ ];
+ after = [ "nginx.service" ] ++ lib.optional cfg.database.createLocally "mysql.service";
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ defaultsFile configFile ];
+ preStart = lib.optionalString useCustomDir ''
+ install -dm775 -o ${user} -g ${group} ${cfg.storageDir}/{${lib.concatStringsSep "," libDirs}}
+ '' + lib.optionalString cfg.database.createLocally ''
+ if ! test -e "/var/lib/${dirName}/db-created"; then
+ ${config.services.mysql.package}/bin/mysql < ${pkg}/share/zoneminder/db/zm_create.sql
+ touch "/var/lib/${dirName}/db-created"
+ fi
+ '';
+ serviceConfig = {
+ User = user;
+ Group = group;
+ SupplementaryGroups = [ "video" ];
+ ExecStart = "${zoneminder}/bin/zmpkg.pl start";
+ ExecStop = "${zoneminder}/bin/zmpkg.pl stop";
+ ExecReload = "${zoneminder}/bin/zmpkg.pl restart";
+ PIDFile = "/run/${dirName}/zm.pid";
+ Type = "forking";
+ Restart = "on-failure";
+ RestartSec = "10s";
+ CacheDirectory = dirs cacheDirs;
+ RuntimeDirectory = dirName;
+ ReadWriteDirectories = lib.mkIf useCustomDir [ cfg.storageDir ];
+ StateDirectory = dirs (if useCustomDir then [] else libDirs);
+ LogsDirectory = dirName;
+ PrivateTmp = true;
+ ProtectSystem = "strict";
+ ProtectKernelTunables = true;
+ SystemCallArchitectures = "native";
+ NoNewPrivileges = true;
+ };
+ };
+ };
+
+ users.groups.${user} = {
+ gid = config.ids.gids.zoneminder;
+ };
+
+ users.users.${user} = {
+ uid = config.ids.uids.zoneminder;
+ group = user;
+ inherit home;
+ inherit (pkgs.zoneminder.meta) description;
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ peterhoeg ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/zookeeper.nix b/nixpkgs/nixos/modules/services/misc/zookeeper.nix
new file mode 100644
index 00000000000..5d91e44a199
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/zookeeper.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.zookeeper;
+
+ zookeeperConfig = ''
+ dataDir=${cfg.dataDir}
+ clientPort=${toString cfg.port}
+ autopurge.purgeInterval=${toString cfg.purgeInterval}
+ ${cfg.extraConf}
+ ${cfg.servers}
+ '';
+
+ configDir = pkgs.buildEnv {
+ name = "zookeeper-conf";
+ paths = [
+ (pkgs.writeTextDir "zoo.cfg" zookeeperConfig)
+ (pkgs.writeTextDir "log4j.properties" cfg.logging)
+ ];
+ };
+
+in {
+
+ options.services.zookeeper = {
+ enable = mkOption {
+ description = "Whether to enable Zookeeper.";
+ default = false;
+ type = types.bool;
+ };
+
+ port = mkOption {
+ description = "Zookeeper Client port.";
+ default = 2181;
+ type = types.int;
+ };
+
+ id = mkOption {
+ description = "Zookeeper ID.";
+ default = 0;
+ type = types.int;
+ };
+
+ purgeInterval = mkOption {
+ description = ''
+ The time interval in hours for which the purge task has to be triggered. Set to a positive integer (1 and above) to enable the auto purging.
+ '';
+ default = 1;
+ type = types.int;
+ };
+
+ extraConf = mkOption {
+ description = "Extra configuration for Zookeeper.";
+ type = types.lines;
+ default = ''
+ initLimit=5
+ syncLimit=2
+ tickTime=2000
+ '';
+ };
+
+ servers = mkOption {
+ description = "All Zookeeper Servers.";
+ default = "";
+ type = types.lines;
+ example = ''
+ server.0=host0:2888:3888
+ server.1=host1:2888:3888
+ server.2=host2:2888:3888
+ '';
+ };
+
+ logging = mkOption {
+ description = "Zookeeper logging configuration.";
+ default = ''
+ zookeeper.root.logger=INFO, CONSOLE
+ log4j.rootLogger=INFO, CONSOLE
+ log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+ log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+ log4j.appender.CONSOLE.layout.ConversionPattern=[myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
+ '';
+ type = types.lines;
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/zookeeper";
+ description = ''
+ Data directory for Zookeeper
+ '';
+ };
+
+ extraCmdLineOptions = mkOption {
+ description = "Extra command line options for the Zookeeper launcher.";
+ default = [ "-Dcom.sun.management.jmxremote" "-Dcom.sun.management.jmxremote.local.only=true" ];
+ type = types.listOf types.str;
+ example = [ "-Djava.net.preferIPv4Stack=true" "-Dcom.sun.management.jmxremote" "-Dcom.sun.management.jmxremote.local.only=true" ];
+ };
+
+ preferIPv4 = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Add the -Djava.net.preferIPv4Stack=true flag to the Zookeeper server.
+ '';
+ };
+
+ package = mkOption {
+ description = "The zookeeper package to use";
+ default = pkgs.zookeeper;
+ defaultText = "pkgs.zookeeper";
+ type = types.package;
+ };
+
+ };
+
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [cfg.package];
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0700 zookeeper - - -"
+ "Z '${cfg.dataDir}' 0700 zookeeper - - -"
+ ];
+
+ systemd.services.zookeeper = {
+ description = "Zookeeper Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment = { ZOOCFGDIR = configDir; };
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.jre}/bin/java \
+ -cp "${cfg.package}/lib/*:${cfg.package}/${cfg.package.name}.jar:${configDir}" \
+ ${escapeShellArgs cfg.extraCmdLineOptions} \
+ -Dzookeeper.datadir.autocreate=false \
+ ${optionalString cfg.preferIPv4 "-Djava.net.preferIPv4Stack=true"} \
+ org.apache.zookeeper.server.quorum.QuorumPeerMain \
+ ${configDir}/zoo.cfg
+ '';
+ User = "zookeeper";
+ };
+ preStart = ''
+ echo "${toString cfg.id}" > ${cfg.dataDir}/myid
+ '';
+ };
+
+ users.users = singleton {
+ name = "zookeeper";
+ uid = config.ids.uids.zookeeper;
+ description = "Zookeeper daemon user";
+ home = cfg.dataDir;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/alerta.nix b/nixpkgs/nixos/modules/services/monitoring/alerta.nix
new file mode 100644
index 00000000000..34f2d41706a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/alerta.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.alerta;
+
+ alertaConf = pkgs.writeTextFile {
+ name = "alertad.conf";
+ text = ''
+ DATABASE_URL = '${cfg.databaseUrl}'
+ DATABASE_NAME = '${cfg.databaseName}'
+ LOG_FILE = '${cfg.logDir}/alertad.log'
+ LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+ CORS_ORIGINS = [ ${concatMapStringsSep ", " (s: "\"" + s + "\"") cfg.corsOrigins} ];
+ AUTH_REQUIRED = ${if cfg.authenticationRequired then "True" else "False"}
+ SIGNUP_ENABLED = ${if cfg.signupEnabled then "True" else "False"}
+ ${cfg.extraConfig}
+ '';
+ };
+in
+{
+ options.services.alerta = {
+ enable = mkEnableOption "alerta";
+
+ port = mkOption {
+ type = types.int;
+ default = 5000;
+ description = "Port of Alerta";
+ };
+
+ bind = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ example = literalExample "0.0.0.0";
+ description = "Address to bind to. The default is to bind to all addresses";
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ description = "Location where the logfiles are stored";
+ default = "/var/log/alerta";
+ };
+
+ databaseUrl = mkOption {
+ type = types.str;
+ description = "URL of the MongoDB or PostgreSQL database to connect to";
+ default = "mongodb://localhost";
+ example = "mongodb://localhost";
+ };
+
+ databaseName = mkOption {
+ type = types.str;
+ description = "Name of the database instance to connect to";
+ default = "monitoring";
+ example = "monitoring";
+ };
+
+ corsOrigins = mkOption {
+ type = types.listOf types.str;
+ description = "List of URLs that can access the API for Cross-Origin Resource Sharing (CORS)";
+ example = [ "http://localhost" "http://localhost:5000" ];
+ default = [ "http://localhost" "http://localhost:5000" ];
+ };
+
+ authenticationRequired = mkOption {
+ type = types.bool;
+ description = "Whether users must authenticate when using the web UI or command-line tool";
+ default = false;
+ };
+
+ signupEnabled = mkOption {
+ type = types.bool;
+ description = "Whether to prevent sign-up of new users via the web UI";
+ default = true;
+ };
+
+ extraConfig = mkOption {
+ description = "These lines go into alertad.conf verbatim.";
+ default = "";
+ type = types.lines;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.logDir}' - alerta alerta - -"
+ ];
+
+ systemd.services.alerta = {
+ description = "Alerta Monitoring System";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "networking.target" ];
+ environment = {
+ ALERTA_SVR_CONF_FILE = alertaConf;
+ };
+ serviceConfig = {
+ ExecStart = "${pkgs.python36Packages.alerta-server}/bin/alertad run --port ${toString cfg.port} --host ${cfg.bind}";
+ User = "alerta";
+ Group = "alerta";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.python36Packages.alerta ];
+
+ users.users.alerta = {
+ uid = config.ids.uids.alerta;
+ description = "Alerta user";
+ };
+
+ users.groups.alerta = {
+ gid = config.ids.gids.alerta;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/apcupsd.nix b/nixpkgs/nixos/modules/services/monitoring/apcupsd.nix
new file mode 100644
index 00000000000..75218aa1d46
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/apcupsd.nix
@@ -0,0 +1,191 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.apcupsd;
+
+ configFile = pkgs.writeText "apcupsd.conf" ''
+ ## apcupsd.conf v1.1 ##
+ # apcupsd complains if the first line is not like above.
+ ${cfg.configText}
+ SCRIPTDIR ${toString scriptDir}
+ '';
+
+ # List of events from "man apccontrol"
+ eventList = [
+ "annoyme"
+ "battattach"
+ "battdetach"
+ "changeme"
+ "commfailure"
+ "commok"
+ "doreboot"
+ "doshutdown"
+ "emergency"
+ "failing"
+ "killpower"
+ "loadlimit"
+ "mainsback"
+ "onbattery"
+ "offbattery"
+ "powerout"
+ "remotedown"
+ "runlimit"
+ "timeout"
+ "startselftest"
+ "endselftest"
+ ];
+
+ shellCmdsForEventScript = eventname: commands: ''
+ echo "#!${pkgs.runtimeShell}" > "$out/${eventname}"
+ echo '${commands}' >> "$out/${eventname}"
+ chmod a+x "$out/${eventname}"
+ '';
+
+ eventToShellCmds = event: if builtins.hasAttr event cfg.hooks then (shellCmdsForEventScript event (builtins.getAttr event cfg.hooks)) else "";
+
+ scriptDir = pkgs.runCommand "apcupsd-scriptdir" { preferLocalBuild = true; } (''
+ mkdir "$out"
+ # Copy SCRIPTDIR from apcupsd package
+ cp -r ${pkgs.apcupsd}/etc/apcupsd/* "$out"/
+ # Make the files writeable (nix will unset the write bits afterwards)
+ chmod u+w "$out"/*
+ # Remove the sample event notification scripts, because they don't work
+ # anyways (they try to send mail to "root" with the "mail" command)
+ (cd "$out" && rm changeme commok commfailure onbattery offbattery)
+ # Remove the sample apcupsd.conf file (we're generating our own)
+ rm "$out/apcupsd.conf"
+ # Set the SCRIPTDIR= line in apccontrol to the dir we're creating now
+ sed -i -e "s|^SCRIPTDIR=.*|SCRIPTDIR=$out|" "$out/apccontrol"
+ '' + concatStringsSep "\n" (map eventToShellCmds eventList)
+
+ );
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.apcupsd = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable the APC UPS daemon. apcupsd monitors your UPS and
+ permits orderly shutdown of your computer in the event of a power
+ failure. User manual: http://www.apcupsd.com/manual/manual.html.
+ Note that apcupsd runs as root (to allow shutdown of computer).
+ You can check the status of your UPS with the "apcaccess" command.
+ '';
+ };
+
+ configText = mkOption {
+ default = ''
+ UPSTYPE usb
+ NISIP 127.0.0.1
+ BATTERYLEVEL 50
+ MINUTES 5
+ '';
+ type = types.lines;
+ description = ''
+ Contents of the runtime configuration file, apcupsd.conf. The default
+ settings makes apcupsd autodetect USB UPSes, limit network access to
+ localhost and shutdown the system when the battery level is below 50
+ percent, or when the UPS has calculated that it has 5 minutes or less
+ of remaining power-on time. See man apcupsd.conf for details.
+ '';
+ };
+
+ hooks = mkOption {
+ default = {};
+ example = {
+ doshutdown = ''# shell commands to notify that the computer is shutting down'';
+ };
+ type = types.attrsOf types.lines;
+ description = ''
+ Each attribute in this option names an apcupsd event and the string
+ value it contains will be executed in a shell, in response to that
+ event (prior to the default action). See "man apccontrol" for the
+ list of events and what they represent.
+
+ A hook script can stop apccontrol from doing its default action by
+ exiting with value 99. Do not do this unless you know what you're
+ doing.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [ {
+ assertion = let hooknames = builtins.attrNames cfg.hooks; in all (x: elem x eventList) hooknames;
+ message = ''
+ One (or more) attribute names in services.apcupsd.hooks are invalid.
+ Current attribute names: ${toString (builtins.attrNames cfg.hooks)}
+ Valid attribute names : ${toString eventList}
+ '';
+ } ];
+
+ # Give users access to the "apcaccess" tool
+ environment.systemPackages = [ pkgs.apcupsd ];
+
+ # NOTE 1: apcupsd runs as root because it needs permission to run
+ # "shutdown"
+ #
+ # NOTE 2: When apcupsd calls "wall", it prints an error because stdout is
+ # not connected to a tty (it is connected to the journal):
+ # wall: cannot get tty name: Inappropriate ioctl for device
+ # The message still gets through.
+ systemd.services.apcupsd = {
+ description = "APC UPS Daemon";
+ wantedBy = [ "multi-user.target" ];
+ preStart = "mkdir -p /run/apcupsd/";
+ serviceConfig = {
+ ExecStart = "${pkgs.apcupsd}/bin/apcupsd -b -f ${configFile} -d1";
+ # TODO: When apcupsd has initiated a shutdown, systemd always ends up
+ # waiting for it to stop ("A stop job is running for UPS daemon"). This
+ # is weird, because in the journal one can clearly see that apcupsd has
+ # received the SIGTERM signal and has already quit (or so it seems).
+ # This reduces the wait time from 90 seconds (default) to just 5. Then
+ # systemd kills it with SIGKILL.
+ TimeoutStopSec = 5;
+ };
+ unitConfig.Documentation = "man:apcupsd(8)";
+ };
+
+ # A special service to tell the UPS to power down/hibernate just before the
+ # computer shuts down. (The UPS has a built in delay before it actually
+ # shuts off power.) Copied from here:
+ # http://forums.opensuse.org/english/get-technical-help-here/applications/479499-apcupsd-systemd-killpower-issues.html
+ systemd.services.apcupsd-killpower = {
+ description = "APC UPS Kill Power";
+ after = [ "shutdown.target" ]; # append umount.target?
+ before = [ "final.target" ];
+ wantedBy = [ "shutdown.target" ];
+ unitConfig = {
+ ConditionPathExists = "/run/apcupsd/powerfail";
+ DefaultDependencies = "no";
+ };
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.apcupsd}/bin/apcupsd --killpower -f ${configFile}";
+ TimeoutSec = "infinity";
+ StandardOutput = "tty";
+ RemainAfterExit = "yes";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/arbtt.nix b/nixpkgs/nixos/modules/services/monitoring/arbtt.nix
new file mode 100644
index 00000000000..b41a3c7b501
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/arbtt.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.arbtt;
+in {
+ options = {
+ services.arbtt = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the arbtt statistics capture service.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.haskellPackages.arbtt;
+ defaultText = "pkgs.haskellPackages.arbtt";
+ example = literalExample "pkgs.haskellPackages.arbtt";
+ description = ''
+ The package to use for the arbtt binaries.
+ '';
+ };
+
+ logFile = mkOption {
+ type = types.str;
+ default = "%h/.arbtt/capture.log";
+ example = "/home/username/.arbtt-capture.log";
+ description = ''
+ The log file for captured samples.
+ '';
+ };
+
+ sampleRate = mkOption {
+ type = types.int;
+ default = 60;
+ example = 120;
+ description = ''
+ The sampling interval in seconds.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.arbtt = {
+ description = "arbtt statistics capture service";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${cfg.package}/bin/arbtt-capture --logfile=${cfg.logFile} --sample-rate=${toString cfg.sampleRate}";
+ Restart = "always";
+ };
+ };
+ };
+
+ meta.maintainers = [ maintainers.michaelpj ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/bosun.nix b/nixpkgs/nixos/modules/services/monitoring/bosun.nix
new file mode 100644
index 00000000000..b1c12cce1f8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/bosun.nix
@@ -0,0 +1,166 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.bosun;
+
+ configFile = pkgs.writeText "bosun.conf" ''
+ ${optionalString (cfg.opentsdbHost !=null) "tsdbHost = ${cfg.opentsdbHost}"}
+ ${optionalString (cfg.influxHost !=null) "influxHost = ${cfg.influxHost}"}
+ httpListen = ${cfg.listenAddress}
+ stateFile = ${cfg.stateFile}
+ ledisDir = ${cfg.ledisDir}
+ checkFrequency = ${cfg.checkFrequency}
+
+ ${cfg.extraConfig}
+ '';
+
+in {
+
+ options = {
+
+ services.bosun = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run bosun.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.bosun;
+ defaultText = "pkgs.bosun";
+ example = literalExample "pkgs.bosun";
+ description = ''
+ bosun binary to use.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "bosun";
+ description = ''
+ User account under which bosun runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "bosun";
+ description = ''
+ Group account under which bosun runs.
+ '';
+ };
+
+ opentsdbHost = mkOption {
+ type = types.nullOr types.str;
+ default = "localhost:4242";
+ description = ''
+ Host and port of the OpenTSDB database that stores bosun data.
+ To disable opentsdb you can pass null as parameter.
+ '';
+ };
+
+ influxHost = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "localhost:8086";
+ description = ''
+ Host and port of the influxdb database.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = ":8070";
+ description = ''
+ The host address and port that bosun's web interface will listen on.
+ '';
+ };
+
+ stateFile = mkOption {
+ type = types.path;
+ default = "/var/lib/bosun/bosun.state";
+ description = ''
+ Path to bosun's state file.
+ '';
+ };
+
+ ledisDir = mkOption {
+ type = types.path;
+ default = "/var/lib/bosun/ledis_data";
+ description = ''
+ Path to bosun's ledis data dir
+ '';
+ };
+
+ checkFrequency = mkOption {
+ type = types.str;
+ default = "5m";
+ description = ''
+ Bosun's check frequency
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration options for Bosun. You should describe your
+ desired templates, alerts, macros, etc through this configuration
+ option.
+
+ A detailed description of the supported syntax can be found at-spi2-atk
+ http://bosun.org/configuration.html
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.bosun = {
+ description = "bosun metrics collector (part of Bosun)";
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ mkdir -p "$(dirname "${cfg.stateFile}")";
+ touch "${cfg.stateFile}"
+ touch "${cfg.stateFile}.tmp"
+
+ mkdir -p "${cfg.ledisDir}";
+
+ if [ "$(id -u)" = 0 ]; then
+ chown ${cfg.user}:${cfg.group} "${cfg.stateFile}"
+ chown ${cfg.user}:${cfg.group} "${cfg.stateFile}.tmp"
+ chown ${cfg.user}:${cfg.group} "${cfg.ledisDir}"
+ fi
+ '';
+
+ serviceConfig = {
+ PermissionsStartOnly = true;
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = ''
+ ${cfg.package.bin}/bin/bosun -c ${configFile}
+ '';
+ };
+ };
+
+ users.users.bosun = {
+ description = "bosun user";
+ group = "bosun";
+ uid = config.ids.uids.bosun;
+ };
+
+ users.groups.bosun.gid = config.ids.gids.bosun;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix b/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix
new file mode 100644
index 00000000000..695a8c42e85
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix
@@ -0,0 +1,141 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cadvisor;
+
+in {
+ options = {
+ services.cadvisor = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to enable cadvisor service.";
+ };
+
+ listenAddress = mkOption {
+ default = "127.0.0.1";
+ type = types.str;
+ description = "Cadvisor listening host";
+ };
+
+ port = mkOption {
+ default = 8080;
+ type = types.int;
+ description = "Cadvisor listening port";
+ };
+
+ storageDriver = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ example = "influxdb";
+ description = "Cadvisor storage driver.";
+ };
+
+ storageDriverHost = mkOption {
+ default = "localhost:8086";
+ type = types.str;
+ description = "Cadvisor storage driver host.";
+ };
+
+ storageDriverDb = mkOption {
+ default = "root";
+ type = types.str;
+ description = "Cadvisord storage driver database name.";
+ };
+
+ storageDriverUser = mkOption {
+ default = "root";
+ type = types.str;
+ description = "Cadvisor storage driver username.";
+ };
+
+ storageDriverPassword = mkOption {
+ default = "root";
+ type = types.str;
+ description = ''
+ Cadvisor storage driver password.
+
+ Warning: this password is stored in the world-readable Nix store. It's
+ recommended to use the <option>storageDriverPasswordFile</option> option
+ since that gives you control over the security of the password.
+ <option>storageDriverPasswordFile</option> also takes precedence over <option>storageDriverPassword</option>.
+ '';
+ };
+
+ storageDriverPasswordFile = mkOption {
+ type = types.str;
+ description = ''
+ File that contains the cadvisor storage driver password.
+
+ <option>storageDriverPasswordFile</option> takes precedence over <option>storageDriverPassword</option>
+
+ Warning: when <option>storageDriverPassword</option> is non-empty this defaults to a file in the
+ world-readable Nix store that contains the value of <option>storageDriverPassword</option>.
+
+ It's recommended to override this with a path not in the Nix store.
+ Tip: use <link xlink:href='https://nixos.org/nixops/manual/#idm140737318306400'>nixops key management</link>
+ '';
+ };
+
+ storageDriverSecure = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Cadvisor storage driver, enable secure communication.";
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Additional cadvisor options.
+
+ See <link xlink:href='https://github.com/google/cadvisor/blob/master/docs/runtime_options.md'/> for available options.
+ '';
+ };
+ };
+ };
+
+ config = mkMerge [
+ { services.cadvisor.storageDriverPasswordFile = mkIf (cfg.storageDriverPassword != "") (
+ mkDefault (toString (pkgs.writeTextFile {
+ name = "cadvisor-storage-driver-password";
+ text = cfg.storageDriverPassword;
+ }))
+ );
+ }
+
+ (mkIf cfg.enable {
+ systemd.services.cadvisor = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "docker.service" "influxdb.service" ];
+
+ postStart = mkBefore ''
+ until ${pkgs.curl.bin}/bin/curl -s -o /dev/null 'http://${cfg.listenAddress}:${toString cfg.port}/containers/'; do
+ sleep 1;
+ done
+ '';
+
+ script = ''
+ exec ${pkgs.cadvisor}/bin/cadvisor \
+ -logtostderr=true \
+ -listen_ip="${cfg.listenAddress}" \
+ -port="${toString cfg.port}" \
+ ${escapeShellArgs cfg.extraOptions} \
+ ${optionalString (cfg.storageDriver != null) ''
+ -storage_driver "${cfg.storageDriver}" \
+ -storage_driver_user "${cfg.storageDriverHost}" \
+ -storage_driver_db "${cfg.storageDriverDb}" \
+ -storage_driver_user "${cfg.storageDriverUser}" \
+ -storage_driver_password "$(cat "${cfg.storageDriverPasswordFile}")" \
+ ${optionalString cfg.storageDriverSecure "-storage_driver_secure"}
+ ''}
+ '';
+
+ serviceConfig.TimeoutStartSec=300;
+ };
+ virtualisation.docker.enable = mkDefault true;
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/collectd.nix b/nixpkgs/nixos/modules/services/monitoring/collectd.nix
new file mode 100644
index 00000000000..6a4c678eb21
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/collectd.nix
@@ -0,0 +1,103 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.collectd;
+
+ conf = pkgs.writeText "collectd.conf" ''
+ BaseDir "${cfg.dataDir}"
+ AutoLoadPlugin ${boolToString cfg.autoLoadPlugin}
+ Hostname "${config.networking.hostName}"
+
+ LoadPlugin syslog
+ <Plugin "syslog">
+ LogLevel "info"
+ NotifyLevel "OKAY"
+ </Plugin>
+
+ ${concatMapStrings (f: ''
+ Include "${f}"
+ '') cfg.include}
+
+ ${cfg.extraConfig}
+ '';
+
+in {
+ options.services.collectd = with types; {
+ enable = mkEnableOption "collectd agent";
+
+ package = mkOption {
+ default = pkgs.collectd;
+ defaultText = "pkgs.collectd";
+ description = ''
+ Which collectd package to use.
+ '';
+ type = package;
+ };
+
+ user = mkOption {
+ default = "collectd";
+ description = ''
+ User under which to run collectd.
+ '';
+ type = nullOr str;
+ };
+
+ dataDir = mkOption {
+ default = "/var/lib/collectd";
+ description = ''
+ Data directory for collectd agent.
+ '';
+ type = path;
+ };
+
+ autoLoadPlugin = mkOption {
+ default = false;
+ description = ''
+ Enable plugin autoloading.
+ '';
+ type = bool;
+ };
+
+ include = mkOption {
+ default = [];
+ description = ''
+ Additional paths to load config from.
+ '';
+ type = listOf str;
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ description = ''
+ Extra configuration for collectd.
+ '';
+ type = lines;
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - ${cfg.user} - - -"
+ ];
+
+ systemd.services.collectd = {
+ description = "Collectd Monitoring Agent";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/sbin/collectd -C ${conf} -f";
+ User = cfg.user;
+ Restart = "on-failure";
+ RestartSec = 3;
+ };
+ };
+
+ users.users = optional (cfg.user == "collectd") {
+ name = "collectd";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix b/nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix
new file mode 100644
index 00000000000..88ca3a9227d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix
@@ -0,0 +1,34 @@
+# A general watchdog for the linux operating system that should run in the
+# background at all times to ensure a realtime process won't hang the machine
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs) das_watchdog;
+
+in {
+ ###### interface
+
+ options = {
+ services.das_watchdog.enable = mkEnableOption "realtime watchdog";
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.das_watchdog.enable {
+ environment.systemPackages = [ das_watchdog ];
+ systemd.services.das_watchdog = {
+ description = "Watchdog to ensure a realtime process won't hang the machine";
+ after = [ "multi-user.target" "sound.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "root";
+ Type = "simple";
+ ExecStart = "${das_watchdog}/bin/das_watchdog";
+ RemainAfterExit = true;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix b/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix
new file mode 100644
index 00000000000..02a9f316fc3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix
@@ -0,0 +1,271 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.datadog-agent;
+
+ ddConf = {
+ dd_url = "https://app.datadoghq.com";
+ skip_ssl_validation = false;
+ confd_path = "/etc/datadog-agent/conf.d";
+ additional_checksd = "/etc/datadog-agent/checks.d";
+ use_dogstatsd = true;
+ }
+ // optionalAttrs (cfg.logLevel != null) { log_level = cfg.logLevel; }
+ // optionalAttrs (cfg.hostname != null) { inherit (cfg) hostname; }
+ // optionalAttrs (cfg.tags != null ) { tags = concatStringsSep ", " cfg.tags; }
+ // optionalAttrs (cfg.enableLiveProcessCollection) { process_config = { enabled = "true"; }; }
+ // optionalAttrs (cfg.enableTraceAgent) { apm_config = { enabled = true; }; }
+ // cfg.extraConfig;
+
+ # Generate Datadog configuration files for each configured checks.
+ # This works because check configurations have predictable paths,
+ # and because JSON is a valid subset of YAML.
+ makeCheckConfigs = entries: mapAttrsToList (name: conf: {
+ source = pkgs.writeText "${name}-check-conf.yaml" (builtins.toJSON conf);
+ target = "datadog-agent/conf.d/${name}.d/conf.yaml";
+ }) entries;
+
+ defaultChecks = {
+ disk = cfg.diskCheck;
+ network = cfg.networkCheck;
+ };
+
+ # Assemble all check configurations and the top-level agent
+ # configuration.
+ etcfiles = with pkgs; with builtins; [{
+ source = writeText "datadog.yaml" (toJSON ddConf);
+ target = "datadog-agent/datadog.yaml";
+ }] ++ makeCheckConfigs (cfg.checks // defaultChecks);
+
+ # Apply the configured extraIntegrations to the provided agent
+ # package. See the documentation of `dd-agent/integrations-core.nix`
+ # for detailed information on this.
+ datadogPkg = cfg.package.override {
+ pythonPackages = pkgs.datadog-integrations-core cfg.extraIntegrations;
+ };
+in {
+ options.services.datadog-agent = {
+ enable = mkOption {
+ description = ''
+ Whether to enable the datadog-agent v6 monitoring service
+ '';
+ default = false;
+ type = types.bool;
+ };
+
+ package = mkOption {
+ default = pkgs.datadog-agent;
+ defaultText = "pkgs.datadog-agent";
+ description = ''
+ Which DataDog v6 agent package to use. Note that the provided
+ package is expected to have an overridable `pythonPackages`-attribute
+ which configures the Python environment with the Datadog
+ checks.
+ '';
+ type = types.package;
+ };
+
+ apiKeyFile = mkOption {
+ description = ''
+ Path to a file containing the Datadog API key to associate the
+ agent with your account.
+ '';
+ example = "/run/keys/datadog_api_key";
+ type = types.path;
+ };
+
+ tags = mkOption {
+ description = "The tags to mark this Datadog agent";
+ example = [ "test" "service" ];
+ default = null;
+ type = types.nullOr (types.listOf types.str);
+ };
+
+ hostname = mkOption {
+ description = "The hostname to show in the Datadog dashboard (optional)";
+ default = null;
+ example = "mymachine.mydomain";
+ type = types.nullOr types.str;
+ };
+
+ logLevel = mkOption {
+ description = "Logging verbosity.";
+ default = null;
+ type = types.nullOr (types.enum ["DEBUG" "INFO" "WARN" "ERROR"]);
+ };
+
+ extraIntegrations = mkOption {
+ default = {};
+ type = types.attrs;
+
+ description = ''
+ Extra integrations from the Datadog core-integrations
+ repository that should be built and included.
+
+ By default the included integrations are disk, mongo, network,
+ nginx and postgres.
+
+ To include additional integrations the name of the derivation
+ and a function to filter its dependencies from the Python
+ package set must be provided.
+ '';
+
+ example = {
+ ntp = (pythonPackages: [ pythonPackages.ntplib ]);
+ };
+ };
+
+ extraConfig = mkOption {
+ default = {};
+ type = types.attrs;
+ description = ''
+ Extra configuration options that will be merged into the
+ main config file <filename>datadog.yaml</filename>.
+ '';
+ };
+
+ enableLiveProcessCollection = mkOption {
+ description = ''
+ Whether to enable the live process collection agent.
+ '';
+ default = false;
+ type = types.bool;
+ };
+
+ enableTraceAgent = mkOption {
+ description = ''
+ Whether to enable the trace agent.
+ '';
+ default = false;
+ type = types.bool;
+ };
+
+ checks = mkOption {
+ description = ''
+ Configuration for all Datadog checks. Keys of this attribute
+ set will be used as the name of the check to create the
+ appropriate configuration in `conf.d/$check.d/conf.yaml`.
+
+ The configuration is converted into JSON from the plain Nix
+ language configuration, meaning that you should write
+ configuration adhering to Datadog's documentation - but in Nix
+ language.
+
+ Refer to the implementation of this module (specifically the
+ definition of `defaultChecks`) for an example.
+
+ Note: The 'disk' and 'network' check are configured in
+ separate options because they exist by default. Attempting to
+ override their configuration here will have no effect.
+ '';
+
+ example = {
+ http_check = {
+ init_config = null; # sic!
+ instances = [
+ {
+ name = "some-service";
+ url = "http://localhost:1337/healthz";
+ tags = [ "some-service" ];
+ }
+ ];
+ };
+ };
+
+ default = {};
+
+ # sic! The structure of the values is up to the check, so we can
+ # not usefully constrain the type further.
+ type = with types; attrsOf attrs;
+ };
+
+ diskCheck = mkOption {
+ description = "Disk check config";
+ type = types.attrs;
+ default = {
+ init_config = {};
+ instances = [ { use_mount = "false"; } ];
+ };
+ };
+
+ networkCheck = mkOption {
+ description = "Network check config";
+ type = types.attrs;
+ default = {
+ init_config = {};
+ # Network check only supports one configured instance
+ instances = [ { collect_connection_state = false;
+ excluded_interfaces = [ "lo" "lo0" ]; } ];
+ };
+ };
+ };
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ datadogPkg pkgs.sysstat pkgs.procps pkgs.iproute ];
+
+ users.extraUsers.datadog = {
+ description = "Datadog Agent User";
+ uid = config.ids.uids.datadog;
+ group = "datadog";
+ home = "/var/log/datadog/";
+ createHome = true;
+ };
+
+ users.extraGroups.datadog.gid = config.ids.gids.datadog;
+
+ systemd.services = let
+ makeService = attrs: recursiveUpdate {
+ path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps pkgs.iproute ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "datadog";
+ Group = "datadog";
+ Restart = "always";
+ RestartSec = 2;
+ };
+ restartTriggers = [ datadogPkg ] ++ map (etc: etc.source) etcfiles;
+ } attrs;
+ in {
+ datadog-agent = makeService {
+ description = "Datadog agent monitor";
+ preStart = ''
+ chown -R datadog: /etc/datadog-agent
+ rm -f /etc/datadog-agent/auth_token
+ '';
+ script = ''
+ export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
+ exec ${datadogPkg}/bin/agent run -c /etc/datadog-agent/datadog.yaml
+ '';
+ serviceConfig.PermissionsStartOnly = true;
+ };
+
+ dd-jmxfetch = lib.mkIf (lib.hasAttr "jmx" cfg.checks) (makeService {
+ description = "Datadog JMX Fetcher";
+ path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps pkgs.jdk ];
+ serviceConfig.ExecStart = "${datadogPkg}/bin/dd-jmxfetch";
+ });
+
+ datadog-process-agent = lib.mkIf cfg.enableLiveProcessCollection (makeService {
+ description = "Datadog Live Process Agent";
+ path = [ ];
+ script = ''
+ export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
+ ${pkgs.datadog-process-agent}/bin/agent --config /etc/datadog-agent/datadog.yaml
+ '';
+ });
+
+ datadog-trace-agent = lib.mkIf cfg.enableTraceAgent (makeService {
+ description = "Datadog Trace Agent";
+ path = [ ];
+ script = ''
+ export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
+ ${datadogPkg}/bin/trace-agent -config /etc/datadog-agent/datadog.yaml
+ '';
+ });
+
+ };
+
+ environment.etc = etcfiles;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix b/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix
new file mode 100644
index 00000000000..04512819742
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix
@@ -0,0 +1,8 @@
+# Generated using update-dd-agent-default, please re-run after updating dd-agent. DO NOT EDIT MANUALLY.
+[
+ "auto_conf"
+ "agent_metrics.yaml.default"
+ "disk.yaml.default"
+ "network.yaml.default"
+ "ntp.yaml.default"
+]
diff --git a/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix b/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
new file mode 100644
index 00000000000..5ee6b092a6a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
@@ -0,0 +1,238 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dd-agent;
+
+ ddConf = pkgs.writeText "datadog.conf" ''
+ [Main]
+ dd_url: https://app.datadoghq.com
+ skip_ssl_validation: no
+ api_key: ${cfg.api_key}
+ ${optionalString (cfg.hostname != null) "hostname: ${cfg.hostname}"}
+
+ collector_log_file: /var/log/datadog/collector.log
+ forwarder_log_file: /var/log/datadog/forwarder.log
+ dogstatsd_log_file: /var/log/datadog/dogstatsd.log
+ pup_log_file: /var/log/datadog/pup.log
+
+ # proxy_host: my-proxy.com
+ # proxy_port: 3128
+ # proxy_user: user
+ # proxy_password: password
+
+ # tags: mytag0, mytag1
+ ${optionalString (cfg.tags != null ) "tags: ${concatStringsSep ", " cfg.tags }"}
+
+ # collect_ec2_tags: no
+ # recent_point_threshold: 30
+ # use_mount: no
+ # listen_port: 17123
+ # graphite_listen_port: 17124
+ # non_local_traffic: no
+ # use_curl_http_client: False
+ # bind_host: localhost
+
+ # use_pup: no
+ # pup_port: 17125
+ # pup_interface: localhost
+ # pup_url: http://localhost:17125
+
+ # dogstatsd_port : 8125
+ # dogstatsd_interval : 10
+ # dogstatsd_normalize : yes
+ # statsd_forward_host: address_of_own_statsd_server
+ # statsd_forward_port: 8125
+
+ # device_blacklist_re: .*\/dev\/mapper\/lxc-box.*
+
+ # ganglia_host: localhost
+ # ganglia_port: 8651
+ '';
+
+ diskConfig = pkgs.writeText "disk.yaml" ''
+ init_config:
+
+ instances:
+ - use_mount: no
+ '';
+
+ networkConfig = pkgs.writeText "network.yaml" ''
+ init_config:
+
+ instances:
+ # Network check only supports one configured instance
+ - collect_connection_state: false
+ excluded_interfaces:
+ - lo
+ - lo0
+ '';
+
+ postgresqlConfig = pkgs.writeText "postgres.yaml" cfg.postgresqlConfig;
+ nginxConfig = pkgs.writeText "nginx.yaml" cfg.nginxConfig;
+ mongoConfig = pkgs.writeText "mongo.yaml" cfg.mongoConfig;
+ jmxConfig = pkgs.writeText "jmx.yaml" cfg.jmxConfig;
+ processConfig = pkgs.writeText "process.yaml" cfg.processConfig;
+
+ etcfiles =
+ let
+ defaultConfd = import ./dd-agent-defaults.nix;
+ in (map (f: { source = "${pkgs.dd-agent}/agent/conf.d-system/${f}";
+ target = "dd-agent/conf.d/${f}";
+ }) defaultConfd) ++ [
+ { source = ddConf;
+ target = "dd-agent/datadog.conf";
+ }
+ { source = diskConfig;
+ target = "dd-agent/conf.d/disk.yaml";
+ }
+ { source = networkConfig;
+ target = "dd-agent/conf.d/network.yaml";
+ } ] ++
+ (optional (cfg.postgresqlConfig != null)
+ { source = postgresqlConfig;
+ target = "dd-agent/conf.d/postgres.yaml";
+ }) ++
+ (optional (cfg.nginxConfig != null)
+ { source = nginxConfig;
+ target = "dd-agent/conf.d/nginx.yaml";
+ }) ++
+ (optional (cfg.mongoConfig != null)
+ { source = mongoConfig;
+ target = "dd-agent/conf.d/mongo.yaml";
+ }) ++
+ (optional (cfg.processConfig != null)
+ { source = processConfig;
+ target = "dd-agent/conf.d/process.yaml";
+ }) ++
+ (optional (cfg.jmxConfig != null)
+ { source = jmxConfig;
+ target = "dd-agent/conf.d/jmx.yaml";
+ });
+
+in {
+ options.services.dd-agent = {
+ enable = mkOption {
+ description = ''
+ Whether to enable the dd-agent v5 monitoring service.
+ For datadog-agent v6, see <option>services.datadog-agent.enable</option>.
+ '';
+ default = false;
+ type = types.bool;
+ };
+
+ api_key = mkOption {
+ description = ''
+ The Datadog API key to associate the agent with your account.
+
+ Warning: this key is stored in cleartext within the world-readable
+ Nix store! Consider using the new v6
+ <option>services.datadog-agent</option> module instead.
+ '';
+ example = "ae0aa6a8f08efa988ba0a17578f009ab";
+ type = types.str;
+ };
+
+ tags = mkOption {
+ description = "The tags to mark this Datadog agent";
+ example = [ "test" "service" ];
+ default = null;
+ type = types.nullOr (types.listOf types.str);
+ };
+
+ hostname = mkOption {
+ description = "The hostname to show in the Datadog dashboard (optional)";
+ default = null;
+ example = "mymachine.mydomain";
+ type = types.nullOr types.str;
+ };
+
+ postgresqlConfig = mkOption {
+ description = "Datadog PostgreSQL integration configuration";
+ default = null;
+ type = types.nullOr types.lines;
+ };
+
+ nginxConfig = mkOption {
+ description = "Datadog nginx integration configuration";
+ default = null;
+ type = types.nullOr types.lines;
+ };
+
+ mongoConfig = mkOption {
+ description = "MongoDB integration configuration";
+ default = null;
+ type = types.nullOr types.lines;
+ };
+
+ jmxConfig = mkOption {
+ description = "JMX integration configuration";
+ default = null;
+ type = types.nullOr types.lines;
+ };
+
+ processConfig = mkOption {
+ description = ''
+ Process integration configuration
+ See <link xlink:href="https://docs.datadoghq.com/integrations/process/"/>
+ '';
+ default = null;
+ type = types.nullOr types.lines;
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.dd-agent pkgs.sysstat pkgs.procps ];
+
+ users.users.datadog = {
+ description = "Datadog Agent User";
+ uid = config.ids.uids.datadog;
+ group = "datadog";
+ home = "/var/log/datadog/";
+ createHome = true;
+ };
+
+ users.groups.datadog.gid = config.ids.gids.datadog;
+
+ systemd.services = let
+ makeService = attrs: recursiveUpdate {
+ path = [ pkgs.dd-agent pkgs.python pkgs.sysstat pkgs.procps pkgs.gohai ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "datadog";
+ Group = "datadog";
+ Restart = "always";
+ RestartSec = 2;
+ PrivateTmp = true;
+ };
+ restartTriggers = [ pkgs.dd-agent ddConf diskConfig networkConfig postgresqlConfig nginxConfig mongoConfig jmxConfig processConfig ];
+ } attrs;
+ in {
+ dd-agent = makeService {
+ description = "Datadog agent monitor";
+ serviceConfig.ExecStart = "${pkgs.dd-agent}/bin/dd-agent foreground";
+ };
+
+ dogstatsd = makeService {
+ description = "Datadog statsd";
+ environment.TMPDIR = "/run/dogstatsd";
+ serviceConfig = {
+ ExecStart = "${pkgs.dd-agent}/bin/dogstatsd start";
+ Type = "forking";
+ PIDFile = "/run/dogstatsd/dogstatsd.pid";
+ RuntimeDirectory = "dogstatsd";
+ };
+ };
+
+ dd-jmxfetch = lib.mkIf (cfg.jmxConfig != null) {
+ description = "Datadog JMX Fetcher";
+ path = [ pkgs.dd-agent pkgs.python pkgs.sysstat pkgs.procps pkgs.jdk ];
+ serviceConfig.ExecStart = "${pkgs.dd-agent}/bin/dd-jmxfetch";
+ };
+ };
+
+ environment.etc = etcfiles;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults b/nixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults
new file mode 100755
index 00000000000..76724173171
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+dd=$(nix-build --no-out-link -A dd-agent ../../../..)
+echo '# Generated using update-dd-agent-default, please re-run after updating dd-agent. DO NOT EDIT MANUALLY.' > dd-agent-defaults.nix
+echo '[' >> dd-agent-defaults.nix
+echo ' "auto_conf"' >> dd-agent-defaults.nix
+for f in $(find $dd/agent/conf.d-system -maxdepth 1 -type f | grep -v '\.example' | sort); do
+ echo " \"$(basename $f)\"" >> dd-agent-defaults.nix
+done
+echo ']' >> dd-agent-defaults.nix
diff --git a/nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix b/nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix
new file mode 100644
index 00000000000..b90579bb70c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix
@@ -0,0 +1,63 @@
+# Fusion Inventory daemon.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.fusionInventory;
+
+ configFile = pkgs.writeText "fusion_inventory.conf" ''
+ server = ${concatStringsSep ", " cfg.servers}
+
+ logger = stderr
+
+ ${cfg.extraConfig}
+ '';
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.fusionInventory = {
+
+ enable = mkEnableOption "Fusion Inventory Agent";
+
+ servers = mkOption {
+ type = types.listOf types.str;
+ description = ''
+ The urls of the OCS/GLPI servers to connect to.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Configuration that is injected verbatim into the configuration file.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton {
+ name = "fusion-inventory";
+ description = "FusionInventory user";
+ };
+
+ systemd.services.fusion-inventory = {
+ description = "Fusion Inventory Agent";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.fusionInventory}/bin/fusioninventory-agent --conf-file=${configFile} --daemon --no-fork";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix b/nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix
new file mode 100644
index 00000000000..b5a78e4583e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.grafana_reporter;
+
+in {
+ options.services.grafana_reporter = {
+ enable = mkEnableOption "grafana_reporter";
+
+ grafana = {
+ protocol = mkOption {
+ description = "Grafana protocol.";
+ default = "http";
+ type = types.enum ["http" "https"];
+ };
+ addr = mkOption {
+ description = "Grafana address.";
+ default = "127.0.0.1";
+ type = types.str;
+ };
+ port = mkOption {
+ description = "Grafana port.";
+ default = 3000;
+ type = types.int;
+ };
+
+ };
+ addr = mkOption {
+ description = "Listening address.";
+ default = "127.0.0.1";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Listening port.";
+ default = 8686;
+ type = types.int;
+ };
+
+ templateDir = mkOption {
+ description = "Optional template directory to use custom tex templates";
+ default = "${pkgs.grafana_reporter}";
+ type = types.str;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.grafana_reporter = {
+ description = "Grafana Reporter Service Daemon";
+ wantedBy = ["multi-user.target"];
+ after = ["network.target"];
+ serviceConfig = let
+ args = lib.concatStringsSep " " [
+ "-proto ${cfg.grafana.protocol}://"
+ "-ip ${cfg.grafana.addr}:${toString cfg.grafana.port}"
+ "-port :${toString cfg.port}"
+ "-templates ${cfg.templateDir}"
+ ];
+ in {
+ ExecStart = "${pkgs.grafana_reporter.bin}/bin/grafana-reporter ${args}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana.nix b/nixpkgs/nixos/modules/services/monitoring/grafana.nix
new file mode 100644
index 00000000000..bf1084eecc3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/grafana.nix
@@ -0,0 +1,559 @@
+{ options, config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.grafana;
+ opt = options.services.grafana;
+
+ envOptions = {
+ PATHS_DATA = cfg.dataDir;
+ PATHS_PLUGINS = "${cfg.dataDir}/plugins";
+ PATHS_LOGS = "${cfg.dataDir}/log";
+
+ SERVER_PROTOCOL = cfg.protocol;
+ SERVER_HTTP_ADDR = cfg.addr;
+ SERVER_HTTP_PORT = cfg.port;
+ SERVER_DOMAIN = cfg.domain;
+ SERVER_ROOT_URL = cfg.rootUrl;
+ SERVER_STATIC_ROOT_PATH = cfg.staticRootPath;
+ SERVER_CERT_FILE = cfg.certFile;
+ SERVER_CERT_KEY = cfg.certKey;
+
+ DATABASE_TYPE = cfg.database.type;
+ DATABASE_HOST = cfg.database.host;
+ DATABASE_NAME = cfg.database.name;
+ DATABASE_USER = cfg.database.user;
+ DATABASE_PASSWORD = cfg.database.password;
+ DATABASE_PATH = cfg.database.path;
+ DATABASE_CONN_MAX_LIFETIME = cfg.database.connMaxLifetime;
+
+ SECURITY_ADMIN_USER = cfg.security.adminUser;
+ SECURITY_ADMIN_PASSWORD = cfg.security.adminPassword;
+ SECURITY_SECRET_KEY = cfg.security.secretKey;
+
+ USERS_ALLOW_SIGN_UP = boolToString cfg.users.allowSignUp;
+ USERS_ALLOW_ORG_CREATE = boolToString cfg.users.allowOrgCreate;
+ USERS_AUTO_ASSIGN_ORG = boolToString cfg.users.autoAssignOrg;
+ USERS_AUTO_ASSIGN_ORG_ROLE = cfg.users.autoAssignOrgRole;
+
+ AUTH_ANONYMOUS_ENABLED = boolToString cfg.auth.anonymous.enable;
+ AUTH_ANONYMOUS_ORG_NAME = cfg.auth.anonymous.org_name;
+ AUTH_ANONYMOUS_ORG_ROLE = cfg.auth.anonymous.org_role;
+
+ ANALYTICS_REPORTING_ENABLED = boolToString cfg.analytics.reporting.enable;
+
+ SMTP_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 specified folder";
+ };
+ type = mkOption {
+ type = types.str;
+ default = "file";
+ description = "Dashboard provider type";
+ };
+ disableDeletion = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Disable deletion when JSON file is removed";
+ };
+ updateIntervalSeconds = mkOption {
+ type = types.int;
+ default = 10;
+ description = "How often Grafana will scan for changed dashboards";
+ };
+ options = {
+ path = mkOption {
+ type = types.path;
+ description = "Path grafana will watch for dashboards";
+ };
+ };
+ };
+ };
+in {
+ options.services.grafana = {
+ enable = mkEnableOption "grafana";
+
+ protocol = mkOption {
+ description = "Which protocol to listen.";
+ default = "http";
+ type = types.enum ["http" "https" "socket"];
+ };
+
+ addr = mkOption {
+ description = "Listening address.";
+ default = "127.0.0.1";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Listening port.";
+ default = 3000;
+ type = types.int;
+ };
+
+ domain = mkOption {
+ description = "The public facing domain name used to access grafana from a browser.";
+ default = "localhost";
+ type = types.str;
+ };
+
+ rootUrl = mkOption {
+ description = "Full public facing url.";
+ default = "%(protocol)s://%(domain)s:%(http_port)s/";
+ type = types.str;
+ };
+
+ certFile = mkOption {
+ description = "Cert file for ssl.";
+ default = "";
+ type = types.str;
+ };
+
+ certKey = mkOption {
+ description = "Cert key for ssl.";
+ default = "";
+ type = types.str;
+ };
+
+ staticRootPath = mkOption {
+ description = "Root path for static assets.";
+ default = "${cfg.package}/share/grafana/public";
+ type = types.str;
+ };
+
+ package = mkOption {
+ description = "Package to use.";
+ default = pkgs.grafana;
+ defaultText = "pkgs.grafana";
+ type = types.package;
+ };
+
+ dataDir = mkOption {
+ description = "Data directory.";
+ default = "/var/lib/grafana";
+ type = types.path;
+ };
+
+ database = {
+ type = mkOption {
+ description = "Database type.";
+ default = "sqlite3";
+ type = types.enum ["mysql" "sqlite3" "postgres"];
+ };
+
+ host = mkOption {
+ description = "Database host.";
+ default = "127.0.0.1:3306";
+ type = types.str;
+ };
+
+ name = mkOption {
+ description = "Database name.";
+ default = "grafana";
+ type = types.str;
+ };
+
+ user = mkOption {
+ description = "Database user.";
+ default = "root";
+ type = types.str;
+ };
+
+ password = mkOption {
+ description = ''
+ Database password.
+ This option is mutual exclusive with the passwordFile option.
+ '';
+ default = "";
+ type = types.str;
+ };
+
+ passwordFile = mkOption {
+ description = ''
+ File that containts the database password.
+ This option is mutual exclusive with the password option.
+ '';
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ path = mkOption {
+ description = "Database path.";
+ default = "${cfg.dataDir}/data/grafana.db";
+ type = types.path;
+ };
+
+ connMaxLifetime = mkOption {
+ description = ''
+ Sets the maximum amount of time (in seconds) a connection may be reused.
+ For MySQL this setting should be shorter than the `wait_timeout' variable.
+ '';
+ default = "unlimited";
+ example = 14400;
+ type = types.either types.int (types.enum [ "unlimited" ]);
+ };
+ };
+
+ provision = {
+ enable = mkEnableOption "provision";
+ datasources = mkOption {
+ description = "Grafana datasources configuration";
+ default = [];
+ type = types.listOf grafanaTypes.datasourceConfig;
+ apply = x: map _filter x;
+ };
+ dashboards = mkOption {
+ description = "Grafana dashboard configuration";
+ default = [];
+ type = types.listOf grafanaTypes.dashboardConfig;
+ apply = x: map _filter x;
+ };
+ };
+
+ security = {
+ adminUser = mkOption {
+ description = "Default admin username.";
+ default = "admin";
+ type = types.str;
+ };
+
+ adminPassword = mkOption {
+ description = ''
+ Default admin password.
+ This option is mutual exclusive with the adminPasswordFile option.
+ '';
+ default = "admin";
+ type = types.str;
+ };
+
+ adminPasswordFile = mkOption {
+ description = ''
+ Default admin password.
+ This option is mutual exclusive with the <literal>adminPassword</literal> option.
+ '';
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ secretKey = mkOption {
+ description = "Secret key used for signing.";
+ default = "SW2YcwTIb9zpOOhoPsMm";
+ type = types.str;
+ };
+
+ secretKeyFile = mkOption {
+ description = "Secret key used for signing.";
+ default = null;
+ type = types.nullOr types.path;
+ };
+ };
+
+ smtp = {
+ enable = mkEnableOption "smtp";
+ host = mkOption {
+ description = "Host to connect to";
+ default = "localhost:25";
+ type = types.str;
+ };
+ user = mkOption {
+ description = "User used for authentication";
+ default = "";
+ type = types.str;
+ };
+ password = mkOption {
+ description = ''
+ Password used for authentication.
+ This option is mutual exclusive with the passwordFile option.
+ '';
+ default = "";
+ type = types.str;
+ };
+ passwordFile = mkOption {
+ description = ''
+ Password used for authentication.
+ This option is mutual exclusive with the password option.
+ '';
+ default = null;
+ type = types.nullOr types.path;
+ };
+ fromAddress = mkOption {
+ description = "Email address used for sending";
+ default = "admin@grafana.localhost";
+ type = types.str;
+ };
+ };
+
+ users = {
+ allowSignUp = mkOption {
+ description = "Disable user signup / registration";
+ default = false;
+ type = types.bool;
+ };
+
+ allowOrgCreate = mkOption {
+ description = "Whether user is allowed to create organizations.";
+ default = false;
+ type = types.bool;
+ };
+
+ autoAssignOrg = mkOption {
+ description = "Whether to automatically assign new users to default org.";
+ default = true;
+ type = types.bool;
+ };
+
+ autoAssignOrgRole = mkOption {
+ description = "Default role new users will be auto assigned.";
+ default = "Viewer";
+ type = types.enum ["Viewer" "Editor"];
+ };
+ };
+
+ auth.anonymous = {
+ enable = mkOption {
+ description = "Whether to allow anonymous access";
+ default = false;
+ type = types.bool;
+ };
+ org_name = mkOption {
+ description = "Which organization to allow anonymous access to";
+ default = "Main Org.";
+ type = types.str;
+ };
+ org_role = mkOption {
+ description = "Which role anonymous users have in the organization";
+ default = "Viewer";
+ type = types.str;
+ };
+
+ };
+
+ analytics.reporting = {
+ enable = mkOption {
+ description = "Whether to allow anonymous usage reporting to stats.grafana.net";
+ default = true;
+ type = types.bool;
+ };
+ };
+
+ extraOptions = mkOption {
+ description = ''
+ Extra configuration options passed as env variables as specified in
+ <link xlink:href="http://docs.grafana.org/installation/configuration/">documentation</link>,
+ but without GF_ prefix
+ '';
+ default = {};
+ type = with types; attrsOf (either str path);
+ };
+ };
+
+ config = mkIf cfg.enable {
+ warnings = flatten [
+ (optional (
+ cfg.database.password != opt.database.password.default ||
+ cfg.security.adminPassword != opt.security.adminPassword.default
+ ) "Grafana passwords will be stored as plaintext in the Nix store!")
+ (optional (
+ any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) cfg.provision.datasources
+ ) "Datasource passwords will be stored as plaintext in the Nix store!")
+ ];
+
+ environment.systemPackages = [ cfg.package ];
+
+ assertions = [
+ {
+ assertion = cfg.database.password != opt.database.password.default -> cfg.database.passwordFile == null;
+ message = "Cannot set both password and passwordFile";
+ }
+ {
+ assertion = cfg.security.adminPassword != opt.security.adminPassword.default -> cfg.security.adminPasswordFile == null;
+ message = "Cannot set both adminPassword and adminPasswordFile";
+ }
+ {
+ assertion = cfg.security.secretKey != opt.security.secretKey.default -> cfg.security.secretKeyFile == null;
+ message = "Cannot set both secretKey and secretKeyFile";
+ }
+ {
+ assertion = cfg.smtp.password != opt.smtp.password.default -> cfg.smtp.passwordFile == null;
+ message = "Cannot set both password and passwordFile";
+ }
+ ];
+
+ systemd.services.grafana = {
+ description = "Grafana Service Daemon";
+ wantedBy = ["multi-user.target"];
+ after = ["networking.target"];
+ environment = {
+ QT_QPA_PLATFORM = "offscreen";
+ } // mapAttrs' (n: v: nameValuePair "GF_${n}" (toString v)) envOptions;
+ script = ''
+ ${optionalString (cfg.database.passwordFile != null) ''
+ export GF_DATABASE_PASSWORD="$(cat ${escapeShellArg cfg.database.passwordFile})"
+ ''}
+ ${optionalString (cfg.security.adminPasswordFile != null) ''
+ export GF_SECURITY_ADMIN_PASSWORD="$(cat ${escapeShellArg cfg.security.adminPasswordFile})"
+ ''}
+ ${optionalString (cfg.security.secretKeyFile != null) ''
+ export GF_SECURITY_SECRET_KEY="$(cat ${escapeShellArg cfg.security.secretKeyFile})"
+ ''}
+ ${optionalString (cfg.smtp.passwordFile != null) ''
+ export GF_SMTP_PASSWORD="$(cat ${escapeShellArg cfg.smtp.passwordFile})"
+ ''}
+ ${optionalString cfg.provision.enable ''
+ export GF_PATHS_PROVISIONING=${provisionConfDir};
+ ''}
+ exec ${cfg.package.bin}/bin/grafana-server -homepath ${cfg.dataDir}
+ '';
+ serviceConfig = {
+ WorkingDirectory = cfg.dataDir;
+ User = "grafana";
+ };
+ preStart = ''
+ ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir}
+ ln -fs ${cfg.package}/share/grafana/tools ${cfg.dataDir}
+ '';
+ };
+
+ users.users.grafana = {
+ uid = config.ids.uids.grafana;
+ description = "Grafana user";
+ home = cfg.dataDir;
+ createHome = true;
+ group = "grafana";
+ };
+ users.groups.grafana = {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/graphite.nix b/nixpkgs/nixos/modules/services/monitoring/graphite.nix
new file mode 100644
index 00000000000..f7874af3df2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/graphite.nix
@@ -0,0 +1,644 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.graphite;
+ writeTextOrNull = f: t: mapNullable (pkgs.writeTextDir f) t;
+
+ dataDir = cfg.dataDir;
+ staticDir = cfg.dataDir + "/static";
+
+ graphiteLocalSettingsDir = pkgs.runCommand "graphite_local_settings" {
+ inherit graphiteLocalSettings;
+ preferLocalBuild = true;
+ } ''
+ mkdir -p $out
+ ln -s $graphiteLocalSettings $out/graphite_local_settings.py
+ '';
+
+ graphiteLocalSettings = pkgs.writeText "graphite_local_settings.py" (
+ "STATIC_ROOT = '${staticDir}'\n" +
+ optionalString (config.time.timeZone != null) "TIME_ZONE = '${config.time.timeZone}'\n"
+ + cfg.web.extraConfig
+ );
+
+ graphiteApiConfig = pkgs.writeText "graphite-api.yaml" ''
+ search_index: ${dataDir}/index
+ ${optionalString (config.time.timeZone != null) ''time_zone: ${config.time.timeZone}''}
+ ${optionalString (cfg.api.finders != []) ''finders:''}
+ ${concatMapStringsSep "\n" (f: " - " + f.moduleName) cfg.api.finders}
+ ${optionalString (cfg.api.functions != []) ''functions:''}
+ ${concatMapStringsSep "\n" (f: " - " + f) cfg.api.functions}
+ ${cfg.api.extraConfig}
+ '';
+
+ seyrenConfig = {
+ SEYREN_URL = cfg.seyren.seyrenUrl;
+ MONGO_URL = cfg.seyren.mongoUrl;
+ GRAPHITE_URL = cfg.seyren.graphiteUrl;
+ } // cfg.seyren.extraConfig;
+
+ pagerConfig = pkgs.writeText "alarms.yaml" cfg.pager.alerts;
+
+ configDir = pkgs.buildEnv {
+ name = "graphite-config";
+ paths = lists.filter (el: el != null) [
+ (writeTextOrNull "carbon.conf" cfg.carbon.config)
+ (writeTextOrNull "storage-aggregation.conf" cfg.carbon.storageAggregation)
+ (writeTextOrNull "storage-schemas.conf" cfg.carbon.storageSchemas)
+ (writeTextOrNull "blacklist.conf" cfg.carbon.blacklist)
+ (writeTextOrNull "whitelist.conf" cfg.carbon.whitelist)
+ (writeTextOrNull "rewrite-rules.conf" cfg.carbon.rewriteRules)
+ (writeTextOrNull "relay-rules.conf" cfg.carbon.relayRules)
+ (writeTextOrNull "aggregation-rules.conf" cfg.carbon.aggregationRules)
+ ];
+ };
+
+ carbonOpts = name: with config.ids; ''
+ --nodaemon --syslog --prefix=${name} --pidfile /run/${name}/${name}.pid ${name}
+ '';
+
+ carbonEnv = {
+ PYTHONPATH = let
+ cenv = pkgs.python.buildEnv.override {
+ extraLibs = [ pkgs.python27Packages.carbon ];
+ };
+ cenvPack = "${cenv}/${pkgs.python.sitePackages}";
+ # opt/graphite/lib contains twisted.plugins.carbon-cache
+ in "${cenvPack}/opt/graphite/lib:${cenvPack}";
+ GRAPHITE_ROOT = dataDir;
+ GRAPHITE_CONF_DIR = configDir;
+ GRAPHITE_STORAGE_DIR = dataDir;
+ };
+
+in {
+
+ ###### interface
+
+ options.services.graphite = {
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/db/graphite";
+ description = ''
+ Data directory for graphite.
+ '';
+ };
+
+ web = {
+ enable = mkOption {
+ description = "Whether to enable graphite web frontend.";
+ default = false;
+ type = types.bool;
+ };
+
+ listenAddress = mkOption {
+ description = "Graphite web frontend listen address.";
+ default = "127.0.0.1";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Graphite web frontend port.";
+ default = 8080;
+ type = types.int;
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Graphite webapp settings. See:
+ <link xlink:href="http://graphite.readthedocs.io/en/latest/config-local-settings.html"/>
+ '';
+ };
+ };
+
+ api = {
+ enable = mkOption {
+ description = ''
+ Whether to enable graphite api. Graphite api is lightweight alternative
+ to graphite web, with api and without dashboard. It's advised to use
+ grafana as alternative dashboard and influxdb as alternative to
+ graphite carbon.
+
+ For more information visit
+ <link xlink:href="https://graphite-api.readthedocs.org/en/latest/"/>
+ '';
+ default = false;
+ type = types.bool;
+ };
+
+ finders = mkOption {
+ description = "List of finder plugins to load.";
+ default = [];
+ example = literalExample "[ pkgs.python27Packages.influxgraph ]";
+ type = types.listOf types.package;
+ };
+
+ functions = mkOption {
+ description = "List of functions to load.";
+ default = [
+ "graphite_api.functions.SeriesFunctions"
+ "graphite_api.functions.PieFunctions"
+ ];
+ type = types.listOf types.str;
+ };
+
+ listenAddress = mkOption {
+ description = "Graphite web service listen address.";
+ default = "127.0.0.1";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Graphite api service port.";
+ default = 8080;
+ type = types.int;
+ };
+
+ package = mkOption {
+ description = "Package to use for graphite api.";
+ default = pkgs.python27Packages.graphite_api;
+ defaultText = "pkgs.python27Packages.graphite_api";
+ type = types.package;
+ };
+
+ extraConfig = mkOption {
+ description = "Extra configuration for graphite api.";
+ default = ''
+ whisper:
+ directories:
+ - ${dataDir}/whisper
+ '';
+ example = ''
+ allowed_origins:
+ - dashboard.example.com
+ cheat_times: true
+ influxdb:
+ host: localhost
+ port: 8086
+ user: influxdb
+ pass: influxdb
+ db: metrics
+ cache:
+ CACHE_TYPE: 'filesystem'
+ CACHE_DIR: '/tmp/graphite-api-cache'
+ '';
+ type = types.lines;
+ };
+ };
+
+ carbon = {
+ config = mkOption {
+ description = "Content of carbon configuration file.";
+ default = ''
+ [cache]
+ # Listen on localhost by default for security reasons
+ UDP_RECEIVER_INTERFACE = 127.0.0.1
+ PICKLE_RECEIVER_INTERFACE = 127.0.0.1
+ LINE_RECEIVER_INTERFACE = 127.0.0.1
+ CACHE_QUERY_INTERFACE = 127.0.0.1
+ # Do not log every update
+ LOG_UPDATES = False
+ LOG_CACHE_HITS = False
+ '';
+ type = types.str;
+ };
+
+ enableCache = mkOption {
+ description = "Whether to enable carbon cache, the graphite storage daemon.";
+ default = false;
+ type = types.bool;
+ };
+
+ storageAggregation = mkOption {
+ description = "Defines how to aggregate data to lower-precision retentions.";
+ default = null;
+ type = types.nullOr types.str;
+ example = ''
+ [all_min]
+ pattern = \.min$
+ xFilesFactor = 0.1
+ aggregationMethod = min
+ '';
+ };
+
+ storageSchemas = mkOption {
+ description = "Defines retention rates for storing metrics.";
+ default = "";
+ type = types.nullOr types.str;
+ example = ''
+ [apache_busyWorkers]
+ pattern = ^servers\.www.*\.workers\.busyWorkers$
+ retentions = 15s:7d,1m:21d,15m:5y
+ '';
+ };
+
+ blacklist = mkOption {
+ description = "Any metrics received which match one of the experssions will be dropped.";
+ default = null;
+ type = types.nullOr types.str;
+ example = "^some\\.noisy\\.metric\\.prefix\\..*";
+ };
+
+ whitelist = mkOption {
+ description = "Only metrics received which match one of the experssions will be persisted.";
+ default = null;
+ type = types.nullOr types.str;
+ example = ".*";
+ };
+
+ rewriteRules = mkOption {
+ description = ''
+ Regular expression patterns that can be used to rewrite metric names
+ in a search and replace fashion.
+ '';
+ default = null;
+ type = types.nullOr types.str;
+ example = ''
+ [post]
+ _sum$ =
+ _avg$ =
+ '';
+ };
+
+ enableRelay = mkOption {
+ description = "Whether to enable carbon relay, the carbon replication and sharding service.";
+ default = false;
+ type = types.bool;
+ };
+
+ relayRules = mkOption {
+ description = "Relay rules are used to send certain metrics to a certain backend.";
+ default = null;
+ type = types.nullOr types.str;
+ example = ''
+ [example]
+ pattern = ^mydata\.foo\..+
+ servers = 10.1.2.3, 10.1.2.4:2004, myserver.mydomain.com
+ '';
+ };
+
+ enableAggregator = mkOption {
+ description = "Whether to enable carbon aggregator, the carbon buffering service.";
+ default = false;
+ type = types.bool;
+ };
+
+ aggregationRules = mkOption {
+ description = "Defines if and how received metrics will be aggregated.";
+ default = null;
+ type = types.nullOr types.str;
+ example = ''
+ <env>.applications.<app>.all.requests (60) = sum <env>.applications.<app>.*.requests
+ <env>.applications.<app>.all.latency (60) = avg <env>.applications.<app>.*.latency
+ '';
+ };
+ };
+
+ seyren = {
+ enable = mkOption {
+ description = "Whether to enable seyren service.";
+ default = false;
+ type = types.bool;
+ };
+
+ port = mkOption {
+ description = "Seyren listening port.";
+ default = 8081;
+ type = types.int;
+ };
+
+ seyrenUrl = mkOption {
+ default = "http://localhost:${toString cfg.seyren.port}/";
+ description = "Host where seyren is accessible.";
+ type = types.str;
+ };
+
+ graphiteUrl = mkOption {
+ default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}";
+ description = "Host where graphite service runs.";
+ type = types.str;
+ };
+
+ mongoUrl = mkOption {
+ default = "mongodb://${config.services.mongodb.bind_ip}:27017/seyren";
+ description = "Mongodb connection string.";
+ type = types.str;
+ };
+
+ extraConfig = mkOption {
+ default = {};
+ description = ''
+ Extra seyren configuration. See
+ <link xlink:href='https://github.com/scobal/seyren#config' />
+ '';
+ type = types.attrsOf types.str;
+ example = literalExample ''
+ {
+ GRAPHITE_USERNAME = "user";
+ GRAPHITE_PASSWORD = "pass";
+ }
+ '';
+ };
+ };
+
+ pager = {
+ enable = mkOption {
+ description = ''
+ Whether to enable graphite-pager service. For more information visit
+ <link xlink:href="https://github.com/seatgeek/graphite-pager"/>
+ '';
+ default = false;
+ type = types.bool;
+ };
+
+ redisUrl = mkOption {
+ description = "Redis connection string.";
+ default = "redis://localhost:${toString config.services.redis.port}/";
+ type = types.str;
+ };
+
+ graphiteUrl = mkOption {
+ description = "URL to your graphite service.";
+ default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}";
+ type = types.str;
+ };
+
+ alerts = mkOption {
+ description = "Alerts configuration for graphite-pager.";
+ default = ''
+ alerts:
+ - target: constantLine(100)
+ warning: 90
+ critical: 200
+ name: Test
+ '';
+ example = ''
+ pushbullet_key: pushbullet_api_key
+ alerts:
+ - target: stats.seatgeek.app.deal_quality.venue_info_cache.hit
+ warning: .5
+ critical: 1
+ name: Deal quality venue cache hits
+ '';
+ type = types.lines;
+ };
+ };
+
+ beacon = {
+ enable = mkEnableOption "graphite beacon";
+
+ config = mkOption {
+ description = "Graphite beacon configuration.";
+ default = {};
+ type = types.attrs;
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkMerge [
+ (mkIf cfg.carbon.enableCache {
+ systemd.services.carbonCache = let name = "carbon-cache"; in {
+ description = "Graphite Data Storage Backend";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment = carbonEnv;
+ serviceConfig = {
+ RuntimeDirectory = name;
+ ExecStart = "${pkgs.pythonPackages.twisted}/bin/twistd ${carbonOpts name}";
+ User = "graphite";
+ Group = "graphite";
+ PermissionsStartOnly = true;
+ PIDFile="/run/${name}/${name}.pid";
+ };
+ preStart = ''
+ install -dm0700 -o graphite -g graphite ${cfg.dataDir}
+ install -dm0700 -o graphite -g graphite ${cfg.dataDir}/whisper
+ '';
+ };
+ })
+
+ (mkIf cfg.carbon.enableAggregator {
+ systemd.services.carbonAggregator = let name = "carbon-aggregator"; in {
+ enable = cfg.carbon.enableAggregator;
+ description = "Carbon Data Aggregator";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment = carbonEnv;
+ serviceConfig = {
+ RuntimeDirectory = name;
+ ExecStart = "${pkgs.pythonPackages.twisted}/bin/twistd ${carbonOpts name}";
+ User = "graphite";
+ Group = "graphite";
+ PIDFile="/run/${name}/${name}.pid";
+ };
+ };
+ })
+
+ (mkIf cfg.carbon.enableRelay {
+ systemd.services.carbonRelay = let name = "carbon-relay"; in {
+ description = "Carbon Data Relay";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment = carbonEnv;
+ serviceConfig = {
+ RuntimeDirectory = name;
+ ExecStart = "${pkgs.pythonPackages.twisted}/bin/twistd ${carbonOpts name}";
+ User = "graphite";
+ Group = "graphite";
+ PIDFile="/run/${name}/${name}.pid";
+ };
+ };
+ })
+
+ (mkIf (cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay) {
+ environment.systemPackages = [
+ pkgs.pythonPackages.carbon
+ ];
+ })
+
+ (mkIf cfg.web.enable (let
+ python27' = pkgs.python27.override {
+ packageOverrides = self: super: {
+ django = self.django_1_8;
+ django_tagging = self.django_tagging_0_4_3;
+ };
+ };
+ pythonPackages = python27'.pkgs;
+ in {
+ systemd.services.graphiteWeb = {
+ description = "Graphite Web Interface";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = [ pkgs.perl ];
+ environment = {
+ PYTHONPATH = let
+ penv = pkgs.python.buildEnv.override {
+ extraLibs = [
+ pythonPackages.graphite-web
+ pythonPackages.pysqlite
+ ];
+ };
+ penvPack = "${penv}/${pkgs.python.sitePackages}";
+ in concatStringsSep ":" [
+ "${graphiteLocalSettingsDir}"
+ "${penvPack}/opt/graphite/webapp"
+ "${penvPack}"
+ # explicitly adding pycairo in path because it cannot be imported via buildEnv
+ "${pkgs.pythonPackages.pycairo}/${pkgs.python.sitePackages}"
+ ];
+ DJANGO_SETTINGS_MODULE = "graphite.settings";
+ GRAPHITE_CONF_DIR = configDir;
+ GRAPHITE_STORAGE_DIR = dataDir;
+ LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib";
+ };
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.python27Packages.waitress-django}/bin/waitress-serve-django \
+ --host=${cfg.web.listenAddress} --port=${toString cfg.web.port}
+ '';
+ User = "graphite";
+ Group = "graphite";
+ PermissionsStartOnly = true;
+ };
+ preStart = ''
+ if ! test -e ${dataDir}/db-created; then
+ mkdir -p ${dataDir}/{whisper/,log/webapp/}
+ chmod 0700 ${dataDir}/{whisper/,log/webapp/}
+
+ ${pkgs.pythonPackages.django_1_8}/bin/django-admin.py migrate --noinput
+
+ chown -R graphite:graphite ${dataDir}
+
+ touch ${dataDir}/db-created
+ fi
+
+ # Only collect static files when graphite_web changes.
+ if ! [ "${dataDir}/current_graphite_web" -ef "${pythonPackages.graphite-web}" ]; then
+ mkdir -p ${staticDir}
+ ${pkgs.pythonPackages.django_1_8}/bin/django-admin.py collectstatic --noinput --clear
+ chown -R graphite:graphite ${staticDir}
+ ln -sfT "${pythonPackages.graphite-web}" "${dataDir}/current_graphite_web"
+ fi
+ '';
+ };
+
+ environment.systemPackages = [ pythonPackages.graphite-web ];
+ }))
+
+ (mkIf cfg.api.enable {
+ systemd.services.graphiteApi = {
+ description = "Graphite Api Interface";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment = {
+ PYTHONPATH = let
+ aenv = pkgs.python.buildEnv.override {
+ extraLibs = [ cfg.api.package pkgs.cairo pkgs.pythonPackages.cffi ] ++ cfg.api.finders;
+ };
+ in "${aenv}/${pkgs.python.sitePackages}";
+ GRAPHITE_API_CONFIG = graphiteApiConfig;
+ LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib";
+ };
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.python27Packages.waitress}/bin/waitress-serve \
+ --host=${cfg.api.listenAddress} --port=${toString cfg.api.port} \
+ graphite_api.app:app
+ '';
+ User = "graphite";
+ Group = "graphite";
+ PermissionsStartOnly = true;
+ };
+ preStart = ''
+ if ! test -e ${dataDir}/db-created; then
+ mkdir -p ${dataDir}/cache/
+ chmod 0700 ${dataDir}/cache/
+
+ chown graphite:graphite ${cfg.dataDir}
+ chown -R graphite:graphite ${cfg.dataDir}/cache
+
+ touch ${dataDir}/db-created
+ fi
+ '';
+ };
+ })
+
+ (mkIf cfg.seyren.enable {
+ systemd.services.seyren = {
+ description = "Graphite Alerting Dashboard";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "mongodb.service" ];
+ environment = seyrenConfig;
+ serviceConfig = {
+ ExecStart = "${pkgs.seyren}/bin/seyren -httpPort ${toString cfg.seyren.port}";
+ WorkingDirectory = dataDir;
+ User = "graphite";
+ Group = "graphite";
+ };
+ preStart = ''
+ if ! test -e ${dataDir}/db-created; then
+ mkdir -p ${dataDir}
+ chown graphite:graphite ${dataDir}
+ fi
+ '';
+ };
+
+ services.mongodb.enable = mkDefault true;
+ })
+
+ (mkIf cfg.pager.enable {
+ systemd.services.graphitePager = {
+ description = "Graphite Pager Alerting Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "redis.service" ];
+ environment = {
+ REDIS_URL = cfg.pager.redisUrl;
+ GRAPHITE_URL = cfg.pager.graphiteUrl;
+ };
+ serviceConfig = {
+ ExecStart = "${pkgs.pythonPackages.graphitepager}/bin/graphite-pager --config ${pagerConfig}";
+ User = "graphite";
+ Group = "graphite";
+ };
+ };
+
+ services.redis.enable = mkDefault true;
+
+ environment.systemPackages = [ pkgs.pythonPackages.graphitepager ];
+ })
+
+ (mkIf cfg.beacon.enable {
+ systemd.services.graphite-beacon = {
+ description = "Grpahite Beacon Alerting Daemon";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.pythonPackages.graphite_beacon}/bin/graphite-beacon \
+ --config=${pkgs.writeText "graphite-beacon.json" (builtins.toJSON cfg.beacon.config)}
+ '';
+ User = "graphite";
+ Group = "graphite";
+ };
+ };
+ })
+
+ (mkIf (
+ cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay ||
+ cfg.web.enable || cfg.api.enable ||
+ cfg.seyren.enable || cfg.pager.enable || cfg.beacon.enable
+ ) {
+ users.users = singleton {
+ name = "graphite";
+ uid = config.ids.uids.graphite;
+ description = "Graphite daemon user";
+ home = dataDir;
+ };
+ users.groups.graphite.gid = config.ids.gids.graphite;
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/hdaps.nix b/nixpkgs/nixos/modules/services/monitoring/hdaps.nix
new file mode 100644
index 00000000000..2cad3b84d84
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/hdaps.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.hdapsd;
+ hdapsd = [ pkgs.hdapsd ];
+in
+{
+ options = {
+ services.hdapsd.enable = mkEnableOption
+ ''
+ Hard Drive Active Protection System Daemon,
+ devices are detected and managed automatically by udev and systemd
+ '';
+ };
+
+ config = mkIf cfg.enable {
+ boot.kernelModules = [ "hdapsd" ];
+ services.udev.packages = hdapsd;
+ systemd.packages = hdapsd;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/heapster.nix b/nixpkgs/nixos/modules/services/monitoring/heapster.nix
new file mode 100644
index 00000000000..6da0831b4c5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/heapster.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.heapster;
+in {
+ options.services.heapster = {
+ enable = mkOption {
+ description = "Whether to enable heapster monitoring";
+ default = false;
+ type = types.bool;
+ };
+
+ source = mkOption {
+ description = "Heapster metric source";
+ example = "kubernetes:https://kubernetes.default";
+ type = types.str;
+ };
+
+ sink = mkOption {
+ description = "Heapster metic sink";
+ example = "influxdb:http://localhost:8086";
+ type = types.str;
+ };
+
+ extraOpts = mkOption {
+ description = "Heapster extra options";
+ default = "";
+ type = types.separatedString " ";
+ };
+
+ package = mkOption {
+ description = "Package to use by heapster";
+ default = pkgs.heapster;
+ defaultText = "pkgs.heapster";
+ type = types.package;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.heapster = {
+ wantedBy = ["multi-user.target"];
+ after = ["cadvisor.service" "kube-apiserver.service"];
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/heapster --source=${cfg.source} --sink=${cfg.sink} ${cfg.extraOpts}";
+ User = "heapster";
+ };
+ };
+
+ users.users = singleton {
+ name = "heapster";
+ uid = config.ids.uids.heapster;
+ description = "Heapster user";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/incron.nix b/nixpkgs/nixos/modules/services/monitoring/incron.nix
new file mode 100644
index 00000000000..1789fd9f205
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/incron.nix
@@ -0,0 +1,98 @@
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.incron;
+
+in
+
+{
+ options = {
+
+ services.incron = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the incron daemon.
+
+ Note that commands run under incrontab only support common Nix profiles for the <envar>PATH</envar> provided variable.
+ '';
+ };
+
+ allow = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = ''
+ Users allowed to use incrontab.
+
+ If empty then no user will be allowed to have their own incrontab.
+ If <literal>null</literal> then will defer to <option>deny</option>.
+ If both <option>allow</option> and <option>deny</option> are null
+ then all users will be allowed to have their own incrontab.
+ '';
+ };
+
+ deny = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = "Users forbidden from using incrontab.";
+ };
+
+ systab = mkOption {
+ type = types.lines;
+ default = "";
+ description = "The system incrontab contents.";
+ example = ''
+ /var/mail IN_CLOSE_WRITE abc $@/$#
+ /tmp IN_ALL_EVENTS efg $@/$# $&
+ '';
+ };
+
+ extraPackages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "[ pkgs.rsync ]";
+ description = "Extra packages available to the system incrontab.";
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ warnings = optional (cfg.allow != null && cfg.deny != null)
+ ''If `services.incron.allow` is set then `services.incron.deny` will be ignored.'';
+
+ environment.systemPackages = [ pkgs.incron ];
+
+ security.wrappers.incrontab.source = "${pkgs.incron}/bin/incrontab";
+
+ # incron won't read symlinks
+ environment.etc."incron.d/system" = {
+ mode = "0444";
+ text = cfg.systab;
+ };
+ environment.etc."incron.allow" = mkIf (cfg.allow != null) {
+ text = concatStringsSep "\n" cfg.allow;
+ };
+ environment.etc."incron.deny" = mkIf (cfg.deny != null) {
+ text = concatStringsSep "\n" cfg.deny;
+ };
+
+ systemd.services.incron = {
+ description = "File System Events Scheduler";
+ wantedBy = [ "multi-user.target" ];
+ path = cfg.extraPackages;
+ serviceConfig.PIDFile = "/run/incrond.pid";
+ serviceConfig.ExecStartPre = "${pkgs.coreutils}/bin/mkdir -m 710 -p /var/spool/incron";
+ serviceConfig.ExecStart = "${pkgs.incron}/bin/incrond --foreground";
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/kapacitor.nix b/nixpkgs/nixos/modules/services/monitoring/kapacitor.nix
new file mode 100644
index 00000000000..9b4ff3c5612
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/kapacitor.nix
@@ -0,0 +1,191 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.kapacitor;
+
+ kapacitorConf = pkgs.writeTextFile {
+ name = "kapacitord.conf";
+ text = ''
+ hostname="${config.networking.hostName}"
+ data_dir="${cfg.dataDir}"
+
+ [http]
+ bind-address = "${cfg.bind}:${toString cfg.port}"
+ log-enabled = false
+ auth-enabled = false
+
+ [task]
+ dir = "${cfg.dataDir}/tasks"
+ snapshot-interval = "${cfg.taskSnapshotInterval}"
+
+ [replay]
+ dir = "${cfg.dataDir}/replay"
+
+ [storage]
+ boltdb = "${cfg.dataDir}/kapacitor.db"
+
+ ${optionalString (cfg.loadDirectory != null) ''
+ [load]
+ enabled = true
+ dir = "${cfg.loadDirectory}"
+ ''}
+
+ ${optionalString (cfg.defaultDatabase.enable) ''
+ [[influxdb]]
+ name = "default"
+ enabled = true
+ default = true
+ urls = [ "${cfg.defaultDatabase.url}" ]
+ username = "${cfg.defaultDatabase.username}"
+ password = "${cfg.defaultDatabase.password}"
+ ''}
+
+ ${optionalString (cfg.alerta.enable) ''
+ [alerta]
+ enabled = true
+ url = "${cfg.alerta.url}"
+ token = "${cfg.alerta.token}"
+ environment = "${cfg.alerta.environment}"
+ origin = "${cfg.alerta.origin}"
+ ''}
+
+ ${cfg.extraConfig}
+ '';
+ };
+in
+{
+ options.services.kapacitor = {
+ enable = mkEnableOption "kapacitor";
+
+ dataDir = mkOption {
+ type = types.path;
+ example = "/var/lib/kapacitor";
+ default = "/var/lib/kapacitor";
+ description = "Location where Kapacitor stores its state";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 9092;
+ description = "Port of Kapacitor";
+ };
+
+ bind = mkOption {
+ type = types.str;
+ default = "";
+ example = literalExample "0.0.0.0";
+ description = "Address to bind to. The default is to bind to all addresses";
+ };
+
+ extraConfig = mkOption {
+ description = "These lines go into kapacitord.conf verbatim.";
+ default = "";
+ type = types.lines;
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "kapacitor";
+ description = "User account under which Kapacitor runs";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "kapacitor";
+ description = "Group under which Kapacitor runs";
+ };
+
+ taskSnapshotInterval = mkOption {
+ type = types.str;
+ description = "Specifies how often to snapshot the task state (in InfluxDB time units)";
+ default = "1m0s";
+ example = "1m0s";
+ };
+
+ loadDirectory = mkOption {
+ type = types.nullOr types.path;
+ description = "Directory where to load services from, such as tasks, templates and handlers (or null to disable service loading on startup)";
+ default = null;
+ };
+
+ defaultDatabase = {
+ enable = mkEnableOption "kapacitor.defaultDatabase";
+
+ url = mkOption {
+ description = "The URL to an InfluxDB server that serves as the default database";
+ example = "http://localhost:8086";
+ type = types.str;
+ };
+
+ username = mkOption {
+ description = "The username to connect to the remote InfluxDB server";
+ type = types.str;
+ };
+
+ password = mkOption {
+ description = "The password to connect to the remote InfluxDB server";
+ type = types.str;
+ };
+ };
+
+ alerta = {
+ enable = mkEnableOption "kapacitor alerta integration";
+
+ url = mkOption {
+ description = "The URL to the Alerta REST API";
+ default = "http://localhost:5000";
+ example = "http://localhost:5000";
+ type = types.str;
+ };
+
+ token = mkOption {
+ description = "Default Alerta authentication token";
+ type = types.str;
+ default = "";
+ };
+
+ environment = mkOption {
+ description = "Default Alerta environment";
+ type = types.str;
+ default = "Production";
+ };
+
+ origin = mkOption {
+ description = "Default origin of alert";
+ type = types.str;
+ default = "kapacitor";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.kapacitor ];
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.kapacitor = {
+ description = "Kapacitor Real-Time Stream Processing Engine";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "networking.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.kapacitor}/bin/kapacitord -config ${kapacitorConf}";
+ User = "kapacitor";
+ Group = "kapacitor";
+ };
+ };
+
+ users.users.kapacitor = {
+ uid = config.ids.uids.kapacitor;
+ description = "Kapacitor user";
+ home = cfg.dataDir;
+ };
+
+ users.groups.kapacitor = {
+ gid = config.ids.gids.kapacitor;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/loki.nix b/nixpkgs/nixos/modules/services/monitoring/loki.nix
new file mode 100644
index 00000000000..f4eec7e0d28
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/loki.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) escapeShellArgs literalExample mkEnableOption mkIf mkOption types;
+
+ cfg = config.services.loki;
+
+ prettyJSON = conf:
+ pkgs.runCommand "loki-config.json" { } ''
+ echo '${builtins.toJSON conf}' | ${pkgs.jq}/bin/jq 'del(._module)' > $out
+ '';
+
+in {
+ options.services.loki = {
+ enable = mkEnableOption "loki";
+
+ user = mkOption {
+ type = types.str;
+ default = "loki";
+ description = ''
+ User under which the Loki service runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "loki";
+ description = ''
+ Group under which the Loki service runs.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/loki";
+ description = ''
+ Specify the directory for Loki.
+ '';
+ };
+
+ configuration = mkOption {
+ type = types.attrs;
+ default = {};
+ description = ''
+ Specify the configuration for Loki in Nix.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Specify a configuration file that Loki should use.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = literalExample [ "--server.http-listen-port=3101" ];
+ description = ''
+ Specify a list of additional command line flags,
+ which get escaped and are then passed to Loki.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = (
+ (cfg.configuration == {} -> cfg.configFile != null) &&
+ (cfg.configFile != null -> cfg.configuration == {})
+ );
+ message = ''
+ Please specify either
+ 'services.loki.configuration' or
+ 'services.loki.configFile'.
+ '';
+ }];
+
+ users.groups.${cfg.group} = { };
+ users.users.${cfg.user} = {
+ description = "Loki Service User";
+ group = cfg.group;
+ home = cfg.dataDir;
+ createHome = true;
+ isSystemUser = true;
+ };
+
+ systemd.services.loki = {
+ description = "Loki Service Daemon";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = let
+ conf = if cfg.configFile == null
+ then prettyJSON cfg.configuration
+ else cfg.configFile;
+ in
+ {
+ ExecStart = "${pkgs.grafana-loki}/bin/loki --config.file=${conf} ${escapeShellArgs cfg.extraFlags}";
+ User = cfg.user;
+ Restart = "always";
+ PrivateTmp = true;
+ ProtectHome = true;
+ ProtectSystem = "full";
+ DevicePolicy = "closed";
+ NoNewPrivileges = true;
+ WorkingDirectory = cfg.dataDir;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/longview.nix b/nixpkgs/nixos/modules/services/monitoring/longview.nix
new file mode 100644
index 00000000000..9c38956f9ba
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/longview.nix
@@ -0,0 +1,160 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.longview;
+
+ runDir = "/run/longview";
+ configsDir = "${runDir}/longview.d";
+
+in {
+ options = {
+
+ services.longview = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, system metrics will be sent to Linode LongView.
+ '';
+ };
+
+ apiKey = mkOption {
+ type = types.str;
+ default = "";
+ example = "01234567-89AB-CDEF-0123456789ABCDEF";
+ description = ''
+ Longview API key. To get this, look in Longview settings which
+ are found at https://manager.linode.com/longview/.
+
+ Warning: this secret is stored in the world-readable Nix store!
+ Use <option>apiKeyFile</option> instead.
+ '';
+ };
+
+ apiKeyFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/longview-api-key";
+ description = ''
+ A file containing the Longview API key.
+ To get this, look in Longview settings which
+ are found at https://manager.linode.com/longview/.
+
+ <option>apiKeyFile</option> takes precedence over <option>apiKey</option>.
+ '';
+ };
+
+ apacheStatusUrl = mkOption {
+ type = types.str;
+ default = "";
+ example = "http://127.0.0.1/server-status";
+ description = ''
+ The Apache status page URL. If provided, Longview will
+ gather statistics from this location. This requires Apache
+ mod_status to be loaded and enabled.
+ '';
+ };
+
+ nginxStatusUrl = mkOption {
+ type = types.str;
+ default = "";
+ example = "http://127.0.0.1/nginx_status";
+ description = ''
+ The Nginx status page URL. Longview will gather statistics
+ from this URL. This requires the Nginx stub_status module to
+ be enabled and configured at the given location.
+ '';
+ };
+
+ mysqlUser = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ The user for connecting to the MySQL database. If provided,
+ Longview will connect to MySQL and collect statistics about
+ queries, etc. This user does not need to have been granted
+ any extra privileges.
+ '';
+ };
+
+ mysqlPassword = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ The password corresponding to <option>mysqlUser</option>.
+ Warning: this is stored in cleartext in the Nix store!
+ Use <option>mysqlPasswordFile</option> instead.
+ '';
+ };
+
+ mysqlPasswordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/dbpassword";
+ description = ''
+ A file containing the password corresponding to <option>mysqlUser</option>.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.longview =
+ { description = "Longview Metrics Collection";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.Type = "forking";
+ serviceConfig.ExecStop = "-${pkgs.coreutils}/bin/kill -TERM $MAINPID";
+ serviceConfig.ExecReload = "-${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ serviceConfig.PIDFile = "${runDir}/longview.pid";
+ serviceConfig.ExecStart = "${pkgs.longview}/bin/longview";
+ preStart = ''
+ umask 077
+ mkdir -p ${configsDir}
+ '' + (optionalString (cfg.apiKeyFile != null) ''
+ cp --no-preserve=all "${cfg.apiKeyFile}" ${runDir}/longview.key
+ '') + (optionalString (cfg.apacheStatusUrl != "") ''
+ cat > ${configsDir}/Apache.conf <<EOF
+ location ${cfg.apacheStatusUrl}?auto
+ EOF
+ '') + (optionalString (cfg.mysqlUser != "" && cfg.mysqlPasswordFile != null) ''
+ cat > ${configsDir}/MySQL.conf <<EOF
+ username ${cfg.mysqlUser}
+ password `head -n1 "${cfg.mysqlPasswordFile}"`
+ EOF
+ '') + (optionalString (cfg.nginxStatusUrl != "") ''
+ cat > ${configsDir}/Nginx.conf <<EOF
+ location ${cfg.nginxStatusUrl}
+ EOF
+ '');
+ };
+
+ warnings = let warn = k: optional (cfg.${k} != "")
+ "config.services.longview.${k} is insecure. Use ${k}File instead.";
+ in concatMap warn [ "apiKey" "mysqlPassword" ];
+
+ assertions = [
+ { assertion = cfg.apiKeyFile != null;
+ message = "Longview needs an API key configured";
+ }
+ ];
+
+ # Create API key file if not configured.
+ services.longview.apiKeyFile = mkIf (cfg.apiKey != "")
+ (mkDefault (toString (pkgs.writeTextFile {
+ name = "longview.key";
+ text = cfg.apiKey;
+ })));
+
+ # Create MySQL password file if not configured.
+ services.longview.mysqlPasswordFile = mkDefault (toString (pkgs.writeTextFile {
+ name = "mysql-password-file";
+ text = cfg.mysqlPassword;
+ }));
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/monit.nix b/nixpkgs/nixos/modules/services/monitoring/monit.nix
new file mode 100644
index 00000000000..ca935227217
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/monit.nix
@@ -0,0 +1,46 @@
+{config, pkgs, lib, ...}:
+
+with lib;
+
+let
+ cfg = config.services.monit;
+in
+
+{
+ options.services.monit = {
+
+ enable = mkEnableOption "Monit";
+
+ config = mkOption {
+ type = types.lines;
+ default = "";
+ description = "monitrc content";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.monit ];
+
+ environment.etc.monitrc = {
+ text = cfg.config;
+ mode = "0400";
+ };
+
+ systemd.services.monit = {
+ description = "Pro-active monitoring utility for unix systems";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.monit}/bin/monit -I -c /etc/monitrc";
+ ExecStop = "${pkgs.monit}/bin/monit -c /etc/monitrc quit";
+ ExecReload = "${pkgs.monit}/bin/monit -c /etc/monitrc reload";
+ KillMode = "process";
+ Restart = "always";
+ };
+ restartTriggers = [ config.environment.etc.monitrc.source ];
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/munin.nix b/nixpkgs/nixos/modules/services/monitoring/munin.nix
new file mode 100644
index 00000000000..8af0650c738
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/munin.nix
@@ -0,0 +1,404 @@
+{ config, lib, pkgs, ... }:
+
+# TODO: support munin-async
+# TODO: LWP/Pg perl libs aren't recognized
+
+# TODO: support fastcgi
+# http://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
+# spawn-fcgi -s /run/munin/fastcgi-graph.sock -U www-data -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
+# spawn-fcgi -s /run/munin/fastcgi-html.sock -U www-data -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
+# https://paste.sh/vofcctHP#-KbDSXVeWoifYncZmLfZzgum
+# nginx https://munin.readthedocs.org/en/latest/example/webserver/nginx.html
+
+
+with lib;
+
+let
+ nodeCfg = config.services.munin-node;
+ cronCfg = config.services.munin-cron;
+
+ muninConf = pkgs.writeText "munin.conf"
+ ''
+ dbdir /var/lib/munin
+ htmldir /var/www/munin
+ logdir /var/log/munin
+ rundir /run/munin
+
+ ${lib.optionalString (cronCfg.extraCSS != "") "staticdir ${customStaticDir}"}
+
+ ${cronCfg.extraGlobalConfig}
+
+ ${cronCfg.hosts}
+ '';
+
+ nodeConf = pkgs.writeText "munin-node.conf"
+ ''
+ log_level 3
+ log_file Sys::Syslog
+ port 4949
+ host *
+ background 0
+ user root
+ group root
+ host_name ${config.networking.hostName}
+ setsid 0
+
+ # wrapped plugins by makeWrapper being with dots
+ ignore_file ^\.
+
+ allow ^::1$
+ allow ^127\.0\.0\.1$
+
+ ${nodeCfg.extraConfig}
+ '';
+
+ pluginConf = pkgs.writeText "munin-plugin-conf"
+ ''
+ [hddtemp_smartctl]
+ user root
+ group root
+
+ [meminfo]
+ user root
+ group root
+
+ [ipmi*]
+ user root
+ group root
+
+ [munin*]
+ env.UPDATE_STATSFILE /var/lib/munin/munin-update.stats
+
+ ${nodeCfg.extraPluginConfig}
+ '';
+
+ pluginConfDir = pkgs.stdenv.mkDerivation {
+ name = "munin-plugin-conf.d";
+ buildCommand = ''
+ mkdir $out
+ ln -s ${pluginConf} $out/nixos-config
+ '';
+ };
+
+ # Copy one Munin plugin into the Nix store with a specific name.
+ # This is suitable for use with plugins going directly into /etc/munin/plugins,
+ # i.e. munin.extraPlugins.
+ internOnePlugin = name: path:
+ "cp -a '${path}' '${name}'";
+
+ # Copy an entire tree of Munin plugins into a single directory in the Nix
+ # store, with no renaming.
+ # This is suitable for use with munin-node-configure --suggest, i.e.
+ # munin.extraAutoPlugins.
+ internManyPlugins = name: path:
+ "find '${path}' -type f -perm /a+x -exec cp -a -t . '{}' '+'";
+
+ # Use the appropriate intern-fn to copy the plugins into the store and patch
+ # them afterwards in an attempt to get them to run on NixOS.
+ internAndFixPlugins = name: intern-fn: paths:
+ pkgs.runCommand name {} ''
+ mkdir -p "$out"
+ cd "$out"
+ ${lib.concatStringsSep "\n"
+ (lib.attrsets.mapAttrsToList intern-fn paths)}
+ chmod -R u+w .
+ find . -type f -exec sed -E -i '
+ s,(/usr)?/s?bin/,/run/current-system/sw/bin/,g
+ ' '{}' '+'
+ '';
+
+ # TODO: write a derivation for munin-contrib, so that for contrib plugins
+ # you can just refer to them by name rather than needing to include a copy
+ # of munin-contrib in your nixos configuration.
+ extraPluginDir = internAndFixPlugins "munin-extra-plugins.d"
+ internOnePlugin nodeCfg.extraPlugins;
+
+ extraAutoPluginDir = internAndFixPlugins "munin-extra-auto-plugins.d"
+ internManyPlugins
+ (builtins.listToAttrs
+ (map
+ (path: { name = baseNameOf path; value = path; })
+ nodeCfg.extraAutoPlugins));
+
+ customStaticDir = pkgs.runCommand "munin-custom-static-data" {} ''
+ cp -a "${pkgs.munin}/etc/opt/munin/static" "$out"
+ cd "$out"
+ chmod -R u+w .
+ echo "${cronCfg.extraCSS}" >> style.css
+ echo "${cronCfg.extraCSS}" >> style-new.css
+ '';
+in
+
+{
+
+ options = {
+
+ services.munin-node = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable Munin Node agent. Munin node listens on 0.0.0.0 and
+ by default accepts connections only from 127.0.0.1 for security reasons.
+
+ See <link xlink:href='http://guide.munin-monitoring.org/en/latest/architecture/index.html' />.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ <filename>munin-node.conf</filename> extra configuration. See
+ <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html' />
+ '';
+ };
+
+ extraPluginConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ <filename>plugin-conf.d</filename> extra plugin configuration. See
+ <link xlink:href='http://guide.munin-monitoring.org/en/latest/plugin/use.html' />
+ '';
+ example = ''
+ [fail2ban_*]
+ user root
+ '';
+ };
+
+ extraPlugins = mkOption {
+ default = {};
+ type = with types; attrsOf path;
+ description = ''
+ Additional Munin plugins to activate. Keys are the name of the plugin
+ symlink, values are the path to the underlying plugin script. You
+ can use the same plugin script multiple times (e.g. for wildcard
+ plugins).
+
+ Note that these plugins do not participate in autoconfiguration. If
+ you want to autoconfigure additional plugins, use
+ <option>services.munin-node.extraAutoPlugins</option>.
+
+ Plugins enabled in this manner take precedence over autoconfigured
+ plugins.
+
+ Plugins will be copied into the Nix store, and it will attempt to
+ modify them to run properly by fixing hardcoded references to
+ <literal>/bin</literal>, <literal>/usr/bin</literal>,
+ <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
+ '';
+ example = literalExample ''
+ {
+ zfs_usage_bigpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
+ zfs_usage_smallpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
+ zfs_list = /src/munin-contrib/plugins/zfs/zfs_list;
+ };
+ '';
+ };
+
+ extraAutoPlugins = mkOption {
+ default = [];
+ type = with types; listOf path;
+ description = ''
+ Additional Munin plugins to autoconfigure, using
+ <literal>munin-node-configure --suggest</literal>. These should be
+ the actual paths to the plugin files (or directories containing them),
+ not just their names.
+
+ If you want to manually enable individual plugins instead, use
+ <option>services.munin-node.extraPlugins</option>.
+
+ Note that only plugins that have the 'autoconfig' capability will do
+ anything if listed here, since plugins that cannot autoconfigure
+ won't be automatically enabled by
+ <literal>munin-node-configure</literal>.
+
+ Plugins will be copied into the Nix store, and it will attempt to
+ modify them to run properly by fixing hardcoded references to
+ <literal>/bin</literal>, <literal>/usr/bin</literal>,
+ <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
+ '';
+ example = literalExample ''
+ [
+ /src/munin-contrib/plugins/zfs
+ /src/munin-contrib/plugins/ssh
+ ];
+ '';
+ };
+
+ disabledPlugins = mkOption {
+ # TODO: figure out why Munin isn't writing the log file and fix it.
+ # In the meantime this at least suppresses a useless graph full of
+ # NaNs in the output.
+ default = [ "munin_stats" ];
+ type = with types; listOf str;
+ description = ''
+ Munin plugins to disable, even if
+ <literal>munin-node-configure --suggest</literal> tries to enable
+ them. To disable a wildcard plugin, use an actual wildcard, as in
+ the example.
+
+ munin_stats is disabled by default as it tries to read
+ <literal>/var/log/munin/munin-update.log</literal> for timing
+ information, and the NixOS build of Munin does not write this file.
+ '';
+ example = [ "diskstats" "zfs_usage_*" ];
+ };
+ };
+
+ services.munin-cron = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable munin-cron. Takes care of all heavy lifting to collect data from
+ nodes and draws graphs to html. Runs munin-update, munin-limits,
+ munin-graphs and munin-html in that order.
+
+ HTML output is in <filename>/var/www/munin/</filename>, configure your
+ favourite webserver to serve static files.
+ '';
+ };
+
+ extraGlobalConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ <filename>munin.conf</filename> extra global configuration.
+ See <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />.
+ Useful to setup notifications, see
+ <link xlink:href='http://guide.munin-monitoring.org/en/latest/tutorial/alert.html' />
+ '';
+ example = ''
+ contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com
+ '';
+ };
+
+ hosts = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Definitions of hosts of nodes to collect data from. Needs at least one
+ host for cron to succeed. See
+ <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />
+ '';
+ example = ''
+ [''${config.networking.hostName}]
+ address localhost
+ '';
+ };
+
+ extraCSS = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Custom styling for the HTML that munin-cron generates. This will be
+ appended to the CSS files used by munin-cron and will thus take
+ precedence over the builtin styles.
+ '';
+ example = ''
+ /* A simple dark theme. */
+ html, body { background: #222222; }
+ #header, #footer { background: #333333; }
+ img.i, img.iwarn, img.icrit, img.iunkn {
+ filter: invert(100%) hue-rotate(-30deg);
+ }
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkMerge [ (mkIf (nodeCfg.enable || cronCfg.enable) {
+
+ environment.systemPackages = [ pkgs.munin ];
+
+ users.users = [{
+ name = "munin";
+ description = "Munin monitoring user";
+ group = "munin";
+ uid = config.ids.uids.munin;
+ home = "/var/lib/munin";
+ }];
+
+ users.groups = [{
+ name = "munin";
+ gid = config.ids.gids.munin;
+ }];
+
+ }) (mkIf nodeCfg.enable {
+
+ systemd.services.munin-node = {
+ description = "Munin Node";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [ munin smartmontools "/run/current-system/sw" "/run/wrappers" ];
+ environment.MUNIN_LIBDIR = "${pkgs.munin}/lib";
+ environment.MUNIN_PLUGSTATE = "/run/munin";
+ environment.MUNIN_LOGDIR = "/var/log/munin";
+ preStart = ''
+ echo "Updating munin plugins..."
+
+ mkdir -p /etc/munin/plugins
+ rm -rf /etc/munin/plugins/*
+
+ # Autoconfigure builtin plugins
+ ${pkgs.munin}/bin/munin-node-configure --suggest --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${pkgs.munin}/lib/plugins --servicedir=/etc/munin/plugins --sconfdir=${pluginConfDir} 2>/dev/null | ${pkgs.bash}/bin/bash
+
+ # Autoconfigure extra plugins
+ ${pkgs.munin}/bin/munin-node-configure --suggest --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${extraAutoPluginDir} --servicedir=/etc/munin/plugins --sconfdir=${pluginConfDir} 2>/dev/null | ${pkgs.bash}/bin/bash
+
+ ${lib.optionalString (nodeCfg.extraPlugins != {}) ''
+ # Link in manually enabled plugins
+ ln -f -s -t /etc/munin/plugins ${extraPluginDir}/*
+ ''}
+
+ ${lib.optionalString (nodeCfg.disabledPlugins != []) ''
+ # Disable plugins
+ cd /etc/munin/plugins
+ rm -f ${toString nodeCfg.disabledPlugins}
+ ''}
+ '';
+ serviceConfig = {
+ ExecStart = "${pkgs.munin}/sbin/munin-node --config ${nodeConf} --servicedir /etc/munin/plugins/ --sconfdir=${pluginConfDir}";
+ };
+ };
+
+ # munin_stats plugin breaks as of 2.0.33 when this doesn't exist
+ systemd.tmpfiles.rules = [ "d /run/munin 0755 munin munin -" ];
+
+ }) (mkIf cronCfg.enable {
+
+ # Munin is hardcoded to use DejaVu Mono and the graphs come out wrong if
+ # it's not available.
+ fonts.fonts = [ pkgs.dejavu_fonts ];
+
+ systemd.timers.munin-cron = {
+ description = "batch Munin master programs";
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnCalendar = "*:0/5";
+ };
+
+ systemd.services.munin-cron = {
+ description = "batch Munin master programs";
+ unitConfig.Documentation = "man:munin-cron(8)";
+
+ serviceConfig = {
+ Type = "oneshot";
+ User = "munin";
+ ExecStart = "${pkgs.munin}/bin/munin-cron --config ${muninConf}";
+ };
+ };
+
+ systemd.tmpfiles.rules = [
+ "d /run/munin 0755 munin munin -"
+ "d /var/log/munin 0755 munin munin -"
+ "d /var/www/munin 0755 munin munin -"
+ "d /var/lib/munin 0755 munin munin -"
+ ];
+ })];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/nagios.nix b/nixpkgs/nixos/modules/services/monitoring/nagios.nix
new file mode 100644
index 00000000000..6a3b9776946
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/nagios.nix
@@ -0,0 +1,187 @@
+# Nagios system/network monitoring daemon.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nagios;
+
+ nagiosState = "/var/lib/nagios";
+ nagiosLogDir = "/var/log/nagios";
+
+ nagiosObjectDefs = cfg.objectDefs;
+
+ nagiosObjectDefsDir = pkgs.runCommand "nagios-objects" {
+ inherit nagiosObjectDefs;
+ preferLocalBuild = true;
+ } "mkdir -p $out; ln -s $nagiosObjectDefs $out/";
+
+ nagiosCfgFile = pkgs.writeText "nagios.cfg"
+ ''
+ # Paths for state and logs.
+ log_file=${nagiosLogDir}/current
+ log_archive_path=${nagiosLogDir}/archive
+ status_file=${nagiosState}/status.dat
+ object_cache_file=${nagiosState}/objects.cache
+ temp_file=${nagiosState}/nagios.tmp
+ lock_file=/run/nagios.lock # Not used I think.
+ state_retention_file=${nagiosState}/retention.dat
+ query_socket=${nagiosState}/nagios.qh
+ check_result_path=${nagiosState}
+ command_file=${nagiosState}/nagios.cmd
+
+ # Configuration files.
+ #resource_file=resource.cfg
+ cfg_dir=${nagiosObjectDefsDir}
+
+ # Uid/gid that the daemon runs under.
+ nagios_user=nagios
+ nagios_group=nagios
+
+ # Misc. options.
+ illegal_macro_output_chars=`~$&|'"<>
+ retain_state_information=1
+ ''; # "
+
+ # Plain configuration for the Nagios web-interface with no
+ # authentication.
+ nagiosCGICfgFile = pkgs.writeText "nagios.cgi.conf"
+ ''
+ main_config_file=${cfg.mainConfigFile}
+ use_authentication=0
+ url_html_path=${cfg.urlPath}
+ '';
+
+ extraHttpdConfig =
+ ''
+ ScriptAlias ${cfg.urlPath}/cgi-bin ${pkgs.nagios}/sbin
+
+ <Directory "${pkgs.nagios}/sbin">
+ Options ExecCGI
+ Require all granted
+ SetEnv NAGIOS_CGI_CONFIG ${cfg.cgiConfigFile}
+ </Directory>
+
+ Alias ${cfg.urlPath} ${pkgs.nagios}/share
+
+ <Directory "${pkgs.nagios}/share">
+ Options None
+ Require all granted
+ </Directory>
+ '';
+
+in
+{
+ options = {
+ services.nagios = {
+ enable = mkOption {
+ default = false;
+ description = "
+ Whether to use <link
+ xlink:href='http://www.nagios.org/'>Nagios</link> to monitor
+ your system or network.
+ ";
+ };
+
+ objectDefs = mkOption {
+ description = "
+ A list of Nagios object configuration files that must define
+ the hosts, host groups, services and contacts for the
+ network that you want Nagios to monitor.
+ ";
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.package;
+ default = [pkgs.nagiosPluginsOfficial pkgs.ssmtp];
+ defaultText = "[pkgs.nagiosPluginsOfficial pkgs.ssmtp]";
+ description = "
+ Packages to be added to the Nagios <envar>PATH</envar>.
+ Typically used to add plugins, but can be anything.
+ ";
+ };
+
+ mainConfigFile = mkOption {
+ type = types.package;
+ default = nagiosCfgFile;
+ defaultText = "nagiosCfgFile";
+ description = "
+ Derivation for the main configuration file of Nagios.
+ ";
+ };
+
+ cgiConfigFile = mkOption {
+ type = types.package;
+ default = nagiosCGICfgFile;
+ defaultText = "nagiosCGICfgFile";
+ description = "
+ Derivation for the configuration file of Nagios CGI scripts
+ that can be used in web servers for running the Nagios web interface.
+ ";
+ };
+
+ enableWebInterface = mkOption {
+ default = false;
+ description = "
+ Whether to enable the Nagios web interface. You should also
+ enable Apache (<option>services.httpd.enable</option>).
+ ";
+ };
+
+ urlPath = mkOption {
+ default = "/nagios";
+ description = "
+ The URL path under which the Nagios web interface appears.
+ That is, you can access the Nagios web interface through
+ <literal>http://<replaceable>server</replaceable>/<replaceable>urlPath</replaceable></literal>.
+ ";
+ };
+ };
+ };
+
+
+ config = mkIf cfg.enable {
+ users.users.nagios = {
+ description = "Nagios user ";
+ uid = config.ids.uids.nagios;
+ home = nagiosState;
+ group = "nagios";
+ };
+
+ users.groups.nagios = { };
+
+ # This isn't needed, it's just so that the user can type "nagiostats
+ # -c /etc/nagios.cfg".
+ environment.etc = [
+ { source = cfg.mainConfigFile;
+ target = "nagios.cfg";
+ }
+ ];
+
+ environment.systemPackages = [ pkgs.nagios ];
+ systemd.services.nagios = {
+ description = "Nagios monitoring daemon";
+ path = [ pkgs.nagios ];
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ User = "nagios";
+ Group = "nagios";
+ Restart = "always";
+ RestartSec = 2;
+ LogsDirectory = "nagios";
+ StateDirectory = "nagios";
+ };
+
+ script = ''
+ for i in ${toString cfg.plugins}; do
+ export PATH=$i/bin:$i/sbin:$i/libexec:$PATH
+ done
+ exec ${pkgs.nagios}/bin/nagios ${cfg.mainConfigFile}
+ '';
+ };
+
+ services.httpd.extraConfig = optionalString cfg.enableWebInterface extraHttpdConfig;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/netdata.nix b/nixpkgs/nixos/modules/services/monitoring/netdata.nix
new file mode 100644
index 00000000000..463b1b882ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/netdata.nix
@@ -0,0 +1,191 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.netdata;
+
+ wrappedPlugins = pkgs.runCommand "wrapped-plugins" { preferLocalBuild = true; } ''
+ mkdir -p $out/libexec/netdata/plugins.d
+ ln -s /run/wrappers/bin/apps.plugin $out/libexec/netdata/plugins.d/apps.plugin
+ ln -s /run/wrappers/bin/freeipmi.plugin $out/libexec/netdata/plugins.d/freeipmi.plugin
+ '';
+
+ plugins = [
+ "${pkgs.netdata}/libexec/netdata/plugins.d"
+ "${wrappedPlugins}/libexec/netdata/plugins.d"
+ ] ++ cfg.extraPluginPaths;
+
+ localConfig = {
+ global = {
+ "plugins directory" = concatStringsSep " " plugins;
+ };
+ web = {
+ "web files owner" = "root";
+ "web files group" = "root";
+ };
+ };
+ mkConfig = generators.toINI {} (recursiveUpdate localConfig cfg.config);
+ configFile = pkgs.writeText "netdata.conf" (if cfg.configText != null then cfg.configText else mkConfig);
+
+ defaultUser = "netdata";
+
+in {
+ options = {
+ services.netdata = {
+ enable = mkEnableOption "netdata";
+
+ user = mkOption {
+ type = types.str;
+ default = "netdata";
+ description = "User account under which netdata runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "netdata";
+ description = "Group under which netdata runs.";
+ };
+
+ configText = mkOption {
+ type = types.nullOr types.lines;
+ description = "Verbatim netdata.conf, cannot be combined with config.";
+ default = null;
+ example = ''
+ [global]
+ debug log = syslog
+ access log = syslog
+ error log = syslog
+ '';
+ };
+
+ python = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable python-based plugins
+ '';
+ };
+ extraPackages = mkOption {
+ default = ps: [];
+ defaultText = "ps: []";
+ example = literalExample ''
+ ps: [
+ ps.psycopg2
+ ps.docker
+ ps.dnspython
+ ]
+ '';
+ description = ''
+ Extra python packages available at runtime
+ to enable additional python plugins.
+ '';
+ };
+ };
+
+ extraPluginPaths = mkOption {
+ type = types.listOf types.path;
+ default = [ ];
+ example = literalExample ''
+ [ "/path/to/plugins.d" ]
+ '';
+ description = ''
+ Extra paths to add to the netdata global "plugins directory"
+ option. Useful for when you want to include your own
+ collection scripts.
+ </para><para>
+ Details about writing a custom netdata plugin are available at:
+ <link xlink:href="https://docs.netdata.cloud/collectors/plugins.d/"/>
+ </para><para>
+ Cannot be combined with configText.
+ '';
+ };
+
+ config = mkOption {
+ type = types.attrsOf types.attrs;
+ default = {};
+ description = "netdata.conf configuration as nix attributes. cannot be combined with configText.";
+ example = literalExample ''
+ global = {
+ "debug log" = "syslog";
+ "access log" = "syslog";
+ "error log" = "syslog";
+ };
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions =
+ [ { assertion = cfg.config != {} -> cfg.configText == null ;
+ message = "Cannot specify both config and configText";
+ }
+ ];
+
+ systemd.tmpfiles.rules = [
+ "d /var/cache/netdata 0755 ${cfg.user} ${cfg.group} -"
+ "Z /var/cache/netdata - ${cfg.user} ${cfg.group} -"
+ "d /var/log/netdata 0755 ${cfg.user} ${cfg.group} -"
+ "Z /var/log/netdata - ${cfg.user} ${cfg.group} -"
+ "d /var/lib/netdata 0755 ${cfg.user} ${cfg.group} -"
+ "Z /var/lib/netdata - ${cfg.user} ${cfg.group} -"
+ "d /etc/netdata 0755 ${cfg.user} ${cfg.group} -"
+ "Z /etc/netdata - ${cfg.user} ${cfg.group} -"
+ ];
+ systemd.services.netdata = {
+ description = "Real time performance monitoring";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = (with pkgs; [ gawk curl ]) ++ lib.optional cfg.python.enable
+ (pkgs.python3.withPackages cfg.python.extraPackages);
+ serviceConfig = {
+ Environment="PYTHONPATH=${pkgs.netdata}/libexec/netdata/python.d/python_modules";
+ ExecStart = "${pkgs.netdata}/bin/netdata -P /run/netdata/netdata.pid -D -c ${configFile}";
+ ExecReload = "${pkgs.utillinux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID";
+ TimeoutStopSec = 60;
+ # User and group
+ User = cfg.user;
+ Group = cfg.group;
+ # Runtime directory and mode
+ RuntimeDirectory = "netdata";
+ RuntimeDirectoryMode = "0755";
+ # Performance
+ LimitNOFILE = "30000";
+ };
+ };
+
+ systemd.enableCgroupAccounting = true;
+
+ security.wrappers."apps.plugin" = {
+ source = "${pkgs.netdata}/libexec/netdata/plugins.d/apps.plugin.org";
+ capabilities = "cap_dac_read_search,cap_sys_ptrace+ep";
+ owner = cfg.user;
+ group = cfg.group;
+ permissions = "u+rx,g+rx,o-rwx";
+ };
+
+ security.wrappers."freeipmi.plugin" = {
+ source = "${pkgs.netdata}/libexec/netdata/plugins.d/freeipmi.plugin.org";
+ capabilities = "cap_dac_override,cap_fowner+ep";
+ owner = cfg.user;
+ group = cfg.group;
+ permissions = "u+rx,g+rx,o-rwx";
+ };
+
+ security.pam.loginLimits = [
+ { domain = "netdata"; type = "soft"; item = "nofile"; value = "10000"; }
+ { domain = "netdata"; type = "hard"; item = "nofile"; value = "30000"; }
+ ];
+
+ users.users = optional (cfg.user == defaultUser) {
+ name = defaultUser;
+ };
+
+ users.groups = optional (cfg.group == defaultUser) {
+ name = defaultUser;
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/osquery.nix b/nixpkgs/nixos/modules/services/monitoring/osquery.nix
new file mode 100644
index 00000000000..c8c625577d3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/osquery.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with builtins;
+with lib;
+
+let
+ cfg = config.services.osquery;
+
+in
+
+{
+
+ options = {
+
+ services.osquery = {
+
+ enable = mkEnableOption "osquery";
+
+ loggerPath = mkOption {
+ type = types.path;
+ description = "Base directory used for logging.";
+ default = "/var/log/osquery";
+ };
+
+ pidfile = mkOption {
+ type = types.path;
+ description = "Path used for pid file.";
+ default = "/var/osquery/osqueryd.pidfile";
+ };
+
+ utc = mkOption {
+ type = types.bool;
+ description = "Attempt to convert all UNIX calendar times to UTC.";
+ default = true;
+ };
+
+ databasePath = mkOption {
+ type = types.path;
+ description = "Path used for database file.";
+ default = "/var/osquery/osquery.db";
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs // {
+ merge = loc: foldl' (res: def: recursiveUpdate res def.value) {};
+ };
+ description = "Extra config to be recursively merged into the JSON config file.";
+ default = { };
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.osquery ];
+
+ environment.etc."osquery/osquery.conf".text = toJSON (
+ recursiveUpdate {
+ options = {
+ config_plugin = "filesystem";
+ logger_plugin = "filesystem";
+ logger_path = cfg.loggerPath;
+ database_path = cfg.databasePath;
+ utc = cfg.utc;
+ };
+ } cfg.extraConfig
+ );
+
+ systemd.services.osqueryd = {
+ description = "The osquery Daemon";
+ after = [ "network.target" "syslog.service" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.osquery ];
+ preStart = ''
+ mkdir -p ${escapeShellArg cfg.loggerPath}
+ mkdir -p "$(dirname ${escapeShellArg cfg.pidfile})"
+ mkdir -p "$(dirname ${escapeShellArg cfg.databasePath})"
+ '';
+ serviceConfig = {
+ TimeoutStartSec = "infinity";
+ ExecStart = "${pkgs.osquery}/bin/osqueryd --logger_path ${escapeShellArg cfg.loggerPath} --pidfile ${escapeShellArg cfg.pidfile} --database_path ${escapeShellArg cfg.databasePath}";
+ KillMode = "process";
+ KillSignal = "SIGTERM";
+ Restart = "on-failure";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix
new file mode 100644
index 00000000000..11d85e9c4fc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -0,0 +1,150 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.alertmanager;
+ mkConfigFile = pkgs.writeText "alertmanager.yml" (builtins.toJSON cfg.configuration);
+
+ checkedConfig = file: pkgs.runCommand "checked-config" { buildInputs = [ cfg.package ]; } ''
+ ln -s ${file} $out
+ amtool check-config $out
+ '';
+
+ alertmanagerYml = let
+ yml = if cfg.configText != null then
+ pkgs.writeText "alertmanager.yml" cfg.configText
+ else mkConfigFile;
+ in checkedConfig yml;
+
+ cmdlineArgs = cfg.extraFlags ++ [
+ "--config.file ${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.
+ '';
+ };
+
+ configuration = mkOption {
+ type = types.nullOr types.attrs;
+ default = null;
+ description = ''
+ Alertmanager configuration as nix attribute set.
+ '';
+ };
+
+ configText = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Alertmanager configuration as YAML text. If non-null, this option
+ defines the text that is written to alertmanager.yml. If null, the
+ contents of alertmanager.yml is generated from the structured config
+ options.
+ '';
+ };
+
+ logFormat = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ If set use a syslog logger or JSON logging.
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.enum ["debug" "info" "warn" "error" "fatal"];
+ default = "warn";
+ description = ''
+ Only log messages with the given severity or above.
+ '';
+ };
+
+ webExternalUrl = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy).
+ Used for generating relative and absolute links back to Alertmanager itself.
+ If the URL has a path portion, it will be used to prefix all HTTP endoints served by Alertmanager.
+ If omitted, relevant URL components will be derived automatically.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Address to listen on for the web interface and API. Empty string will listen on all interfaces.
+ "localhost" will listen on 127.0.0.1 (but not ::1).
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 9093;
+ description = ''
+ Port to listen on for the web interface and API.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open port in firewall for incoming connections.
+ '';
+ };
+
+ 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" ];
+ serviceConfig = {
+ Restart = "always";
+ DynamicUser = true;
+ WorkingDirectory = "/tmp";
+ ExecStart = "${cfg.package}/bin/alertmanager" +
+ optionalString (length cmdlineArgs != 0) (" \\\n " +
+ concatStringsSep " \\\n " cmdlineArgs);
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix
new file mode 100644
index 00000000000..191c0bff9c8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix
@@ -0,0 +1,622 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus;
+
+ workingDir = "/var/lib/" + cfg.stateDir;
+
+ # a wrapper that verifies that the configuration is valid
+ promtoolCheck = what: name: file:
+ pkgs.runCommand
+ "${name}-${replaceStrings [" "] [""] what}-checked"
+ { buildInputs = [ cfg.package ]; } ''
+ ln -s ${file} $out
+ promtool ${what} $out
+ '';
+
+ # Pretty-print JSON to a file
+ writePrettyJSON = name: x:
+ pkgs.runCommand name { preferLocalBuild = true; } ''
+ echo '${builtins.toJSON x}' | ${pkgs.jq}/bin/jq . > $out
+ '';
+
+ generatedPrometheusYml = writePrettyJSON "prometheus.yml" promConfig;
+
+ # This becomes the main config file for Prometheus
+ promConfig = {
+ global = filterValidPrometheus cfg.globalConfig;
+ rule_files = map (promtoolCheck "check rules" "rules") (cfg.ruleFiles ++ [
+ (pkgs.writeText "prometheus.rules" (concatStringsSep "\n" cfg.rules))
+ ]);
+ scrape_configs = filterValidPrometheus cfg.scrapeConfigs;
+ alerting = {
+ inherit (cfg) alertmanagers;
+ };
+ };
+
+ prometheusYml = let
+ yml = if cfg.configText != null then
+ pkgs.writeText "prometheus.yml" cfg.configText
+ else generatedPrometheusYml;
+ in promtoolCheck "check config" "prometheus.yml" yml;
+
+ cmdlineArgs = cfg.extraFlags ++ [
+ "--storage.tsdb.path=${workingDir}/data/"
+ "--config.file=${prometheusYml}"
+ "--web.listen-address=${cfg.listenAddress}"
+ "--alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}"
+ "--alertmanager.timeout=${toString cfg.alertmanagerTimeout}s"
+ ] ++
+ optional (cfg.webExternalUrl != null) "--web.external-url=${cfg.webExternalUrl}";
+
+ filterValidPrometheus = filterAttrsListRecursive (n: v: !(n == "_module" || v == null));
+ filterAttrsListRecursive = pred: x:
+ if isAttrs x then
+ listToAttrs (
+ concatMap (name:
+ let v = x.${name}; in
+ if pred name v then [
+ (nameValuePair name (filterAttrsListRecursive pred v))
+ ] else []
+ ) (attrNames x)
+ )
+ else if isList x then
+ map (filterAttrsListRecursive pred) x
+ else x;
+
+ mkDefOpt = type : defaultStr : description : mkOpt type (description + ''
+
+ Defaults to <literal>${defaultStr}</literal> in prometheus
+ when set to <literal>null</literal>.
+ '');
+
+ mkOpt = type : description : mkOption {
+ type = types.nullOr type;
+ default = null;
+ inherit description;
+ };
+
+ promTypes.globalConfig = types.submodule {
+ options = {
+ scrape_interval = mkDefOpt types.str "1m" ''
+ How frequently to scrape targets by default.
+ '';
+
+ scrape_timeout = mkDefOpt types.str "10s" ''
+ How long until a scrape request times out.
+ '';
+
+ evaluation_interval = mkDefOpt types.str "1m" ''
+ How frequently to evaluate rules by default.
+ '';
+
+ external_labels = mkOpt (types.attrsOf types.str) ''
+ The labels to add to any time series or alerts when
+ communicating with external systems (federation, remote
+ storage, Alertmanager).
+ '';
+ };
+ };
+
+ promTypes.scrape_config = types.submodule {
+ options = {
+ job_name = mkOption {
+ type = types.str;
+ description = ''
+ The job name assigned to scraped metrics by default.
+ '';
+ };
+ scrape_interval = mkOpt types.str ''
+ How frequently to scrape targets from this job. Defaults to the
+ globally configured default.
+ '';
+
+ scrape_timeout = mkOpt types.str ''
+ Per-target timeout when scraping this job. Defaults to the
+ globally configured default.
+ '';
+
+ metrics_path = mkDefOpt types.str "/metrics" ''
+ The HTTP resource path on which to fetch metrics from targets.
+ '';
+
+ honor_labels = mkDefOpt types.bool "false" ''
+ Controls how Prometheus handles conflicts between labels
+ that are already present in scraped data and labels that
+ Prometheus would attach server-side ("job" and "instance"
+ labels, manually configured target labels, and labels
+ generated by service discovery implementations).
+
+ If honor_labels is set to "true", label conflicts are
+ resolved by keeping label values from the scraped data and
+ ignoring the conflicting server-side labels.
+
+ If honor_labels is set to "false", label conflicts are
+ resolved by renaming conflicting labels in the scraped data
+ to "exported_&lt;original-label&gt;" (for example
+ "exported_instance", "exported_job") and then attaching
+ server-side labels. This is useful for use cases such as
+ federation, where all labels specified in the target should
+ be preserved.
+ '';
+
+ honor_timestamps = mkDefOpt types.bool "true" ''
+ honor_timestamps controls whether Prometheus respects the timestamps present
+ in scraped data.
+
+ If honor_timestamps is set to <literal>true</literal>, the timestamps of the metrics exposed
+ by the target will be used.
+
+ If honor_timestamps is set to <literal>false</literal>, the timestamps of the metrics exposed
+ by the target will be ignored.
+ '';
+
+ scheme = mkDefOpt (types.enum ["http" "https"]) "http" ''
+ The URL scheme with which to fetch metrics from targets.
+ '';
+
+ params = mkOpt (types.attrsOf (types.listOf types.str)) ''
+ Optional HTTP URL parameters.
+ '';
+
+ basic_auth = mkOpt (types.submodule {
+ options = {
+ username = mkOption {
+ type = types.str;
+ description = ''
+ HTTP username
+ '';
+ };
+ password = mkOption {
+ type = types.str;
+ description = ''
+ HTTP password
+ '';
+ };
+ };
+ }) ''
+ Optional http login credentials for metrics scraping.
+ '';
+
+ bearer_token = mkOpt types.str ''
+ Sets the `Authorization` header on every scrape request with
+ the configured bearer token. It is mutually exclusive with
+ <option>bearer_token_file</option>.
+ '';
+
+ bearer_token_file = mkOpt types.str ''
+ Sets the `Authorization` header on every scrape request with
+ the bearer token read from the configured file. It is mutually
+ exclusive with <option>bearer_token</option>.
+ '';
+
+ tls_config = mkOpt promTypes.tls_config ''
+ Configures the scrape request's TLS settings.
+ '';
+
+ proxy_url = mkOpt types.str ''
+ Optional proxy URL.
+ '';
+
+ ec2_sd_configs = mkOpt (types.listOf promTypes.ec2_sd_config) ''
+ List of EC2 service discovery configurations.
+ '';
+
+ dns_sd_configs = mkOpt (types.listOf promTypes.dns_sd_config) ''
+ List of DNS service discovery configurations.
+ '';
+
+ consul_sd_configs = mkOpt (types.listOf promTypes.consul_sd_config) ''
+ List of Consul service discovery configurations.
+ '';
+
+ file_sd_configs = mkOpt (types.listOf promTypes.file_sd_config) ''
+ List of file service discovery configurations.
+ '';
+
+ static_configs = mkOpt (types.listOf promTypes.static_config) ''
+ List of labeled target groups for this job.
+ '';
+
+ relabel_configs = mkOpt (types.listOf promTypes.relabel_config) ''
+ List of relabel configurations.
+ '';
+
+ sample_limit = mkDefOpt types.int "0" ''
+ Per-scrape limit on number of scraped samples that will be accepted.
+ If more than this number of samples are present after metric relabelling
+ the entire scrape will be treated as failed. 0 means no limit.
+ '';
+ };
+ };
+
+ promTypes.static_config = types.submodule {
+ options = {
+ targets = mkOption {
+ type = types.listOf types.str;
+ description = ''
+ The targets specified by the target group.
+ '';
+ };
+ labels = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ description = ''
+ Labels assigned to all metrics scraped from the targets.
+ '';
+ };
+ };
+ };
+
+ promTypes.ec2_sd_config = types.submodule {
+ options = {
+ region = mkOption {
+ type = types.str;
+ description = ''
+ The AWS Region.
+ '';
+ };
+ endpoint = mkOpt types.str ''
+ Custom endpoint to be used.
+ '';
+
+ access_key = mkOpt types.str ''
+ The AWS API key id. If blank, the environment variable
+ <literal>AWS_ACCESS_KEY_ID</literal> is used.
+ '';
+
+ secret_key = mkOpt types.str ''
+ The AWS API key secret. If blank, the environment variable
+ <literal>AWS_SECRET_ACCESS_KEY</literal> is used.
+ '';
+
+ profile = mkOpt types.str ''
+ Named AWS profile used to connect to the API.
+ '';
+
+ role_arn = mkOpt types.str ''
+ AWS Role ARN, an alternative to using AWS API keys.
+ '';
+
+ refresh_interval = mkDefOpt types.str "60s" ''
+ Refresh interval to re-read the instance list.
+ '';
+
+ port = mkDefOpt types.int "80" ''
+ The port to scrape metrics from. If using the public IP
+ address, this must instead be specified in the relabeling
+ rule.
+ '';
+
+ filters = mkOpt (types.listOf promTypes.filter) ''
+ Filters can be used optionally to filter the instance list by other criteria.
+ '';
+ };
+ };
+
+ promTypes.filter = types.submodule {
+ options = {
+ name = mkOption {
+ type = types.str;
+ description = ''
+ See <link xlink:href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html">this list</link>
+ for the available filters.
+ '';
+ };
+
+ value = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Value of the filter.
+ '';
+ };
+ };
+ };
+
+ promTypes.dns_sd_config = types.submodule {
+ options = {
+ names = mkOption {
+ type = types.listOf types.str;
+ description = ''
+ A list of DNS SRV record names to be queried.
+ '';
+ };
+
+ refresh_interval = mkDefOpt types.str "30s" ''
+ The time after which the provided names are refreshed.
+ '';
+ };
+ };
+
+ promTypes.consul_sd_config = types.submodule {
+ options = {
+ server = mkDefOpt types.str "localhost:8500" ''
+ Consul server to query.
+ '';
+
+ token = mkOpt types.str "Consul token";
+
+ datacenter = mkOpt types.str "Consul datacenter";
+
+ scheme = mkDefOpt types.str "http" "Consul scheme";
+
+ username = mkOpt types.str "Consul username";
+
+ password = mkOpt types.str "Consul password";
+
+ tls_config = mkOpt promTypes.tls_config ''
+ Configures the Consul request's TLS settings.
+ '';
+
+ services = mkOpt (types.listOf types.str) ''
+ A list of services for which targets are retrieved.
+ '';
+
+ tags = mkOpt (types.listOf types.str) ''
+ An optional list of tags used to filter nodes for a given
+ service. Services must contain all tags in the list.
+ '';
+
+ node_meta = mkOpt (types.attrsOf types.str) ''
+ Node metadata used to filter nodes for a given service.
+ '';
+
+ tag_separator = mkDefOpt types.str "," ''
+ The string by which Consul tags are joined into the tag label.
+ '';
+
+ allow_stale = mkOpt types.bool ''
+ Allow stale Consul results
+ (see <link xlink:href="https://www.consul.io/api/index.html#consistency-modes"/>).
+
+ Will reduce load on Consul.
+ '';
+
+ refresh_interval = mkDefOpt types.str "30s" ''
+ The time after which the provided names are refreshed.
+
+ On large setup it might be a good idea to increase this value
+ because the catalog will change all the time.
+ '';
+ };
+ };
+
+ promTypes.file_sd_config = types.submodule {
+ options = {
+ files = mkOption {
+ type = types.listOf types.str;
+ description = ''
+ Patterns for files from which target groups are extracted. Refer
+ to the Prometheus documentation for permitted filename patterns
+ and formats.
+ '';
+ };
+
+ refresh_interval = mkDefOpt types.str "5m" ''
+ Refresh interval to re-read the files.
+ '';
+ };
+ };
+
+ promTypes.relabel_config = types.submodule {
+ options = {
+ source_labels = mkOpt (types.listOf types.str) ''
+ The source labels select values from existing labels. Their content
+ is concatenated using the configured separator and matched against
+ the configured regular expression.
+ '';
+
+ separator = mkDefOpt types.str ";" ''
+ Separator placed between concatenated source label values.
+ '';
+
+ target_label = mkOpt types.str ''
+ Label to which the resulting value is written in a replace action.
+ It is mandatory for replace actions.
+ '';
+
+ regex = mkDefOpt types.str "(.*)" ''
+ Regular expression against which the extracted value is matched.
+ '';
+
+ modulus = mkOpt types.int ''
+ Modulus to take of the hash of the source label values.
+ '';
+
+ replacement = mkDefOpt types.str "$1" ''
+ Replacement value against which a regex replace is performed if the
+ regular expression matches.
+ '';
+
+ action = mkDefOpt (types.enum ["replace" "keep" "drop"]) "replace" ''
+ Action to perform based on regex matching.
+ '';
+
+ };
+ };
+
+ promTypes.tls_config = types.submodule {
+ options = {
+ ca_file = mkOpt types.str ''
+ CA certificate to validate API server certificate with.
+ '';
+
+ cert_file = mkOpt types.str ''
+ Certificate file for client cert authentication to the server.
+ '';
+
+ key_file = mkOpt types.str ''
+ Key file for client cert authentication to the server.
+ '';
+
+ server_name = mkOpt types.str ''
+ ServerName extension to indicate the name of the server.
+ http://tools.ietf.org/html/rfc4366#section-3.1
+ '';
+
+ insecure_skip_verify = mkOpt types.bool ''
+ Disable validation of the server certificate.
+ '';
+ };
+ };
+
+in {
+ options.services.prometheus = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the Prometheus monitoring daemon.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.prometheus;
+ defaultText = "pkgs.prometheus";
+ description = ''
+ The prometheus package that should be used.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0:9090";
+ description = ''
+ Address to listen on for the web interface, API, and telemetry.
+ '';
+ };
+
+ stateDir = mkOption {
+ type = types.str;
+ default = "prometheus2";
+ description = ''
+ Directory below <literal>/var/lib</literal> to store Prometheus metrics data.
+ This directory will be created automatically using systemd's StateDirectory mechanism.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Extra commandline options when launching Prometheus.
+ '';
+ };
+
+ configText = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ If non-null, this option defines the text that is written to
+ prometheus.yml. If null, the contents of prometheus.yml is generated
+ from the structured config options.
+ '';
+ };
+
+ globalConfig = mkOption {
+ type = promTypes.globalConfig;
+ default = {};
+ description = ''
+ Parameters that are valid in all configuration contexts. They
+ also serve as defaults for other configuration sections
+ '';
+ };
+
+ rules = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Alerting and/or Recording rules to evaluate at runtime.
+ '';
+ };
+
+ ruleFiles = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ Any additional rules files to include in this configuration.
+ '';
+ };
+
+ scrapeConfigs = mkOption {
+ type = types.listOf promTypes.scrape_config;
+ default = [];
+ description = ''
+ A list of scrape configurations.
+ '';
+ };
+
+ alertmanagers = mkOption {
+ type = types.listOf types.attrs;
+ example = literalExample ''
+ [ {
+ scheme = "https";
+ path_prefix = "/alertmanager";
+ static_configs = [ {
+ targets = [
+ "prometheus.domain.tld"
+ ];
+ } ];
+ } ]
+ '';
+ default = [];
+ description = ''
+ A list of alertmanagers to send alerts to.
+ See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#alertmanager_config">the official documentation</link> for more information.
+ '';
+ };
+
+ alertmanagerNotificationQueueCapacity = mkOption {
+ type = types.int;
+ default = 10000;
+ description = ''
+ The capacity of the queue for pending alert manager notifications.
+ '';
+ };
+
+ alertmanagerTimeout = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ Alert manager HTTP API timeout (in seconds).
+ '';
+ };
+
+ webExternalUrl = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "https://example.com/";
+ description = ''
+ The URL under which Prometheus is externally reachable (for example,
+ if Prometheus is served via a reverse proxy).
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.groups.prometheus.gid = config.ids.gids.prometheus;
+ users.users.prometheus = {
+ description = "Prometheus daemon user";
+ uid = config.ids.uids.prometheus;
+ group = "prometheus";
+ };
+ systemd.services.prometheus = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/prometheus" +
+ optionalString (length cmdlineArgs != 0) (" \\\n " +
+ concatStringsSep " \\\n " cmdlineArgs);
+ User = "prometheus";
+ Restart = "always";
+ WorkingDirectory = workingDir;
+ StateDirectory = cfg.stateDir;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix
new file mode 100644
index 00000000000..84486aa98a4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -0,0 +1,211 @@
+{ config, pkgs, lib, options, ... }:
+
+let
+ inherit (lib) concatStrings foldl foldl' genAttrs literalExample maintainers
+ mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
+ optional types;
+
+ cfg = config.services.prometheus.exporters;
+
+ # each attribute in `exporterOpts` is expected to have specified:
+ # - port (types.int): port on which the exporter listens
+ # - serviceOpts (types.attrs): config that is merged with the
+ # default definition of the exporter's
+ # systemd service
+ # - extraOpts (types.attrs): extra configuration options to
+ # configure the exporter with, which
+ # are appended to the default options
+ #
+ # Note that `extraOpts` is optional, but a script for the exporter's
+ # systemd service must be provided by specifying either
+ # `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart`
+
+ exporterOpts = genAttrs [
+ "bind"
+ "blackbox"
+ "collectd"
+ "dnsmasq"
+ "dovecot"
+ "fritzbox"
+ "json"
+ "mail"
+ "minio"
+ "nginx"
+ "node"
+ "postfix"
+ "postgres"
+ "rspamd"
+ "snmp"
+ "surfboard"
+ "tor"
+ "unifi"
+ "varnish"
+ "wireguard"
+ ] (name:
+ import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
+ );
+
+ mkExporterOpts = ({ name, port }: {
+ enable = mkEnableOption "the prometheus ${name} exporter";
+ port = mkOption {
+ type = types.int;
+ default = port;
+ description = ''
+ Port to listen on.
+ '';
+ };
+ listenAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ Address to listen on.
+ '';
+ };
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Extra commandline options to pass to the ${name} exporter.
+ '';
+ };
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open port in firewall for incoming connections.
+ '';
+ };
+ firewallFilter = mkOption {
+ type = types.str;
+ default = "-p tcp -m tcp --dport ${toString port}";
+ example = literalExample ''
+ "-i eth0 -p tcp -m tcp --dport ${toString port}"
+ '';
+ description = ''
+ Specify a filter for iptables to use when
+ <option>services.prometheus.exporters.${name}.openFirewall</option>
+ is true. It is used as `ip46tables -I nixos-fw <option>firewallFilter</option> -j nixos-fw-accept`.
+ '';
+ };
+ user = mkOption {
+ type = types.str;
+ default = "${name}-exporter";
+ description = ''
+ User name under which the ${name} exporter shall be run.
+ Has no effect when <option>systemd.services.prometheus-${name}-exporter.serviceConfig.DynamicUser</option> is true.
+ '';
+ };
+ group = mkOption {
+ type = types.str;
+ default = "${name}-exporter";
+ description = ''
+ Group under which the ${name} exporter shall be run.
+ Has no effect when <option>systemd.services.prometheus-${name}-exporter.serviceConfig.DynamicUser</option> is true.
+ '';
+ };
+ });
+
+ mkSubModule = { name, port, extraOpts, imports }: {
+ ${name} = mkOption {
+ type = types.submodule {
+ inherit imports;
+ options = (mkExporterOpts {
+ inherit name port;
+ } // extraOpts);
+ };
+ internal = true;
+ default = {};
+ };
+ };
+
+ mkSubModules = (foldl' (a: b: a//b) {}
+ (mapAttrsToList (name: opts: mkSubModule {
+ inherit name;
+ inherit (opts) port;
+ extraOpts = opts.extraOpts or {};
+ imports = opts.imports or [];
+ }) exporterOpts)
+ );
+
+ mkExporterConf = { name, conf, serviceOpts }:
+ let
+ enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true;
+ in
+ mkIf conf.enable {
+ warnings = conf.warnings or [];
+ users.users."${name}-exporter" = (mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) {
+ description = "Prometheus ${name} exporter service user";
+ isSystemUser = true;
+ inherit (conf) group;
+ });
+ users.groups = (mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) {
+ "${name}-exporter" = {};
+ });
+ networking.firewall.extraCommands = mkIf conf.openFirewall (concatStrings [
+ "ip46tables -A nixos-fw ${conf.firewallFilter} "
+ "-m comment --comment ${name}-exporter -j nixos-fw-accept"
+ ]);
+ systemd.services."prometheus-${name}-exporter" = mkMerge ([{
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig.Restart = mkDefault "always";
+ serviceConfig.PrivateTmp = mkDefault true;
+ serviceConfig.WorkingDirectory = mkDefault /tmp;
+ serviceConfig.DynamicUser = mkDefault enableDynamicUser;
+ } serviceOpts ] ++ optional (!enableDynamicUser) {
+ serviceConfig.User = conf.user;
+ serviceConfig.Group = conf.group;
+ });
+ };
+in
+{
+ options.services.prometheus.exporters = mkOption {
+ type = types.submodule {
+ options = (mkSubModules);
+ };
+ description = "Prometheus exporter configuration";
+ default = {};
+ example = literalExample ''
+ {
+ node = {
+ enable = true;
+ enabledCollectors = [ "systemd" ];
+ };
+ varnish.enable = true;
+ }
+ '';
+ };
+
+ config = mkMerge ([{
+ assertions = [ {
+ assertion = (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null);
+ message = ''
+ Please ensure you have either `services.prometheus.exporters.snmp.configuration'
+ or `services.prometheus.exporters.snmp.configurationPath' set!
+ '';
+ } {
+ assertion = (cfg.mail.configFile == null) != (cfg.mail.configuration == {});
+ message = ''
+ Please specify either 'services.prometheus.exporters.mail.configuration'
+ or 'services.prometheus.exporters.mail.configFile'.
+ '';
+ } ];
+ }] ++ [(mkIf config.services.minio.enable {
+ services.prometheus.exporters.minio.minioAddress = mkDefault "http://localhost:9000";
+ services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey;
+ services.prometheus.exporters.minio.minioAccessSecret = mkDefault config.services.minio.secretKey;
+ })] ++ [(mkIf config.services.rspamd.enable {
+ services.prometheus.exporters.rspamd.url = mkDefault "http://localhost:11334/stat";
+ })] ++ (mapAttrsToList (name: conf:
+ mkExporterConf {
+ inherit name;
+ inherit (conf) serviceOpts;
+ conf = cfg.${name};
+ }) exporterOpts)
+ );
+
+ meta = {
+ doc = ./exporters.xml;
+ maintainers = [ maintainers.willibutz ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml
new file mode 100644
index 00000000000..c2d4b05996a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml
@@ -0,0 +1,227 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="module-services-prometheus-exporters">
+ <title>Prometheus exporters</title>
+ <para>
+ Prometheus exporters provide metrics for the
+ <link xlink:href="https://prometheus.io">prometheus monitoring system</link>.
+ </para>
+ <section xml:id="module-services-prometheus-exporters-configuration">
+ <title>Configuration</title>
+
+ <para>
+ One of the most common exporters is the
+ <link xlink:href="https://github.com/prometheus/node_exporter">node
+ exporter</link>, it provides hardware and OS metrics from the host it's
+ running on. The exporter could be configured as follows:
+<programlisting>
+ services.prometheus.exporters.node = {
+ enable = true;
+ enabledCollectors = [
+ "logind"
+ "systemd"
+ ];
+ disabledCollectors = [
+ "textfile"
+ ];
+ openFirewall = true;
+ firewallFilter = "-i br0 -p tcp -m tcp --dport 9100";
+ };
+</programlisting>
+ It should now serve all metrics from the collectors that are explicitly
+ enabled and the ones that are
+ <link xlink:href="https://github.com/prometheus/node_exporter#enabled-by-default">enabled
+ by default</link>, via http under <literal>/metrics</literal>. In this
+ example the firewall should just allow incoming connections to the
+ exporter's port on the bridge interface <literal>br0</literal> (this would
+ have to be configured seperately of course). For more information about
+ configuration see <literal>man configuration.nix</literal> or search through
+ the
+ <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available
+ options</link>.
+ </para>
+ </section>
+ <section xml:id="module-services-prometheus-exporters-new-exporter">
+ <title>Adding a new exporter</title>
+
+ <para>
+ To add a new exporter, it has to be packaged first (see
+ <literal>nixpkgs/pkgs/servers/monitoring/prometheus/</literal> for
+ examples), then a module can be added. The postfix exporter is used in this
+ example:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Some default options for all exporters are provided by
+ <literal>nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix</literal>:
+ </para>
+ </listitem>
+ <listitem override='none'>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>enable</literal>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>port</literal>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>listenAddress</literal>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>extraFlags</literal>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>openFirewall</literal>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>firewallFilter</literal>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>user</literal>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>group</literal>
+ </para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ <para>
+ As there is already a package available, the module can now be added. This
+ is accomplished by adding a new file to the
+ <literal>nixos/modules/services/monitoring/prometheus/exporters/</literal>
+ directory, which will be called postfix.nix and contains all exporter
+ specific options and configuration:
+<programlisting>
+# nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ # for convenience we define cfg here
+ cfg = config.services.prometheus.exporters.postfix;
+in
+{
+ port = 9154; # The postfix exporter listens on this port by default
+
+ # `extraOpts` is an attribute set which contains additional options
+ # (and optional overrides for default options).
+ # Note that this attribute is optional.
+ extraOpts = {
+ telemetryPath = mkOption {
+ type = types.str;
+ default = "/metrics";
+ description = ''
+ Path under which to expose metrics.
+ '';
+ };
+ logfilePath = mkOption {
+ type = types.path;
+ default = /var/log/postfix_exporter_input.log;
+ example = /var/log/mail.log;
+ description = ''
+ Path where Postfix writes log entries.
+ This file will be truncated by this exporter!
+ '';
+ };
+ showqPath = mkOption {
+ type = types.path;
+ default = /var/spool/postfix/public/showq;
+ example = /var/lib/postfix/queue/public/showq;
+ description = ''
+ Path at which Postfix places its showq socket.
+ '';
+ };
+ };
+
+ # `serviceOpts` is an attribute set which contains configuration
+ # for the exporter's systemd service. One of
+ # `serviceOpts.script` and `serviceOpts.serviceConfig.ExecStart`
+ # has to be specified here. This will be merged with the default
+ # service confiuration.
+ # Note that by default 'DynamicUser' is 'true'.
+ serviceOpts = {
+ serviceConfig = {
+ DynamicUser = false;
+ ExecStart = ''
+ ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
+ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ --web.telemetry-path ${cfg.telemetryPath} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
+</programlisting>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ This should already be enough for the postfix exporter. Additionally one
+ could now add assertions and conditional default values. This can be done
+ in the 'meta-module' that combines all exporter definitions and generates
+ the submodules:
+ <literal>nixpkgs/nixos/modules/services/prometheus/exporters.nix</literal>
+ </para>
+ </listitem>
+ </itemizedlist>
+ </section>
+ <section xml:id="module-services-prometheus-exporters-update-exporter-module">
+ <title>Updating an exporter module</title>
+ <para>
+ Should an exporter option change at some point, it is possible to add
+ information about the change to the exporter definition similar to
+ <literal>nixpkgs/nixos/modules/rename.nix</literal>:
+<programlisting>
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.nginx;
+in
+{
+ port = 9113;
+ extraOpts = {
+ # additional module options
+ # ...
+ };
+ serviceOpts = {
+ # service configuration
+ # ...
+ };
+ imports = [
+ # 'services.prometheus.exporters.nginx.telemetryEndpoint' -> 'services.prometheus.exporters.nginx.telemetryPath'
+ (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
+
+ # removed option 'services.prometheus.exporters.nginx.insecure'
+ (mkRemovedOptionModule [ "insecure" ] ''
+ This option was replaced by 'prometheus.exporters.nginx.sslVerify' which defaults to true.
+ '')
+ ({ options.warnings = options.warnings; })
+ ];
+}
+</programlisting>
+ </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bind.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
new file mode 100644
index 00000000000..972632b5a24
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.bind;
+in
+{
+ port = 9119;
+ extraOpts = {
+ bindURI = mkOption {
+ type = types.str;
+ default = "http://localhost:8053/";
+ description = ''
+ HTTP XML API address of an Bind server.
+ '';
+ };
+ bindTimeout = mkOption {
+ type = types.str;
+ default = "10s";
+ description = ''
+ Timeout for trying to get stats from Bind.
+ '';
+ };
+ bindVersion = mkOption {
+ type = types.enum [ "xml.v2" "xml.v3" "auto" ];
+ default = "auto";
+ description = ''
+ BIND statistics version. Can be detected automatically.
+ '';
+ };
+ bindGroups = mkOption {
+ type = types.listOf (types.enum [ "server" "view" "tasks" ]);
+ default = [ "server" "view" ];
+ description = ''
+ List of statistics to collect. Available: [server, view, tasks]
+ '';
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.prometheus-bind-exporter}/bin/bind_exporter \
+ -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ -bind.pid-file /var/run/named/named.pid \
+ -bind.timeout ${toString cfg.bindTimeout} \
+ -bind.stats-url ${cfg.bindURI} \
+ -bind.stats-version ${cfg.bindVersion} \
+ -bind.stats-groups ${concatStringsSep "," cfg.bindGroups} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
new file mode 100644
index 00000000000..ca4366121e1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.blackbox;
+
+ checkConfig = file: pkgs.runCommand "checked-blackbox-exporter.conf" {
+ preferLocalBuild = true;
+ buildInputs = [ pkgs.buildPackages.prometheus-blackbox-exporter ]; } ''
+ ln -s ${file} $out
+ blackbox_exporter --config.check --config.file $out
+ '';
+in
+{
+ port = 9115;
+ extraOpts = {
+ configFile = mkOption {
+ type = types.path;
+ description = ''
+ Path to configuration file.
+ '';
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ AmbientCapabilities = [ "CAP_NET_RAW" ]; # for ping probes
+ ExecStart = ''
+ ${pkgs.prometheus-blackbox-exporter}/bin/blackbox_exporter \
+ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ --config.file ${checkConfig cfg.configFile} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
new file mode 100644
index 00000000000..1cc34641809
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.collectd;
+in
+{
+ port = 9103;
+ extraOpts = {
+ collectdBinary = {
+ enable = mkEnableOption "collectd binary protocol receiver";
+
+ authFile = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "File mapping user names to pre-shared keys (passwords).";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 25826;
+ description = ''Network address on which to accept collectd binary network packets.'';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ Address to listen on for binary network packets.
+ '';
+ };
+
+ securityLevel = mkOption {
+ type = types.enum ["None" "Sign" "Encrypt"];
+ default = "None";
+ description = ''
+ Minimum required security level for accepted packets.
+ '';
+ };
+ };
+
+ logFormat = mkOption {
+ type = types.str;
+ default = "logger:stderr";
+ example = "logger:syslog?appname=bob&local=7 or logger:stdout?json=true";
+ description = ''
+ Set the log target and format.
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.enum ["debug" "info" "warn" "error" "fatal"];
+ default = "info";
+ description = ''
+ Only log messages with the given severity or above.
+ '';
+ };
+ };
+ serviceOpts = let
+ collectSettingsArgs = if (cfg.collectdBinary.enable) then ''
+ -collectd.listen-address ${cfg.collectdBinary.listenAddress}:${toString cfg.collectdBinary.port} \
+ -collectd.security-level ${cfg.collectdBinary.securityLevel} \
+ '' else "";
+ in {
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.prometheus-collectd-exporter}/bin/collectd_exporter \
+ -log.format ${cfg.logFormat} \
+ -log.level ${cfg.logLevel} \
+ -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ ${collectSettingsArgs} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
new file mode 100644
index 00000000000..e9fa26cb1f5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.dnsmasq;
+in
+{
+ port = 9153;
+ extraOpts = {
+ dnsmasqListenAddress = mkOption {
+ type = types.str;
+ default = "localhost:53";
+ description = ''
+ Address on which dnsmasq listens.
+ '';
+ };
+ leasesPath = mkOption {
+ type = types.path;
+ default = "/var/lib/misc/dnsmasq.leases";
+ example = "/var/lib/dnsmasq/dnsmasq.leases";
+ description = ''
+ Path to the <literal>dnsmasq.leases</literal> file.
+ '';
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.prometheus-dnsmasq-exporter}/bin/dnsmasq_exporter \
+ --listen ${cfg.listenAddress}:${toString cfg.port} \
+ --dnsmasq ${cfg.dnsmasqListenAddress} \
+ --leases_path ${cfg.leasesPath} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
new file mode 100644
index 00000000000..a01074758ff
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.dovecot;
+in
+{
+ port = 9166;
+ extraOpts = {
+ telemetryPath = mkOption {
+ type = types.str;
+ default = "/metrics";
+ description = ''
+ Path under which to expose metrics.
+ '';
+ };
+ socketPath = mkOption {
+ type = types.path;
+ default = "/var/run/dovecot/stats";
+ example = "/var/run/dovecot2/old-stats";
+ description = ''
+ Path under which the stats socket is placed.
+ The user/group under which the exporter runs,
+ should be able to access the socket in order
+ to scrape the metrics successfully.
+
+ Please keep in mind that the stats module has changed in
+ <link xlink:href="https://wiki2.dovecot.org/Upgrading/2.3">Dovecot 2.3+</link> which
+ is not <link xlink:href="https://github.com/kumina/dovecot_exporter/issues/8">compatible with this exporter</link>.
+
+ The following extra config has to be passed to Dovecot to ensure that recent versions
+ work with this exporter:
+ <programlisting>
+ {
+ <xref linkend="opt-services.prometheus.exporters.dovecot.enable" /> = true;
+ <xref linkend="opt-services.prometheus.exporters.dovecot.socketPath" /> = "/var/run/dovecot2/old-stats";
+ <xref linkend="opt-services.dovecot2.extraConfig" /> = '''
+ mail_plugins = $mail_plugins old_stats
+ service old-stats {
+ unix_listener old-stats {
+ user = dovecot-exporter
+ group = dovecot-exporter
+ }
+ }
+ ''';
+ }
+ </programlisting>
+ '';
+ };
+ scopes = mkOption {
+ type = types.listOf types.str;
+ default = [ "user" ];
+ example = [ "user" "global" ];
+ description = ''
+ Stats scopes to query.
+ '';
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ DynamicUser = false;
+ ExecStart = ''
+ ${pkgs.prometheus-dovecot-exporter}/bin/dovecot_exporter \
+ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ --web.telemetry-path ${cfg.telemetryPath} \
+ --dovecot.socket-path ${cfg.socketPath} \
+ --dovecot.scopes ${concatStringsSep "," cfg.scopes} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
new file mode 100644
index 00000000000..9526597b8c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.fritzbox;
+in
+{
+ port = 9133;
+ extraOpts = {
+ gatewayAddress = mkOption {
+ type = types.str;
+ default = "fritz.box";
+ description = ''
+ The hostname or IP of the FRITZ!Box.
+ '';
+ };
+
+ gatewayPort = mkOption {
+ type = types.int;
+ default = 49000;
+ description = ''
+ The port of the FRITZ!Box UPnP service.
+ '';
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.prometheus-fritzbox-exporter}/bin/exporter \
+ -listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ -gateway-address ${cfg.gatewayAddress} \
+ -gateway-port ${toString cfg.gatewayPort} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/json.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/json.nix
new file mode 100644
index 00000000000..82a55bafc98
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/json.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.json;
+in
+{
+ port = 7979;
+ extraOpts = {
+ url = mkOption {
+ type = types.str;
+ description = ''
+ URL to scrape JSON from.
+ '';
+ };
+ configFile = mkOption {
+ type = types.path;
+ description = ''
+ Path to configuration file.
+ '';
+ };
+ listenAddress = {}; # not used
+ };
+ serviceOpts = {
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.prometheus-json-exporter}/bin/prometheus-json-exporter \
+ --port ${toString cfg.port} \
+ ${cfg.url} ${cfg.configFile} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
new file mode 100644
index 00000000000..7d8c6fb6140
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
@@ -0,0 +1,157 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.mail;
+
+ configurationFile = pkgs.writeText "prometheus-mail-exporter.conf" (builtins.toJSON (
+ # removes the _module attribute, null values and converts attrNames to lowercase
+ mapAttrs' (name: value:
+ if name == "servers"
+ then nameValuePair (toLower name)
+ ((map (srv: (mapAttrs' (n: v: nameValuePair (toLower n) v)
+ (filterAttrs (n: v: !(n == "_module" || v == null)) srv)
+ ))) value)
+ else nameValuePair (toLower name) value
+ ) (filterAttrs (n: _: !(n == "_module")) cfg.configuration)
+ ));
+
+ serverOptions.options = {
+ name = mkOption {
+ type = types.str;
+ description = ''
+ Value for label 'configname' which will be added to all metrics.
+ '';
+ };
+ server = mkOption {
+ type = types.str;
+ description = ''
+ Hostname of the server that should be probed.
+ '';
+ };
+ port = mkOption {
+ type = types.int;
+ example = 587;
+ description = ''
+ Port to use for SMTP.
+ '';
+ };
+ from = mkOption {
+ type = types.str;
+ example = "exporteruser@domain.tld";
+ description = ''
+ Content of 'From' Header for probing mails.
+ '';
+ };
+ to = mkOption {
+ type = types.str;
+ example = "exporteruser@domain.tld";
+ description = ''
+ Content of 'To' Header for probing mails.
+ '';
+ };
+ detectionDir = mkOption {
+ type = types.path;
+ example = "/var/spool/mail/exporteruser/new";
+ description = ''
+ Directory in which new mails for the exporter user are placed.
+ Note that this needs to exist when the exporter starts.
+ '';
+ };
+ login = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "exporteruser@domain.tld";
+ description = ''
+ Username to use for SMTP authentication.
+ '';
+ };
+ passphrase = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Password to use for SMTP authentication.
+ '';
+ };
+ };
+
+ exporterOptions.options = {
+ monitoringInterval = mkOption {
+ type = types.str;
+ example = "10s";
+ description = ''
+ Time interval between two probe attempts.
+ '';
+ };
+ mailCheckTimeout = mkOption {
+ type = types.str;
+ description = ''
+ Timeout until mails are considered "didn't make it".
+ '';
+ };
+ disableFileDelition = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Disables the exporter's function to delete probing mails.
+ '';
+ };
+ servers = mkOption {
+ type = types.listOf (types.submodule serverOptions);
+ default = [];
+ example = literalExample ''
+ [ {
+ name = "testserver";
+ server = "smtp.domain.tld";
+ port = 587;
+ from = "exporteruser@domain.tld";
+ to = "exporteruser@domain.tld";
+ detectionDir = "/path/to/Maildir/new";
+ } ]
+ '';
+ description = ''
+ List of servers that should be probed.
+ '';
+ };
+ };
+in
+{
+ port = 9225;
+ extraOpts = {
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Specify the mailexporter configuration file to use.
+ '';
+ };
+ configuration = mkOption {
+ type = types.submodule exporterOptions;
+ default = {};
+ description = ''
+ Specify the mailexporter configuration file to use.
+ '';
+ };
+ telemetryPath = mkOption {
+ type = types.str;
+ default = "/metrics";
+ description = ''
+ Path under which to expose metrics.
+ '';
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ DynamicUser = false;
+ ExecStart = ''
+ ${pkgs.prometheus-mail-exporter}/bin/mailexporter \
+ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ --config.file ${
+ if cfg.configuration != {} then configurationFile else cfg.configFile
+ } \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/minio.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
new file mode 100644
index 00000000000..ab3e3d7d5d5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
@@ -0,0 +1,64 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.minio;
+in
+{
+ port = 9290;
+ extraOpts = {
+ minioAddress = mkOption {
+ type = types.str;
+ example = "https://10.0.0.1:9000";
+ description = ''
+ The URL of the minio server.
+ Use HTTPS if Minio accepts secure connections only.
+ By default this connects to the local minio server if enabled.
+ '';
+ };
+
+ minioAccessKey = mkOption {
+ type = types.str;
+ example = "yourMinioAccessKey";
+ description = ''
+ The value of the Minio access key.
+ It is required in order to connect to the server.
+ By default this uses the one from the local minio server if enabled
+ and <literal>config.services.minio.accessKey</literal>.
+ '';
+ };
+
+ minioAccessSecret = mkOption {
+ type = types.str;
+ description = ''
+ The value of the Minio access secret.
+ It is required in order to connect to the server.
+ By default this uses the one from the local minio server if enabled
+ and <literal>config.services.minio.secretKey</literal>.
+ '';
+ };
+
+ minioBucketStats = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Collect statistics about the buckets and files in buckets.
+ It requires more computation, use it carefully in case of large buckets..
+ '';
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.prometheus-minio-exporter}/bin/minio-exporter \
+ -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ -minio.server ${cfg.minioAddress} \
+ -minio.access-key ${cfg.minioAccessKey} \
+ -minio.access-secret ${cfg.minioAccessSecret} \
+ ${optionalString cfg.minioBucketStats "-minio.bucket-stats"} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
new file mode 100644
index 00000000000..554377df37b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.nginx;
+in
+{
+ port = 9113;
+ extraOpts = {
+ scrapeUri = mkOption {
+ type = types.str;
+ default = "http://localhost/nginx_status";
+ description = ''
+ Address to access the nginx status page.
+ Can be enabled with services.nginx.statusPage = true.
+ '';
+ };
+ telemetryPath = mkOption {
+ type = types.str;
+ default = "/metrics";
+ description = ''
+ Path under which to expose metrics.
+ '';
+ };
+ sslVerify = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to perform certificate verification for https.
+ '';
+ };
+
+ };
+ serviceOpts = {
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.prometheus-nginx-exporter}/bin/nginx-prometheus-exporter \
+ --nginx.scrape-uri '${cfg.scrapeUri}' \
+ --nginx.ssl-verify ${toString cfg.sslVerify} \
+ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ --web.telemetry-path ${cfg.telemetryPath} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+ imports = [
+ (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
+ (mkRemovedOptionModule [ "insecure" ] ''
+ This option was replaced by 'prometheus.exporters.nginx.sslVerify'.
+ '')
+ ({ options.warnings = options.warnings; })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix
new file mode 100644
index 00000000000..adc2abe0b91
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.node;
+in
+{
+ port = 9100;
+ extraOpts = {
+ enabledCollectors = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ''[ "systemd" ]'';
+ description = ''
+ Collectors to enable. The collectors listed here are enabled in addition to the default ones.
+ '';
+ };
+ disabledCollectors = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ''[ "timex" ]'';
+ description = ''
+ Collectors to disable which are enabled by default.
+ '';
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ DynamicUser = false;
+ RuntimeDirectory = "prometheus-node-exporter";
+ ExecStart = ''
+ ${pkgs.prometheus-node-exporter}/bin/node_exporter \
+ ${concatMapStringsSep " " (x: "--collector." + x) cfg.enabledCollectors} \
+ ${concatMapStringsSep " " (x: "--no-collector." + x) cfg.disabledCollectors} \
+ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
new file mode 100644
index 00000000000..f40819e826b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.postfix;
+in
+{
+ port = 9154;
+ extraOpts = {
+ telemetryPath = mkOption {
+ type = types.str;
+ default = "/metrics";
+ description = ''
+ Path under which to expose metrics.
+ '';
+ };
+ logfilePath = mkOption {
+ type = types.path;
+ default = "/var/log/postfix_exporter_input.log";
+ example = "/var/log/mail.log";
+ description = ''
+ Path where Postfix writes log entries.
+ This file will be truncated by this exporter!
+ '';
+ };
+ showqPath = mkOption {
+ type = types.path;
+ default = "/var/spool/postfix/public/showq";
+ example = "/var/lib/postfix/queue/public/showq";
+ description = ''
+ Path where Postfix places it's showq socket.
+ '';
+ };
+ systemd = {
+ enable = mkEnableOption ''
+ reading metrics from the systemd-journal instead of from a logfile
+ '';
+ unit = mkOption {
+ type = types.str;
+ default = "postfix.service";
+ description = ''
+ Name of the postfix systemd unit.
+ '';
+ };
+ slice = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Name of the postfix systemd slice.
+ This overrides the <option>systemd.unit</option>.
+ '';
+ };
+ journalPath = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Path to the systemd journal.
+ '';
+ };
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ DynamicUser = false;
+ ExecStart = ''
+ ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
+ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ --web.telemetry-path ${cfg.telemetryPath} \
+ --postfix.showq_path ${cfg.showqPath} \
+ ${concatStringsSep " \\\n " (cfg.extraFlags
+ ++ optional cfg.systemd.enable "--systemd.enable"
+ ++ optional cfg.systemd.enable (if cfg.systemd.slice != null
+ then "--systemd.slice ${cfg.systemd.slice}"
+ else "--systemd.unit ${cfg.systemd.unit}")
+ ++ optional (cfg.systemd.enable && (cfg.systemd.journalPath != null))
+ "--systemd.jounal_path ${cfg.systemd.journalPath}"
+ ++ optional (!cfg.systemd.enable) "--postfix.logfile_path ${cfg.logfilePath}")}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
new file mode 100644
index 00000000000..1ece73a1159
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.postgres;
+in
+{
+ port = 9187;
+ extraOpts = {
+ telemetryPath = mkOption {
+ type = types.str;
+ default = "/metrics";
+ description = ''
+ Path under which to expose metrics.
+ '';
+ };
+ dataSourceName = mkOption {
+ type = types.str;
+ default = "user=postgres database=postgres host=/run/postgresql sslmode=disable";
+ example = "postgresql://username:password@localhost:5432/postgres?sslmode=disable";
+ description = ''
+ Accepts PostgreSQL URI form and key=value form arguments.
+ '';
+ };
+ runAsLocalSuperUser = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run the exporter as the local 'postgres' super user.
+ '';
+ };
+ };
+ serviceOpts = {
+ environment.DATA_SOURCE_NAME = cfg.dataSourceName;
+ serviceConfig = {
+ DynamicUser = false;
+ User = mkIf cfg.runAsLocalSuperUser (mkForce "postgres");
+ ExecStart = ''
+ ${pkgs.prometheus-postgres-exporter}/bin/postgres_exporter \
+ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ --web.telemetry-path ${cfg.telemetryPath} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
new file mode 100644
index 00000000000..1f02ae20724
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.rspamd;
+
+ prettyJSON = conf:
+ pkgs.runCommand "rspamd-exporter-config.yml" { } ''
+ echo '${builtins.toJSON conf}' | ${pkgs.buildPackages.jq}/bin/jq '.' > $out
+ '';
+
+ generateConfig = extraLabels: (map (path: {
+ name = "rspamd_${replaceStrings [ "." " " ] [ "_" "_" ] path}";
+ path = "$.${path}";
+ labels = extraLabels;
+ }) [
+ "actions.'add header'"
+ "actions.'no action'"
+ "actions.'rewrite subject'"
+ "actions.'soft reject'"
+ "actions.greylist"
+ "actions.reject"
+ "bytes_allocated"
+ "chunks_allocated"
+ "chunks_freed"
+ "chunks_oversized"
+ "connections"
+ "control_connections"
+ "ham_count"
+ "learned"
+ "pools_allocated"
+ "pools_freed"
+ "read_only"
+ "scanned"
+ "shared_chunks_allocated"
+ "spam_count"
+ "total_learns"
+ ]) ++ [{
+ name = "rspamd_statfiles";
+ type = "object";
+ path = "$.statfiles[*]";
+ labels = recursiveUpdate {
+ symbol = "$.symbol";
+ type = "$.type";
+ } extraLabels;
+ values = {
+ revision = "$.revision";
+ size = "$.size";
+ total = "$.total";
+ used = "$.used";
+ languages = "$.languages";
+ users = "$.users";
+ };
+ }];
+in
+{
+ port = 7980;
+ extraOpts = {
+ listenAddress = {}; # not used
+
+ url = mkOption {
+ type = types.str;
+ description = ''
+ URL to the rspamd metrics endpoint.
+ Defaults to http://localhost:11334/stat when
+ <option>services.rspamd.enable</option> is true.
+ '';
+ };
+
+ extraLabels = mkOption {
+ type = types.attrsOf types.str;
+ default = {
+ host = config.networking.hostName;
+ };
+ defaultText = "{ host = config.networking.hostName; }";
+ example = literalExample ''
+ {
+ host = config.networking.hostName;
+ custom_label = "some_value";
+ }
+ '';
+ description = "Set of labels added to each metric.";
+ };
+ };
+ serviceOpts.serviceConfig.ExecStart = ''
+ ${pkgs.prometheus-json-exporter}/bin/prometheus-json-exporter \
+ --port ${toString cfg.port} \
+ ${cfg.url} ${prettyJSON (generateConfig cfg.extraLabels)} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
new file mode 100644
index 00000000000..fe7ae8a8ac9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.snmp;
+in
+{
+ port = 9116;
+ extraOpts = {
+ configurationPath = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Path to a snmp exporter configuration file. Mutually exclusive with 'configuration' option.
+ '';
+ example = "./snmp.yml";
+ };
+
+ configuration = mkOption {
+ type = types.nullOr types.attrs;
+ default = {};
+ description = ''
+ Snmp exporter configuration as nix attribute set. Mutually exclusive with 'configurationPath' option.
+ '';
+ example = ''
+ {
+ "default" = {
+ "version" = 2;
+ "auth" = {
+ "community" = "public";
+ };
+ };
+ };
+ '';
+ };
+
+ logFormat = mkOption {
+ type = types.str;
+ default = "logger:stderr";
+ description = ''
+ Set the log target and format.
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.enum ["debug" "info" "warn" "error" "fatal"];
+ default = "info";
+ description = ''
+ Only log messages with the given severity or above.
+ '';
+ };
+ };
+ serviceOpts = let
+ configFile = if cfg.configurationPath != null
+ then cfg.configurationPath
+ else "${pkgs.writeText "snmp-eporter-conf.yml" (builtins.toJSON cfg.configuration)}";
+ in {
+ serviceConfig = {
+ 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} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
new file mode 100644
index 00000000000..81c5c70ed93
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.surfboard;
+in
+{
+ port = 9239;
+ extraOpts = {
+ modemAddress = mkOption {
+ type = types.str;
+ default = "192.168.100.1";
+ description = ''
+ The hostname or IP of the cable modem.
+ '';
+ };
+ };
+ serviceOpts = {
+ description = "Prometheus exporter for surfboard cable modem";
+ unitConfig.Documentation = "https://github.com/ipstatic/surfboard_exporter";
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.prometheus-surfboard-exporter}/bin/surfboard_exporter \
+ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ --modem-address ${cfg.modemAddress} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
new file mode 100644
index 00000000000..36c473677ef
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.tor;
+in
+{
+ port = 9130;
+ extraOpts = {
+ torControlAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ Tor control IP address or hostname.
+ '';
+ };
+
+ torControlPort = mkOption {
+ type = types.int;
+ default = 9051;
+ description = ''
+ Tor control port.
+ '';
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.prometheus-tor-exporter}/bin/prometheus-tor-exporter \
+ -b ${cfg.listenAddress} \
+ -p ${toString cfg.port} \
+ -a ${cfg.torControlAddress} \
+ -c ${toString cfg.torControlPort} \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+
+ # CPython requires a process to either have $HOME defined or run as a UID
+ # defined in /etc/passwd. The latter is false with DynamicUser, so define a
+ # dummy $HOME. https://bugs.python.org/issue10496
+ environment = { HOME = "/var/empty"; };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
new file mode 100644
index 00000000000..9aa0f1b85aa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.unifi;
+in
+{
+ port = 9130;
+ extraOpts = {
+ unifiAddress = mkOption {
+ type = types.str;
+ example = "https://10.0.0.1:8443";
+ description = ''
+ URL of the UniFi Controller API.
+ '';
+ };
+
+ unifiInsecure = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled skip the verification of the TLS certificate of the UniFi Controller API.
+ Use with caution.
+ '';
+ };
+
+ unifiUsername = mkOption {
+ type = types.str;
+ example = "ReadOnlyUser";
+ description = ''
+ username for authentication against UniFi Controller API.
+ '';
+ };
+
+ unifiPassword = mkOption {
+ type = types.str;
+ description = ''
+ Password for authentication against UniFi Controller API.
+ '';
+ };
+
+ unifiTimeout = mkOption {
+ type = types.str;
+ default = "5s";
+ example = "2m";
+ description = ''
+ Timeout including unit for UniFi Controller API requests.
+ '';
+ };
+ };
+ serviceOpts = {
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.prometheus-unifi-exporter}/bin/unifi_exporter \
+ -telemetry.addr ${cfg.listenAddress}:${toString cfg.port} \
+ -unifi.addr ${cfg.unifiAddress} \
+ -unifi.username ${cfg.unifiUsername} \
+ -unifi.password ${cfg.unifiPassword} \
+ -unifi.timeout ${cfg.unifiTimeout} \
+ ${optionalString cfg.unifiInsecure "-unifi.insecure" } \
+ ${concatStringsSep " \\\n " cfg.extraFlags}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
new file mode 100644
index 00000000000..12153fa021e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
@@ -0,0 +1,88 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.varnish;
+in
+{
+ port = 9131;
+ extraOpts = {
+ noExit = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Do not exit server on Varnish scrape errors.
+ '';
+ };
+ withGoMetrics = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Export go runtime and http handler metrics.
+ '';
+ };
+ verbose = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable verbose logging.
+ '';
+ };
+ raw = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable raw stdout logging without timestamps.
+ '';
+ };
+ varnishStatPath = mkOption {
+ type = types.str;
+ default = "varnishstat";
+ description = ''
+ Path to varnishstat.
+ '';
+ };
+ instance = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ varnishstat -n value.
+ '';
+ };
+ healthPath = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Path under which to expose healthcheck. Disabled unless configured.
+ '';
+ };
+ telemetryPath = mkOption {
+ type = types.str;
+ default = "/metrics";
+ description = ''
+ Path under which to expose metrics.
+ '';
+ };
+ };
+ serviceOpts = {
+ path = [ pkgs.varnish ];
+ serviceConfig = {
+ RestartSec = mkDefault 1;
+ DynamicUser = false;
+ ExecStart = ''
+ ${pkgs.prometheus-varnish-exporter}/bin/prometheus_varnish_exporter \
+ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+ --web.telemetry-path ${cfg.telemetryPath} \
+ --varnishstat-path ${cfg.varnishStatPath} \
+ ${concatStringsSep " \\\n " (cfg.extraFlags
+ ++ optional (cfg.healthPath != null) "--web.health-path ${cfg.healthPath}"
+ ++ optional (cfg.instance != null) "-n ${cfg.instance}"
+ ++ optional cfg.noExit "--no-exit"
+ ++ optional cfg.withGoMetrics "--with-go-metrics"
+ ++ optional cfg.verbose "--verbose"
+ ++ optional cfg.raw "--raw")}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
new file mode 100644
index 00000000000..8ae2c927b58
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.exporters.wireguard;
+in {
+ port = 9586;
+ extraOpts = {
+ verbose = mkEnableOption "Verbose logging mode for prometheus-wireguard-exporter";
+
+ wireguardConfig = mkOption {
+ type = with types; nullOr (either path str);
+ default = null;
+
+ description = ''
+ Path to the Wireguard Config to
+ <link xlink:href="https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/2.0.0#usage">add the peer's name to the stats of a peer</link>.
+
+ Please note that <literal>networking.wg-quick</literal> is required for this feature
+ as <literal>networking.wireguard</literal> uses
+ <citerefentry><refentrytitle>wg</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ to set the peers up.
+ '';
+ };
+
+ singleSubnetPerField = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ By default, all allowed IPs and subnets are comma-separated in the
+ <literal>allowed_ips</literal> field. With this option enabled,
+ a single IP and subnet will be listed in fields like <literal>allowed_ip_0</literal>,
+ <literal>allowed_ip_1</literal> and so on.
+ '';
+ };
+
+ withRemoteIp = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether or not the remote IP of a WireGuard peer should be exposed via prometheus.
+ '';
+ };
+ };
+ serviceOpts = {
+ path = [ pkgs.wireguard-tools ];
+
+ serviceConfig = {
+ AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+ ExecStart = ''
+ ${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \
+ -p ${toString cfg.port} \
+ ${optionalString cfg.verbose "-v"} \
+ ${optionalString cfg.singleSubnetPerField "-s"} \
+ ${optionalString cfg.withRemoteIp "-r"} \
+ ${optionalString (cfg.wireguardConfig != null) "-n ${cfg.wireguardConfig}"}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix
new file mode 100644
index 00000000000..f8fcc3eb97e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix
@@ -0,0 +1,166 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.prometheus.pushgateway;
+
+ cmdlineArgs =
+ opt "web.listen-address" cfg.web.listen-address
+ ++ opt "web.telemetry-path" cfg.web.telemetry-path
+ ++ opt "web.external-url" cfg.web.external-url
+ ++ opt "web.route-prefix" cfg.web.route-prefix
+ ++ optional cfg.persistMetrics ''--persistence.file="/var/lib/${cfg.stateDir}/metrics"''
+ ++ opt "persistence.interval" cfg.persistence.interval
+ ++ opt "log.level" cfg.log.level
+ ++ opt "log.format" cfg.log.format
+ ++ cfg.extraFlags;
+
+ opt = k : v : optional (v != null) ''--${k}="${v}"'';
+
+in {
+ options = {
+ services.prometheus.pushgateway = {
+ enable = mkEnableOption "Prometheus Pushgateway";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.prometheus-pushgateway;
+ defaultText = "pkgs.prometheus-pushgateway";
+ description = ''
+ Package that should be used for the prometheus pushgateway.
+ '';
+ };
+
+ web.listen-address = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Address to listen on for the web interface, API and telemetry.
+
+ <literal>null</literal> will default to <literal>:9091</literal>.
+ '';
+ };
+
+ web.telemetry-path = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Path under which to expose metrics.
+
+ <literal>null</literal> will default to <literal>/metrics</literal>.
+ '';
+ };
+
+ web.external-url = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The URL under which Pushgateway is externally reachable.
+ '';
+ };
+
+ web.route-prefix = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Prefix for the internal routes of web endpoints.
+
+ Defaults to the path of
+ <option>services.prometheus.pushgateway.web.external-url</option>.
+ '';
+ };
+
+ persistence.interval = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "10m";
+ description = ''
+ The minimum interval at which to write out the persistence file.
+
+ <literal>null</literal> will default to <literal>5m</literal>.
+ '';
+ };
+
+ log.level = mkOption {
+ type = types.nullOr (types.enum ["debug" "info" "warn" "error" "fatal"]);
+ default = null;
+ description = ''
+ Only log messages with the given severity or above.
+
+ <literal>null</literal> will default to <literal>info</literal>.
+ '';
+ };
+
+ log.format = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "logger:syslog?appname=bob&local=7";
+ description = ''
+ Set the log target and format.
+
+ <literal>null</literal> will default to <literal>logger:stderr</literal>.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Extra commandline options when launching the Pushgateway.
+ '';
+ };
+
+ persistMetrics = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to persist metrics to a file.
+
+ When enabled metrics will be saved to a file called
+ <literal>metrics</literal> in the directory
+ <literal>/var/lib/pushgateway</literal>. The directory below
+ <literal>/var/lib</literal> can be set using
+ <option>services.prometheus.pushgateway.stateDir</option>.
+ '';
+ };
+
+ stateDir = mkOption {
+ type = types.str;
+ default = "pushgateway";
+ description = ''
+ Directory below <literal>/var/lib</literal> to store metrics.
+
+ This directory will be created automatically using systemd's
+ StateDirectory mechanism when
+ <option>services.prometheus.pushgateway.persistMetrics</option>
+ is enabled.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = !hasPrefix "/" cfg.stateDir;
+ message =
+ "The option services.prometheus.pushgateway.stateDir" +
+ " shouldn't be an absolute directory." +
+ " It should be a directory relative to /var/lib.";
+ }
+ ];
+ systemd.services.pushgateway = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ Restart = "always";
+ DynamicUser = true;
+ ExecStart = "${cfg.package}/bin/pushgateway" +
+ optionalString (length cmdlineArgs != 0) (" \\\n " +
+ concatStringsSep " \\\n " cmdlineArgs);
+ StateDirectory = if cfg.persistMetrics then cfg.stateDir else null;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/riemann-dash.nix b/nixpkgs/nixos/modules/services/monitoring/riemann-dash.nix
new file mode 100644
index 00000000000..16eb8300850
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/riemann-dash.nix
@@ -0,0 +1,81 @@
+{ config, pkgs, lib, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+ cfg = config.services.riemann-dash;
+
+ conf = writeText "config.rb" ''
+ riemann_base = "${cfg.dataDir}"
+ config.store[:ws_config] = "#{riemann_base}/config/config.json"
+ ${cfg.config}
+ '';
+
+ launcher = writeScriptBin "riemann-dash" ''
+ #!/bin/sh
+ exec ${pkgs.riemann-dash}/bin/riemann-dash ${conf}
+ '';
+
+in {
+
+ options = {
+
+ services.riemann-dash = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the riemann-dash dashboard daemon.
+ '';
+ };
+ config = mkOption {
+ type = types.lines;
+ description = ''
+ Contents added to the end of the riemann-dash configuration file.
+ '';
+ };
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/riemann-dash";
+ description = ''
+ Location of the riemann-base dir. The dashboard configuration file is
+ is stored to this directory. The directory is created automatically on
+ service start, and owner is set to the riemanndash user.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ users.groups.riemanndash.gid = config.ids.gids.riemanndash;
+
+ users.users.riemanndash = {
+ description = "riemann-dash daemon user";
+ uid = config.ids.uids.riemanndash;
+ group = "riemanndash";
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - riemanndash riemanndash - -"
+ ];
+
+ systemd.services.riemann-dash = {
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "riemann.service" ];
+ after = [ "riemann.service" ];
+ preStart = ''
+ mkdir -p '${cfg.dataDir}/config'
+ '';
+ serviceConfig = {
+ User = "riemanndash";
+ ExecStart = "${launcher}/bin/riemann-dash";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix b/nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix
new file mode 100644
index 00000000000..86a11694e7b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix
@@ -0,0 +1,70 @@
+{ config, pkgs, lib, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+ cfg = config.services.riemann-tools;
+
+ riemannHost = "${cfg.riemannHost}";
+
+ healthLauncher = writeScriptBin "riemann-health" ''
+ #!/bin/sh
+ exec ${pkgs.riemann-tools}/bin/riemann-health ${builtins.concatStringsSep " " cfg.extraArgs} --host ${riemannHost}
+ '';
+
+
+in {
+
+ options = {
+
+ services.riemann-tools = {
+ enableHealth = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the riemann-health daemon.
+ '';
+ };
+ riemannHost = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ Address of the host riemann node. Defaults to localhost.
+ '';
+ };
+ extraArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ A list of commandline-switches forwarded to a riemann-tool.
+ See for example `riemann-health --help` for available options.
+ '';
+ example = ["-p 5555" "--timeout=30" "--attribute=myattribute=42"];
+ };
+ };
+ };
+
+ config = mkIf cfg.enableHealth {
+
+ users.groups.riemanntools.gid = config.ids.gids.riemanntools;
+
+ users.users.riemanntools = {
+ description = "riemann-tools daemon user";
+ uid = config.ids.uids.riemanntools;
+ group = "riemanntools";
+ };
+
+ systemd.services.riemann-health = {
+ wantedBy = [ "multi-user.target" ];
+ path = [ procps ];
+ serviceConfig = {
+ User = "riemanntools";
+ ExecStart = "${healthLauncher}/bin/riemann-health";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/riemann.nix b/nixpkgs/nixos/modules/services/monitoring/riemann.nix
new file mode 100644
index 00000000000..13d2b1cc060
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/riemann.nix
@@ -0,0 +1,105 @@
+{ config, pkgs, lib, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+ cfg = config.services.riemann;
+
+ classpath = concatStringsSep ":" (
+ cfg.extraClasspathEntries ++ [ "${riemann}/share/java/riemann.jar" ]
+ );
+
+ riemannConfig = concatStringsSep "\n" (
+ [cfg.config] ++ (map (f: ''(load-file "${f}")'') cfg.configFiles)
+ );
+
+ launcher = writeScriptBin "riemann" ''
+ #!/bin/sh
+ exec ${jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOpts} \
+ -cp ${classpath} \
+ riemann.bin ${cfg.configFile}
+ '';
+
+in {
+
+ options = {
+
+ services.riemann = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the Riemann network monitoring daemon.
+ '';
+ };
+ config = mkOption {
+ type = types.lines;
+ description = ''
+ Contents of the Riemann configuration file. For more complicated
+ config you should use configFile.
+ '';
+ };
+ configFiles = mkOption {
+ type = with types; listOf path;
+ default = [];
+ description = ''
+ Extra files containing Riemann configuration. These files will be
+ loaded at runtime by Riemann (with Clojure's
+ <literal>load-file</literal> function) at the end of the
+ configuration if you use the config option, this is ignored if you
+ use configFile.
+ '';
+ };
+ configFile = mkOption {
+ type = types.str;
+ description = ''
+ A Riemann config file. Any files in the same directory as this file
+ will be added to the classpath by Riemann.
+ '';
+ };
+ extraClasspathEntries = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = ''
+ Extra entries added to the Java classpath when running Riemann.
+ '';
+ };
+ extraJavaOpts = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = ''
+ Extra Java options used when launching Riemann.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ users.groups.riemann.gid = config.ids.gids.riemann;
+
+ users.users.riemann = {
+ description = "riemann daemon user";
+ uid = config.ids.uids.riemann;
+ group = "riemann";
+ };
+
+ services.riemann.configFile = mkDefault (
+ writeText "riemann-config.clj" riemannConfig
+ );
+
+ systemd.services.riemann = {
+ wantedBy = [ "multi-user.target" ];
+ path = [ inetutils ];
+ serviceConfig = {
+ User = "riemann";
+ ExecStart = "${launcher}/bin/riemann";
+ };
+ serviceConfig.LimitNOFILE = 65536;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/scollector.nix b/nixpkgs/nixos/modules/services/monitoring/scollector.nix
new file mode 100644
index 00000000000..38cd2213de7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/scollector.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.scollector;
+
+ collectors = pkgs.runCommand "collectors" { preferLocalBuild = true; }
+ ''
+ mkdir -p $out
+ ${lib.concatStringsSep
+ "\n"
+ (lib.mapAttrsToList
+ (frequency: binaries:
+ "mkdir -p $out/${frequency}\n" +
+ (lib.concatStringsSep
+ "\n"
+ (map (path: "ln -s ${path} $out/${frequency}/$(basename ${path})")
+ binaries)))
+ cfg.collectors)}
+ '';
+
+ conf = pkgs.writeText "scollector.toml" ''
+ Host = "${cfg.bosunHost}"
+ ColDir = "${collectors}"
+ ${cfg.extraConfig}
+ '';
+
+in {
+
+ options = {
+
+ services.scollector = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run scollector.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.scollector;
+ defaultText = "pkgs.scollector";
+ example = literalExample "pkgs.scollector";
+ description = ''
+ scollector binary to use.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "scollector";
+ description = ''
+ User account under which scollector runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "scollector";
+ description = ''
+ Group account under which scollector runs.
+ '';
+ };
+
+ bosunHost = mkOption {
+ type = types.str;
+ default = "localhost:8070";
+ description = ''
+ Host and port of the bosun server that will store the collected
+ data.
+ '';
+ };
+
+ collectors = mkOption {
+ type = with types; attrsOf (listOf path);
+ default = {};
+ example = literalExample "{ \"0\" = [ \"\${postgresStats}/bin/collect-stats\" ]; }";
+ description = ''
+ An attribute set mapping the frequency of collection to a list of
+ binaries that should be executed at that frequency. You can use "0"
+ to run a binary forever.
+ '';
+ };
+
+ extraOpts = mkOption {
+ type = with types; listOf str;
+ default = [];
+ example = [ "-d" ];
+ description = ''
+ Extra scollector command line options
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra scollector configuration added to the end of scollector.toml
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf config.services.scollector.enable {
+
+ systemd.services.scollector = {
+ description = "scollector metrics collector (part of Bosun)";
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ pkgs.coreutils pkgs.iproute ];
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${cfg.package.bin}/bin/scollector -conf=${conf} ${lib.concatStringsSep " " cfg.extraOpts}";
+ };
+ };
+
+ users.users.scollector = {
+ description = "scollector user";
+ group = "scollector";
+ uid = config.ids.uids.scollector;
+ };
+
+ users.groups.scollector.gid = config.ids.gids.scollector;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/smartd.nix b/nixpkgs/nixos/modules/services/monitoring/smartd.nix
new file mode 100644
index 00000000000..c345ec48a01
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/smartd.nix
@@ -0,0 +1,242 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ host = config.networking.hostName or "unknown"
+ + optionalString (config.networking.domain != null) ".${config.networking.domain}";
+
+ cfg = config.services.smartd;
+
+ nm = cfg.notifications.mail;
+ nw = cfg.notifications.wall;
+ nx = cfg.notifications.x11;
+
+ smartdNotify = pkgs.writeScript "smartd-notify.sh" ''
+ #! ${pkgs.runtimeShell}
+ ${optionalString nm.enable ''
+ {
+ ${pkgs.coreutils}/bin/cat << EOF
+ From: smartd on ${host} <root>
+ To: undisclosed-recipients:;
+ Subject: SMART error on $SMARTD_DEVICESTRING: $SMARTD_FAILTYPE
+
+ $SMARTD_FULLMESSAGE
+ EOF
+
+ ${pkgs.smartmontools}/sbin/smartctl -a -d "$SMARTD_DEVICETYPE" "$SMARTD_DEVICE"
+ } | ${nm.mailer} -i "${nm.recipient}"
+ ''}
+ ${optionalString nw.enable ''
+ {
+ ${pkgs.coreutils}/bin/cat << EOF
+ Problem detected with disk: $SMARTD_DEVICESTRING
+ Warning message from smartd is:
+
+ $SMARTD_MESSAGE
+ EOF
+ } | ${pkgs.utillinux}/bin/wall 2>/dev/null
+ ''}
+ ${optionalString nx.enable ''
+ export DISPLAY=${nx.display}
+ {
+ ${pkgs.coreutils}/bin/cat << EOF
+ Problem detected with disk: $SMARTD_DEVICESTRING
+ Warning message from smartd is:
+
+ $SMARTD_FULLMESSAGE
+ EOF
+ } | ${pkgs.xorg.xmessage}/bin/xmessage -file - 2>/dev/null &
+ ''}
+ '';
+
+ notifyOpts = optionalString (nm.enable || nw.enable || nx.enable)
+ ("-m <nomailer> -M exec ${smartdNotify} " + optionalString cfg.notifications.test "-M test ");
+
+ smartdConf = pkgs.writeText "smartd.conf" ''
+ # Autogenerated smartd startup config file
+ DEFAULT ${notifyOpts}${cfg.defaults.monitored}
+
+ ${concatMapStringsSep "\n" (d: "${d.device} ${d.options}") cfg.devices}
+
+ ${optionalString cfg.autodetect
+ "DEVICESCAN ${notifyOpts}${cfg.defaults.autodetected}"}
+ '';
+
+ smartdDeviceOpts = { ... }: {
+
+ options = {
+
+ device = mkOption {
+ example = "/dev/sda";
+ type = types.str;
+ description = "Location of the device.";
+ };
+
+ options = mkOption {
+ default = "";
+ example = "-d sat";
+ type = types.separatedString " ";
+ description = "Options that determine how smartd monitors the device.";
+ };
+
+ };
+
+ };
+
+in
+
+{
+ ###### interface
+
+ options = {
+
+ services.smartd = {
+
+ enable = mkEnableOption "smartd daemon from <literal>smartmontools</literal> package";
+
+ autodetect = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whenever smartd should monitor all devices connected to the
+ machine at the time it's being started (the default).
+
+ Set to false to monitor the devices listed in
+ <option>services.smartd.devices</option> only.
+ '';
+ };
+
+ extraOptions = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ example = ["-A /var/log/smartd/" "--interval=3600"];
+ description = ''
+ Extra command-line options passed to the <literal>smartd</literal>
+ daemon on startup.
+
+ (See <literal>man 8 smartd</literal>.)
+ '';
+ };
+
+ notifications = {
+
+ mail = {
+ enable = mkOption {
+ default = config.services.mail.sendmailSetuidWrapper != null;
+ type = types.bool;
+ description = "Whenever to send e-mail notifications.";
+ };
+
+ recipient = mkOption {
+ default = "root";
+ type = types.str;
+ description = "Recipient of the notification messages.";
+ };
+
+ mailer = mkOption {
+ default = "/run/wrappers/bin/sendmail";
+ type = types.path;
+ description = ''
+ Sendmail-compatible binary to be used to send the messages.
+
+ You should probably enable
+ <option>services.postfix</option> or some other MTA for
+ this to work.
+ '';
+ };
+ };
+
+ wall = {
+ enable = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whenever to send wall notifications to all users.";
+ };
+ };
+
+ x11 = {
+ enable = mkOption {
+ default = config.services.xserver.enable;
+ type = types.bool;
+ description = "Whenever to send X11 xmessage notifications.";
+ };
+
+ display = mkOption {
+ default = ":${toString config.services.xserver.display}";
+ type = types.str;
+ description = "DISPLAY to send X11 notifications to.";
+ };
+ };
+
+ test = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whenever to send a test notification on startup.";
+ };
+
+ };
+
+ defaults = {
+ monitored = mkOption {
+ default = "-a";
+ type = types.separatedString " ";
+ example = "-a -o on -s (S/../.././02|L/../../7/04)";
+ description = ''
+ Common default options for explicitly monitored (listed in
+ <option>services.smartd.devices</option>) devices.
+
+ The default value turns on monitoring of all the things (see
+ <literal>man 5 smartd.conf</literal>).
+
+ The example also turns on SMART Automatic Offline Testing on
+ startup, and schedules short self-tests daily, and long
+ self-tests weekly.
+ '';
+ };
+
+ autodetected = mkOption {
+ default = cfg.defaults.monitored;
+ type = types.separatedString " ";
+ description = ''
+ Like <option>services.smartd.defaults.monitored</option>, but for the
+ autodetected devices.
+ '';
+ };
+ };
+
+ devices = mkOption {
+ default = [];
+ example = [ { device = "/dev/sda"; } { device = "/dev/sdb"; options = "-d sat"; } ];
+ type = with types; listOf (submodule smartdDeviceOpts);
+ description = "List of devices to monitor.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [ {
+ assertion = cfg.autodetect || cfg.devices != [];
+ message = "smartd can't run with both disabled autodetect and an empty list of devices to monitor.";
+ } ];
+
+ systemd.services.smartd = {
+ description = "S.M.A.R.T. Daemon";
+
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ pkgs.nettools ]; # for hostname and dnsdomanname calls in smartd
+
+ serviceConfig.ExecStart = "${pkgs.smartmontools}/sbin/smartd ${lib.concatStringsSep " " cfg.extraOptions} --no-fork --configfile=${smartdConf}";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/statsd.nix b/nixpkgs/nixos/modules/services/monitoring/statsd.nix
new file mode 100644
index 00000000000..ea155821ecc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/statsd.nix
@@ -0,0 +1,150 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.statsd;
+
+ isBuiltinBackend = name:
+ builtins.elem name [ "graphite" "console" "repeater" ];
+
+ backendsToPackages = let
+ mkMap = list: name:
+ if isBuiltinBackend name then list
+ else list ++ [ pkgs.nodePackages.${name} ];
+ in foldl mkMap [];
+
+ configFile = pkgs.writeText "statsd.conf" ''
+ {
+ address: "${cfg.listenAddress}",
+ port: "${toString cfg.port}",
+ mgmt_address: "${cfg.mgmt_address}",
+ mgmt_port: "${toString cfg.mgmt_port}",
+ backends: [${
+ concatMapStringsSep "," (name:
+ if (isBuiltinBackend name)
+ then ''"./backends/${name}"''
+ else ''"${name}"''
+ ) cfg.backends}],
+ ${optionalString (cfg.graphiteHost!=null) ''graphiteHost: "${cfg.graphiteHost}",''}
+ ${optionalString (cfg.graphitePort!=null) ''graphitePort: "${toString cfg.graphitePort}",''}
+ console: {
+ prettyprint: false
+ },
+ log: {
+ backend: "stdout"
+ },
+ automaticConfigReload: false${optionalString (cfg.extraConfig != null) ","}
+ ${cfg.extraConfig}
+ }
+ '';
+
+ deps = pkgs.buildEnv {
+ name = "statsd-runtime-deps";
+ pathsToLink = [ "/lib" ];
+ ignoreCollisions = true;
+
+ paths = backendsToPackages cfg.backends;
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options.services.statsd = {
+
+ enable = mkEnableOption "statsd";
+
+ listenAddress = mkOption {
+ description = "Address that statsd listens on over UDP";
+ default = "127.0.0.1";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Port that stats listens for messages on over UDP";
+ default = 8125;
+ type = types.int;
+ };
+
+ mgmt_address = mkOption {
+ description = "Address to run management TCP interface on";
+ default = "127.0.0.1";
+ type = types.str;
+ };
+
+ mgmt_port = mkOption {
+ description = "Port to run the management TCP interface on";
+ default = 8126;
+ type = types.int;
+ };
+
+ backends = mkOption {
+ description = "List of backends statsd will use for data persistence";
+ default = [];
+ example = [
+ "graphite"
+ "console"
+ "repeater"
+ "statsd-librato-backend"
+ "stackdriver-statsd-backend"
+ "statsd-influxdb-backend"
+ ];
+ type = types.listOf types.str;
+ };
+
+ graphiteHost = mkOption {
+ description = "Hostname or IP of Graphite server";
+ default = null;
+ type = types.nullOr types.str;
+ };
+
+ graphitePort = mkOption {
+ description = "Port of Graphite server (i.e. carbon-cache).";
+ default = null;
+ type = types.nullOr types.int;
+ };
+
+ extraConfig = mkOption {
+ description = "Extra configuration options for statsd";
+ default = "";
+ type = types.nullOr types.str;
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = map (backend: {
+ assertion = !isBuiltinBackend backend -> hasAttrByPath [ backend ] pkgs.nodePackages;
+ message = "Only builtin backends (graphite, console, repeater) or backends enumerated in `pkgs.nodePackages` are allowed!";
+ }) cfg.backends;
+
+ users.users = singleton {
+ name = "statsd";
+ uid = config.ids.uids.statsd;
+ description = "Statsd daemon user";
+ };
+
+ systemd.services.statsd = {
+ description = "Statsd Server";
+ wantedBy = [ "multi-user.target" ];
+ environment = {
+ NODE_PATH = "${deps}/lib/node_modules";
+ };
+ serviceConfig = {
+ ExecStart = "${pkgs.statsd}/bin/statsd ${configFile}";
+ User = "statsd";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.statsd ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/sysstat.nix b/nixpkgs/nixos/modules/services/monitoring/sysstat.nix
new file mode 100644
index 00000000000..d668faa53cc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/sysstat.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.sysstat;
+in {
+ options = {
+ services.sysstat = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable sar system activity collection.
+ '';
+ };
+
+ collect-frequency = mkOption {
+ default = "*:00/10";
+ description = ''
+ OnCalendar specification for sysstat-collect
+ '';
+ };
+
+ collect-args = mkOption {
+ default = "1 1";
+ description = ''
+ Arguments to pass sa1 when collecting statistics
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.sysstat = {
+ description = "Resets System Activity Logs";
+ wantedBy = [ "multi-user.target" ];
+ preStart = "test -d /var/log/sa || mkdir -p /var/log/sa";
+
+ serviceConfig = {
+ User = "root";
+ RemainAfterExit = true;
+ Type = "oneshot";
+ ExecStart = "${pkgs.sysstat}/lib/sa/sa1 --boot";
+ };
+ };
+
+ systemd.services.sysstat-collect = {
+ description = "system activity accounting tool";
+ unitConfig.Documentation = "man:sa1(8)";
+
+ serviceConfig = {
+ Type = "oneshot";
+ User = "root";
+ ExecStart = "${pkgs.sysstat}/lib/sa/sa1 ${cfg.collect-args}";
+ };
+ };
+
+ systemd.timers.sysstat-collect = {
+ description = "Run system activity accounting tool on a regular basis";
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnCalendar = cfg.collect-frequency;
+ };
+
+ systemd.services.sysstat-summary = {
+ description = "Generate a daily summary of process accounting";
+ unitConfig.Documentation = "man:sa2(8)";
+
+ serviceConfig = {
+ Type = "oneshot";
+ User = "root";
+ ExecStart = "${pkgs.sysstat}/lib/sa/sa2 -A";
+ };
+ };
+
+ systemd.timers.sysstat-summary = {
+ description = "Generate summary of yesterday's process accounting";
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnCalendar = "00:07:00";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/teamviewer.nix b/nixpkgs/nixos/modules/services/monitoring/teamviewer.nix
new file mode 100644
index 00000000000..dd98ecab828
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/teamviewer.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.teamviewer;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.teamviewer.enable = mkEnableOption "TeamViewer daemon";
+
+ };
+
+ ###### implementation
+
+ config = mkIf (cfg.enable) {
+
+ environment.systemPackages = [ pkgs.teamviewer ];
+
+ systemd.services.teamviewerd = {
+ description = "TeamViewer remote control daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "NetworkManager-wait-online.service" "network.target" ];
+ preStart = "mkdir -pv /var/lib/teamviewer /var/log/teamviewer";
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.teamviewer}/bin/teamviewerd -d";
+ PIDFile = "/run/teamviewerd.pid";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ Restart = "on-abort";
+ StartLimitInterval = "60";
+ StartLimitBurst = "10";
+ };
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/telegraf.nix b/nixpkgs/nixos/modules/services/monitoring/telegraf.nix
new file mode 100644
index 00000000000..d8786732668
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/telegraf.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.telegraf;
+
+ configFile = pkgs.runCommand "config.toml" {
+ buildInputs = [ pkgs.remarshal ];
+ preferLocalBuild = true;
+ } ''
+ remarshal -if json -of toml \
+ < ${pkgs.writeText "config.json" (builtins.toJSON cfg.extraConfig)} \
+ > $out
+ '';
+in {
+ ###### interface
+ options = {
+ services.telegraf = {
+ enable = mkEnableOption "telegraf server";
+
+ package = mkOption {
+ default = pkgs.telegraf;
+ defaultText = "pkgs.telegraf";
+ description = "Which telegraf derivation to use";
+ type = types.package;
+ };
+
+ extraConfig = mkOption {
+ default = {};
+ description = "Extra configuration options for telegraf";
+ type = types.attrs;
+ example = {
+ outputs = {
+ influxdb = {
+ urls = ["http://localhost:8086"];
+ database = "telegraf";
+ };
+ };
+ inputs = {
+ statsd = {
+ service_address = ":8125";
+ delete_timings = true;
+ };
+ };
+ };
+ };
+ };
+ };
+
+
+ ###### implementation
+ config = mkIf config.services.telegraf.enable {
+ systemd.services.telegraf = {
+ description = "Telegraf Agent";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target" ];
+ serviceConfig = {
+ ExecStart=''${cfg.package}/bin/telegraf -config "${configFile}"'';
+ ExecReload="${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ User = "telegraf";
+ Restart = "on-failure";
+ };
+ };
+
+ users.users = [{
+ name = "telegraf";
+ uid = config.ids.uids.telegraf;
+ description = "telegraf daemon user";
+ }];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/thanos.nix b/nixpkgs/nixos/modules/services/monitoring/thanos.nix
new file mode 100644
index 00000000000..215cd43fd86
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/thanos.nix
@@ -0,0 +1,801 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.thanos;
+
+ nullOpt = type: description: mkOption {
+ type = types.nullOr type;
+ default = null;
+ inherit description;
+ };
+
+ optionToArgs = opt: v : optional (v != null) ''--${opt}="${toString v}"'';
+ flagToArgs = opt: v : optional v ''--${opt}'';
+ listToArgs = opt: vs : map (v: ''--${opt}="${v}"'') vs;
+ attrsToArgs = opt: kvs: mapAttrsToList (k: v: ''--${opt}=${k}=\"${v}\"'') kvs;
+
+ mkParamDef = type: default: description: mkParam type (description + ''
+
+ Defaults to <literal>${toString default}</literal> in Thanos
+ when set to <literal>null</literal>.
+ '');
+
+ mkParam = type: description: {
+ toArgs = optionToArgs;
+ option = nullOpt type description;
+ };
+
+ mkFlagParam = description: {
+ toArgs = flagToArgs;
+ option = mkOption {
+ type = types.bool;
+ default = false;
+ inherit description;
+ };
+ };
+
+ mkListParam = opt: description: {
+ toArgs = _opt: listToArgs opt;
+ option = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ inherit description;
+ };
+ };
+
+ mkAttrsParam = opt: description: {
+ toArgs = _opt: attrsToArgs opt;
+ option = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ inherit description;
+ };
+ };
+
+ mkStateDirParam = opt: default: description: {
+ toArgs = _opt: stateDir: optionToArgs opt "/var/lib/${stateDir}";
+ option = mkOption {
+ type = types.str;
+ inherit default;
+ inherit description;
+ };
+ };
+
+ toYAML = name: attrs: pkgs.runCommandNoCC name {
+ preferLocalBuild = true;
+ json = builtins.toFile "${name}.json" (builtins.toJSON attrs);
+ nativeBuildInputs = [ pkgs.remarshal ];
+ } ''json2yaml -i $json -o $out'';
+
+ thanos = cmd: "${cfg.package}/bin/thanos ${cmd}" +
+ (let args = cfg.${cmd}.arguments;
+ in optionalString (length args != 0) (" \\\n " +
+ concatStringsSep " \\\n " args));
+
+ argumentsOf = cmd: concatLists (collect isList
+ (flip mapParamsRecursive params.${cmd} (path: param:
+ let opt = concatStringsSep "." path;
+ v = getAttrFromPath path cfg.${cmd};
+ in param.toArgs opt v)));
+
+ mkArgumentsOption = cmd: mkOption {
+ type = types.listOf types.str;
+ default = argumentsOf cmd;
+ description = ''
+ Arguments to the <literal>thanos ${cmd}</literal> command.
+
+ Defaults to a list of arguments formed by converting the structured
+ options of <option>services.thanos.${cmd}</option> to a list of arguments.
+
+ Overriding this option will cause none of the structured options to have
+ any effect. So only set this if you know what you're doing!
+ '';
+ };
+
+ mapParamsRecursive =
+ let noParam = attr: !(attr ? toArgs && attr ? option);
+ in mapAttrsRecursiveCond noParam;
+
+ paramsToOptions = mapParamsRecursive (_path: param: param.option);
+
+ params = {
+
+ log = {
+
+ log.level = mkParamDef (types.enum ["debug" "info" "warn" "error" "fatal"]) "info" ''
+ Log filtering level.
+ '';
+
+ log.format = mkParam types.str ''
+ Log format to use.
+ '';
+ };
+
+ tracing = cfg: {
+ tracing.config-file = {
+ toArgs = _opt: path: optionToArgs "tracing.config-file" path;
+ option = mkOption {
+ type = with types; nullOr str;
+ default = if cfg.tracing.config == null then null
+ else toString (toYAML "tracing.yaml" cfg.tracing.config);
+ defaultText = ''
+ if config.services.thanos.<cmd>.tracing.config == null then null
+ else toString (toYAML "tracing.yaml" config.services.thanos.<cmd>.tracing.config);
+ '';
+ description = ''
+ Path to YAML file that contains tracing configuration.
+ '';
+ };
+ };
+
+ tracing.config =
+ {
+ toArgs = _opt: _attrs: [];
+ option = nullOpt types.attrs ''
+ Tracing configuration.
+
+ When not <literal>null</literal> the attribute set gets converted to
+ a YAML file and stored in the Nix store. The option
+ <option>tracing.config-file</option> will default to its path.
+
+ If <option>tracing.config-file</option> is set this option has no effect.
+ '';
+ };
+ };
+
+ common = cfg: params.log // params.tracing cfg // {
+
+ http-address = mkParamDef types.str "0.0.0.0:10902" ''
+ Listen <literal>host:port</literal> for HTTP endpoints.
+ '';
+
+ grpc-address = mkParamDef types.str "0.0.0.0:10901" ''
+ Listen <literal>ip:port</literal> address for gRPC endpoints (StoreAPI).
+
+ Make sure this address is routable from other components.
+ '';
+
+ grpc-server-tls-cert = mkParam types.str ''
+ TLS Certificate for gRPC server, leave blank to disable TLS
+ '';
+
+ grpc-server-tls-key = mkParam types.str ''
+ TLS Key for the gRPC server, leave blank to disable TLS
+ '';
+
+ grpc-server-tls-client-ca = mkParam types.str ''
+ TLS CA to verify clients against.
+
+ If no client CA is specified, there is no client verification on server side.
+ (tls.NoClientCert)
+ '';
+ };
+
+ objstore = cfg: {
+
+ objstore.config-file = {
+ toArgs = _opt: path: optionToArgs "objstore.config-file" path;
+ option = mkOption {
+ type = with types; nullOr str;
+ default = if cfg.objstore.config == null then null
+ else toString (toYAML "objstore.yaml" cfg.objstore.config);
+ defaultText = ''
+ if config.services.thanos.<cmd>.objstore.config == null then null
+ else toString (toYAML "objstore.yaml" config.services.thanos.<cmd>.objstore.config);
+ '';
+ description = ''
+ Path to YAML file that contains object store configuration.
+ '';
+ };
+ };
+
+ objstore.config =
+ {
+ toArgs = _opt: _attrs: [];
+ option = nullOpt types.attrs ''
+ Object store configuration.
+
+ When not <literal>null</literal> the attribute set gets converted to
+ a YAML file and stored in the Nix store. The option
+ <option>objstore.config-file</option> will default to its path.
+
+ If <option>objstore.config-file</option> is set this option has no effect.
+ '';
+ };
+ };
+
+ sidecar = params.common cfg.sidecar // params.objstore cfg.sidecar // {
+
+ prometheus.url = mkParamDef types.str "http://localhost:9090" ''
+ URL at which to reach Prometheus's API.
+
+ For better performance use local network.
+ '';
+
+ tsdb.path = {
+ toArgs = optionToArgs;
+ option = mkOption {
+ type = types.str;
+ default = "/var/lib/${config.services.prometheus.stateDir}/data";
+ defaultText = "/var/lib/\${config.services.prometheus.stateDir}/data";
+ description = ''
+ Data directory of TSDB.
+ '';
+ };
+ };
+
+ reloader.config-file = mkParam types.str ''
+ Config file watched by the reloader.
+ '';
+
+ reloader.config-envsubst-file = mkParam types.str ''
+ Output file for environment variable substituted config file.
+ '';
+
+ reloader.rule-dirs = mkListParam "reloader.rule-dir" ''
+ Rule directories for the reloader to refresh.
+ '';
+
+ };
+
+ store = params.common cfg.store // params.objstore cfg.store // {
+
+ stateDir = mkStateDirParam "data-dir" "thanos-store" ''
+ Data directory relative to <literal>/var/lib</literal>
+ in which to cache remote blocks.
+ '';
+
+ index-cache-size = mkParamDef types.str "250MB" ''
+ Maximum size of items held in the index cache.
+ '';
+
+ chunk-pool-size = mkParamDef types.str "2GB" ''
+ Maximum size of concurrently allocatable bytes for chunks.
+ '';
+
+ store.grpc.series-sample-limit = mkParamDef types.int 0 ''
+ Maximum amount of samples returned via a single Series call.
+
+ <literal>0</literal> means no limit.
+
+ NOTE: for efficiency we take 120 as the number of samples in chunk (it
+ cannot be bigger than that), so the actual number of samples might be
+ lower, even though the maximum could be hit.
+ '';
+
+ store.grpc.series-max-concurrency = mkParamDef types.int 20 ''
+ Maximum number of concurrent Series calls.
+ '';
+
+ sync-block-duration = mkParamDef types.str "3m" ''
+ Repeat interval for syncing the blocks between local and remote view.
+ '';
+
+ block-sync-concurrency = mkParamDef types.int 20 ''
+ Number of goroutines to use when syncing blocks from object storage.
+ '';
+ };
+
+ query = params.common cfg.query // {
+
+ grpc-client-tls-secure = mkFlagParam ''
+ Use TLS when talking to the gRPC server
+ '';
+
+ grpc-client-tls-cert = mkParam types.str ''
+ TLS Certificates to use to identify this client to the server
+ '';
+
+ grpc-client-tls-key = mkParam types.str ''
+ TLS Key for the client's certificate
+ '';
+
+ grpc-client-tls-ca = mkParam types.str ''
+ TLS CA Certificates to use to verify gRPC servers
+ '';
+
+ grpc-client-server-name = mkParam types.str ''
+ Server name to verify the hostname on the returned gRPC certificates.
+ See <link xlink:href="https://tools.ietf.org/html/rfc4366#section-3.1"/>
+ '';
+
+ web.route-prefix = mkParam types.str ''
+ Prefix for API and UI endpoints.
+
+ This allows thanos UI to be served on a sub-path. This option is
+ analogous to <option>web.route-prefix</option> of Promethus.
+ '';
+
+ web.external-prefix = mkParam types.str ''
+ Static prefix for all HTML links and redirect URLs in the UI query web
+ interface.
+
+ Actual endpoints are still served on / or the
+ <option>web.route-prefix</option>. This allows thanos UI to be served
+ behind a reverse proxy that strips a URL sub-path.
+ '';
+
+ web.prefix-header = mkParam types.str ''
+ Name of HTTP request header used for dynamic prefixing of UI links and
+ redirects.
+
+ This option is ignored if the option
+ <literal>web.external-prefix</literal> is set.
+
+ Security risk: enable this option only if a reverse proxy in front of
+ thanos is resetting the header.
+
+ The setting <literal>web.prefix-header="X-Forwarded-Prefix"</literal>
+ can be useful, for example, if Thanos UI is served via Traefik reverse
+ proxy with <literal>PathPrefixStrip</literal> option enabled, which
+ sends the stripped prefix value in <literal>X-Forwarded-Prefix</literal>
+ header. This allows thanos UI to be served on a sub-path.
+ '';
+
+ query.timeout = mkParamDef types.str "2m" ''
+ Maximum time to process query by query node.
+ '';
+
+ query.max-concurrent = mkParamDef types.int 20 ''
+ Maximum number of queries processed concurrently by query node.
+ '';
+
+ query.replica-label = mkParam types.str ''
+ Label to treat as a replica indicator along which data is
+ deduplicated.
+
+ Still you will be able to query without deduplication using
+ <literal>dedup=false</literal> parameter.
+ '';
+
+ selector-labels = mkAttrsParam "selector-label" ''
+ Query selector labels that will be exposed in info endpoint.
+ '';
+
+ store.addresses = mkListParam "store" ''
+ Addresses of statically configured store API servers.
+
+ The scheme may be prefixed with <literal>dns+</literal> or
+ <literal>dnssrv+</literal> to detect store API servers through
+ respective DNS lookups.
+ '';
+
+ store.sd-files = mkListParam "store.sd-files" ''
+ Path to files that contain addresses of store API servers. The path
+ can be a glob pattern.
+ '';
+
+ store.sd-interval = mkParamDef types.str "5m" ''
+ Refresh interval to re-read file SD files. It is used as a resync fallback.
+ '';
+
+ store.sd-dns-interval = mkParamDef types.str "30s" ''
+ Interval between DNS resolutions.
+ '';
+
+ store.unhealthy-timeout = mkParamDef types.str "5m" ''
+ Timeout before an unhealthy store is cleaned from the store UI page.
+ '';
+
+ query.auto-downsampling = mkFlagParam ''
+ Enable automatic adjustment (step / 5) to what source of data should
+ be used in store gateways if no
+ <literal>max_source_resolution</literal> param is specified.
+ '';
+
+ query.partial-response = mkFlagParam ''
+ Enable partial response for queries if no
+ <literal>partial_response</literal> param is specified.
+ '';
+
+ query.default-evaluation-interval = mkParamDef types.str "1m" ''
+ Set default evaluation interval for sub queries.
+ '';
+
+ store.response-timeout = mkParamDef types.str "0ms" ''
+ If a Store doesn't send any data in this specified duration then a
+ Store will be ignored and partial data will be returned if it's
+ enabled. <literal>0</literal> disables timeout.
+ '';
+ };
+
+ rule = params.common cfg.rule // params.objstore cfg.rule // {
+
+ labels = mkAttrsParam "label" ''
+ Labels to be applied to all generated metrics.
+
+ Similar to external labels for Prometheus,
+ used to identify ruler and its blocks as unique source.
+ '';
+
+ stateDir = mkStateDirParam "data-dir" "thanos-rule" ''
+ Data directory relative to <literal>/var/lib</literal>.
+ '';
+
+ rule-files = mkListParam "rule-file" ''
+ Rule files that should be used by rule manager. Can be in glob format.
+ '';
+
+ eval-interval = mkParamDef types.str "30s" ''
+ The default evaluation interval to use.
+ '';
+
+ tsdb.block-duration = mkParamDef types.str "2h" ''
+ Block duration for TSDB block.
+ '';
+
+ tsdb.retention = mkParamDef types.str "48h" ''
+ Block retention time on local disk.
+ '';
+
+ alertmanagers.urls = mkListParam "alertmanagers.url" ''
+ Alertmanager replica URLs to push firing alerts.
+
+ Ruler claims success if push to at least one alertmanager from
+ discovered succeeds. The scheme may be prefixed with
+ <literal>dns+</literal> or <literal>dnssrv+</literal> to detect
+ Alertmanager IPs through respective DNS lookups. The port defaults to
+ <literal>9093</literal> or the SRV record's value. The URL path is
+ used as a prefix for the regular Alertmanager API path.
+ '';
+
+ alertmanagers.send-timeout = mkParamDef types.str "10s" ''
+ Timeout for sending alerts to alertmanager.
+ '';
+
+ alert.query-url = mkParam types.str ''
+ The external Thanos Query URL that would be set in all alerts 'Source' field.
+ '';
+
+ alert.label-drop = mkListParam "alert.label-drop" ''
+ Labels by name to drop before sending to alertmanager.
+
+ This allows alert to be deduplicated on replica label.
+
+ Similar Prometheus alert relabelling
+ '';
+
+ web.route-prefix = mkParam types.str ''
+ Prefix for API and UI endpoints.
+
+ This allows thanos UI to be served on a sub-path.
+
+ This option is analogous to <literal>--web.route-prefix</literal> of Promethus.
+ '';
+
+ web.external-prefix = mkParam types.str ''
+ Static prefix for all HTML links and redirect URLs in the UI query web
+ interface.
+
+ Actual endpoints are still served on / or the
+ <option>web.route-prefix</option>. This allows thanos UI to be served
+ behind a reverse proxy that strips a URL sub-path.
+ '';
+
+ web.prefix-header = mkParam types.str ''
+ Name of HTTP request header used for dynamic prefixing of UI links and
+ redirects.
+
+ This option is ignored if the option
+ <option>web.external-prefix</option> is set.
+
+ Security risk: enable this option only if a reverse proxy in front of
+ thanos is resetting the header.
+
+ The header <literal>X-Forwarded-Prefix</literal> can be useful, for
+ example, if Thanos UI is served via Traefik reverse proxy with
+ <literal>PathPrefixStrip</literal> option enabled, which sends the
+ stripped prefix value in <literal>X-Forwarded-Prefix</literal>
+ header. This allows thanos UI to be served on a sub-path.
+ '';
+
+ query.addresses = mkListParam "query" ''
+ Addresses of statically configured query API servers.
+
+ The scheme may be prefixed with <literal>dns+</literal> or
+ <literal>dnssrv+</literal> to detect query API servers through
+ respective DNS lookups.
+ '';
+
+ query.sd-files = mkListParam "query.sd-files" ''
+ Path to file that contain addresses of query peers.
+ The path can be a glob pattern.
+ '';
+
+ query.sd-interval = mkParamDef types.str "5m" ''
+ Refresh interval to re-read file SD files. (used as a fallback)
+ '';
+
+ query.sd-dns-interval = mkParamDef types.str "30s" ''
+ Interval between DNS resolutions.
+ '';
+ };
+
+ compact = params.log // params.tracing cfg.compact // params.objstore cfg.compact // {
+
+ http-address = mkParamDef types.str "0.0.0.0:10902" ''
+ Listen <literal>host:port</literal> for HTTP endpoints.
+ '';
+
+ stateDir = mkStateDirParam "data-dir" "thanos-compact" ''
+ Data directory relative to <literal>/var/lib</literal>
+ in which to cache blocks and process compactions.
+ '';
+
+ consistency-delay = mkParamDef types.str "30m" ''
+ Minimum age of fresh (non-compacted) blocks before they are being
+ processed. Malformed blocks older than the maximum of consistency-delay
+ and 30m0s will be removed.
+ '';
+
+ retention.resolution-raw = mkParamDef types.str "0d" ''
+ How long to retain raw samples in bucket.
+
+ <literal>0d</literal> - disables this retention
+ '';
+
+ retention.resolution-5m = mkParamDef types.str "0d" ''
+ How long to retain samples of resolution 1 (5 minutes) in bucket.
+
+ <literal>0d</literal> - disables this retention
+ '';
+
+ retention.resolution-1h = mkParamDef types.str "0d" ''
+ How long to retain samples of resolution 2 (1 hour) in bucket.
+
+ <literal>0d</literal> - disables this retention
+ '';
+
+ startAt = {
+ toArgs = _opt: startAt: flagToArgs "wait" (startAt == null);
+ option = nullOpt types.str ''
+ When this option is set to a <literal>systemd.time</literal>
+ specification the Thanos compactor will run at the specified period.
+
+ When this option is <literal>null</literal> the Thanos compactor service
+ will run continuously. So it will not exit after all compactions have
+ been processed but wait for new work.
+ '';
+ };
+
+ block-sync-concurrency = mkParamDef types.int 20 ''
+ Number of goroutines to use when syncing block metadata from object storage.
+ '';
+
+ compact.concurrency = mkParamDef types.int 1 ''
+ Number of goroutines to use when compacting groups.
+ '';
+ };
+
+ downsample = params.log // params.tracing cfg.downsample // params.objstore cfg.downsample // {
+
+ stateDir = mkStateDirParam "data-dir" "thanos-downsample" ''
+ Data directory relative to <literal>/var/lib</literal>
+ in which to cache blocks and process downsamplings.
+ '';
+
+ };
+
+ receive = params.common cfg.receive // params.objstore cfg.receive // {
+
+ remote-write.address = mkParamDef types.str "0.0.0.0:19291" ''
+ Address to listen on for remote write requests.
+ '';
+
+ stateDir = mkStateDirParam "tsdb.path" "thanos-receive" ''
+ Data directory relative to <literal>/var/lib</literal> of TSDB.
+ '';
+
+ labels = mkAttrsParam "labels" ''
+ External labels to announce.
+
+ This flag will be removed in the future when handling multiple tsdb
+ instances is added.
+ '';
+
+ tsdb.retention = mkParamDef types.str "15d" ''
+ How long to retain raw samples on local storage.
+
+ <literal>0d</literal> - disables this retention
+ '';
+ };
+
+ };
+
+ assertRelativeStateDir = cmd: {
+ assertions = [
+ {
+ assertion = !hasPrefix "/" cfg.${cmd}.stateDir;
+ message =
+ "The option services.thanos.${cmd}.stateDir should not be an absolute directory." +
+ " It should be a directory relative to /var/lib.";
+ }
+ ];
+ };
+
+in {
+
+ options.services.thanos = {
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.thanos;
+ defaultText = "pkgs.thanos";
+ description = ''
+ The thanos package that should be used.
+ '';
+ };
+
+ sidecar = paramsToOptions params.sidecar // {
+ enable = mkEnableOption
+ "the Thanos sidecar for Prometheus server";
+ arguments = mkArgumentsOption "sidecar";
+ };
+
+ store = paramsToOptions params.store // {
+ enable = mkEnableOption
+ "the Thanos store node giving access to blocks in a bucket provider.";
+ arguments = mkArgumentsOption "store";
+ };
+
+ query = paramsToOptions params.query // {
+ enable = mkEnableOption
+ ("the Thanos query node exposing PromQL enabled Query API " +
+ "with data retrieved from multiple store nodes");
+ arguments = mkArgumentsOption "query";
+ };
+
+ rule = paramsToOptions params.rule // {
+ enable = mkEnableOption
+ ("the Thanos ruler service which evaluates Prometheus rules against" +
+ " given Query nodes, exposing Store API and storing old blocks in bucket");
+ arguments = mkArgumentsOption "rule";
+ };
+
+ compact = paramsToOptions params.compact // {
+ enable = mkEnableOption
+ "the Thanos compactor which continuously compacts blocks in an object store bucket";
+ arguments = mkArgumentsOption "compact";
+ };
+
+ downsample = paramsToOptions params.downsample // {
+ enable = mkEnableOption
+ "the Thanos downsampler which continuously downsamples blocks in an object store bucket";
+ arguments = mkArgumentsOption "downsample";
+ };
+
+ receive = paramsToOptions params.receive // {
+ enable = mkEnableOption
+ ("the Thanos receiver which accept Prometheus remote write API requests " +
+ "and write to local tsdb (EXPERIMENTAL, this may change drastically without notice)");
+ arguments = mkArgumentsOption "receive";
+ };
+ };
+
+ config = mkMerge [
+
+ (mkIf cfg.sidecar.enable {
+ assertions = [
+ {
+ assertion = config.services.prometheus.enable;
+ message =
+ "Please enable services.prometheus when enabling services.thanos.sidecar.";
+ }
+ {
+ assertion = !(config.services.prometheus.globalConfig.external_labels == null ||
+ config.services.prometheus.globalConfig.external_labels == {});
+ message =
+ "services.thanos.sidecar requires uniquely identifying external labels " +
+ "to be configured in the Prometheus server. " +
+ "Please set services.prometheus.globalConfig.external_labels.";
+ }
+ ];
+ systemd.services.thanos-sidecar = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "prometheus.service" ];
+ serviceConfig = {
+ User = "prometheus";
+ Restart = "always";
+ ExecStart = thanos "sidecar";
+ };
+ };
+ })
+
+ (mkIf cfg.store.enable (mkMerge [
+ (assertRelativeStateDir "store")
+ {
+ systemd.services.thanos-store = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = cfg.store.stateDir;
+ Restart = "always";
+ ExecStart = thanos "store";
+ };
+ };
+ }
+ ]))
+
+ (mkIf cfg.query.enable {
+ systemd.services.thanos-query = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ DynamicUser = true;
+ Restart = "always";
+ ExecStart = thanos "query";
+ };
+ };
+ })
+
+ (mkIf cfg.rule.enable (mkMerge [
+ (assertRelativeStateDir "rule")
+ {
+ systemd.services.thanos-rule = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = cfg.rule.stateDir;
+ Restart = "always";
+ ExecStart = thanos "rule";
+ };
+ };
+ }
+ ]))
+
+ (mkIf cfg.compact.enable (mkMerge [
+ (assertRelativeStateDir "compact")
+ {
+ systemd.services.thanos-compact =
+ let wait = cfg.compact.startAt == null; in {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ Type = if wait then "simple" else "oneshot";
+ Restart = if wait then "always" else "no";
+ DynamicUser = true;
+ StateDirectory = cfg.compact.stateDir;
+ ExecStart = thanos "compact";
+ };
+ } // optionalAttrs (!wait) { inherit (cfg.compact) startAt; };
+ }
+ ]))
+
+ (mkIf cfg.downsample.enable (mkMerge [
+ (assertRelativeStateDir "downsample")
+ {
+ systemd.services.thanos-downsample = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = cfg.downsample.stateDir;
+ Restart = "always";
+ ExecStart = thanos "downsample";
+ };
+ };
+ }
+ ]))
+
+ (mkIf cfg.receive.enable (mkMerge [
+ (assertRelativeStateDir "receive")
+ {
+ systemd.services.thanos-receive = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = cfg.receive.stateDir;
+ Restart = "always";
+ ExecStart = thanos "receive";
+ };
+ };
+ }
+ ]))
+
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/ups.nix b/nixpkgs/nixos/modules/services/monitoring/ups.nix
new file mode 100644
index 00000000000..1bdc4e4410f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/ups.nix
@@ -0,0 +1,280 @@
+{ config, lib, pkgs, ... }:
+
+# TODO: This is not secure, have a look at the file docs/security.txt inside
+# the project sources.
+with lib;
+
+let
+ cfg = config.power.ups;
+in
+
+let
+ upsOptions = {name, config, ...}:
+ {
+ options = {
+ # This can be infered from the UPS model by looking at
+ # /nix/store/nut/share/driver.list
+ driver = mkOption {
+ type = types.str;
+ description = ''
+ Specify the program to run to talk to this UPS. apcsmart,
+ bestups, and sec are some examples.
+ '';
+ };
+
+ port = mkOption {
+ type = types.str;
+ description = ''
+ The serial port to which your UPS is connected. /dev/ttyS0 is
+ usually the first port on Linux boxes, for example.
+ '';
+ };
+
+ shutdownOrder = mkOption {
+ default = 0;
+ type = types.int;
+ description = ''
+ When you have multiple UPSes on your system, you usually need to
+ turn them off in a certain order. upsdrvctl shuts down all the
+ 0s, then the 1s, 2s, and so on. To exclude a UPS from the
+ shutdown sequence, set this to -1.
+ '';
+ };
+
+ maxStartDelay = mkOption {
+ default = null;
+ type = types.uniq (types.nullOr types.int);
+ description = ''
+ This can be set as a global variable above your first UPS
+ definition and it can also be set in a UPS section. This value
+ controls how long upsdrvctl will wait for the driver to finish
+ starting. This keeps your system from getting stuck due to a
+ broken driver or UPS.
+ '';
+ };
+
+ description = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Description of the UPS.
+ '';
+ };
+
+ directives = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ List of configuration directives for this UPS.
+ '';
+ };
+
+ summary = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Lines which would be added inside ups.conf for handling this UPS.
+ '';
+ };
+
+ };
+
+ config = {
+ directives = mkOrder 10 ([
+ "driver = ${config.driver}"
+ "port = ${config.port}"
+ ''desc = "${config.description}"''
+ "sdorder = ${toString config.shutdownOrder}"
+ ] ++ (optional (config.maxStartDelay != null)
+ "maxstartdelay = ${toString config.maxStartDelay}")
+ );
+
+ summary =
+ concatStringsSep "\n "
+ (["[${name}]"] ++ config.directives);
+ };
+ };
+
+in
+
+
+{
+ options = {
+ # powerManagement.powerDownCommands
+
+ power.ups = {
+ enable = mkOption {
+ default = false;
+ type = with types; bool;
+ description = ''
+ Enables support for Power Devices, such as Uninterruptible Power
+ Supplies, Power Distribution Units and Solar Controllers.
+ '';
+ };
+
+ # This option is not used yet.
+ mode = mkOption {
+ default = "standalone";
+ type = types.str;
+ description = ''
+ The MODE determines which part of the NUT is to be started, and
+ which configuration files must be modified.
+
+ The values of MODE can be:
+
+ - none: NUT is not configured, or use the Integrated Power
+ Management, or use some external system to startup NUT
+ components. So nothing is to be started.
+
+ - standalone: This mode address a local only configuration, with 1
+ UPS protecting the local system. This implies to start the 3 NUT
+ layers (driver, upsd and upsmon) and the matching configuration
+ files. This mode can also address UPS redundancy.
+
+ - netserver: same as for the standalone configuration, but also
+ need some more ACLs and possibly a specific LISTEN directive in
+ upsd.conf. Since this MODE is opened to the network, a special
+ care should be applied to security concerns.
+
+ - netclient: this mode only requires upsmon.
+ '';
+ };
+
+ schedulerRules = mkOption {
+ example = "/etc/nixos/upssched.conf";
+ type = types.str;
+ description = ''
+ File which contains the rules to handle UPS events.
+ '';
+ };
+
+
+ maxStartDelay = mkOption {
+ default = 45;
+ type = types.int;
+ description = ''
+ This can be set as a global variable above your first UPS
+ definition and it can also be set in a UPS section. This value
+ controls how long upsdrvctl will wait for the driver to finish
+ starting. This keeps your system from getting stuck due to a
+ broken driver or UPS.
+ '';
+ };
+
+ ups = mkOption {
+ default = {};
+ # see nut/etc/ups.conf.sample
+ description = ''
+ This is where you configure all the UPSes that this system will be
+ monitoring directly. These are usually attached to serial ports,
+ but USB devices are also supported.
+ '';
+ type = with types; attrsOf (submodule upsOptions);
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.nut ];
+
+ systemd.services.upsmon = {
+ description = "Uninterruptible Power Supplies (Monitor)";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.Type = "forking";
+ script = "${pkgs.nut}/sbin/upsmon";
+ environment.NUT_CONFPATH = "/etc/nut/";
+ environment.NUT_STATEPATH = "/var/lib/nut/";
+ };
+
+ systemd.services.upsd = {
+ description = "Uninterruptible Power Supplies (Daemon)";
+ after = [ "network.target" "upsmon.service" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.Type = "forking";
+ # TODO: replace 'root' by another username.
+ script = "${pkgs.nut}/sbin/upsd -u root";
+ environment.NUT_CONFPATH = "/etc/nut/";
+ environment.NUT_STATEPATH = "/var/lib/nut/";
+ };
+
+ systemd.services.upsdrv = {
+ description = "Uninterruptible Power Supplies (Register all UPS)";
+ after = [ "upsd.service" ];
+ wantedBy = [ "multi-user.target" ];
+ # TODO: replace 'root' by another username.
+ script = ''${pkgs.nut}/bin/upsdrvctl -u root start'';
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ environment.NUT_CONFPATH = "/etc/nut/";
+ environment.NUT_STATEPATH = "/var/lib/nut/";
+ };
+
+ environment.etc = [
+ { source = pkgs.writeText "nut.conf"
+ ''
+ MODE = ${cfg.mode}
+ '';
+ target = "nut/nut.conf";
+ }
+ { source = pkgs.writeText "ups.conf"
+ ''
+ maxstartdelay = ${toString cfg.maxStartDelay}
+
+ ${flip concatStringsSep (forEach (attrValues cfg.ups) (ups: ups.summary)) "
+
+ "}
+ '';
+ target = "nut/ups.conf";
+ }
+ { source = cfg.schedulerRules;
+ target = "nut/upssched.conf";
+ }
+ # These file are containing private informations and thus should not
+ # be stored inside the Nix store.
+ /*
+ { source = ;
+ target = "nut/upsd.conf";
+ }
+ { source = ;
+ target = "nut/upsd.users";
+ }
+ { source = ;
+ target = "nut/upsmon.conf;
+ }
+ */
+ ];
+
+ power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample";
+
+ system.activationScripts.upsSetup = stringAfter [ "users" "groups" ]
+ ''
+ # Used to store pid files of drivers.
+ mkdir -p /var/state/ups
+ '';
+
+
+/*
+ users.users = [
+ { name = "nut";
+ uid = 84;
+ home = "/var/lib/nut";
+ createHome = true;
+ group = "nut";
+ description = "UPnP A/V Media Server user";
+ }
+ ];
+
+ users.groups = [
+ { name = "nut";
+ gid = 84;
+ }
+ ];
+*/
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/uptime.nix b/nixpkgs/nixos/modules/services/monitoring/uptime.nix
new file mode 100644
index 00000000000..245badc3e44
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/uptime.nix
@@ -0,0 +1,96 @@
+{ config, pkgs, lib, ... }:
+let
+ inherit (lib) mkOption mkEnableOption mkIf mkMerge types optional;
+
+ cfg = config.services.uptime;
+
+ configDir = pkgs.runCommand "config" { preferLocalBuild = true; }
+ (if cfg.configFile != null then ''
+ mkdir $out
+ ext=`echo ${cfg.configFile} | grep -o \\..*`
+ ln -sv ${cfg.configFile} $out/default$ext
+ ln -sv /var/lib/uptime/runtime.json $out/runtime.json
+ '' else ''
+ mkdir $out
+ cat ${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/config/default.yaml > $out/default.yaml
+ cat >> $out/default.yaml <<EOF
+
+ autoStartMonitor: false
+
+ mongodb:
+ connectionString: 'mongodb://localhost/uptime'
+ EOF
+ ln -sv /var/lib/uptime/runtime.json $out/runtime.json
+ '');
+in {
+ options.services.uptime = {
+ configFile = mkOption {
+ description = ''
+ The uptime configuration file
+
+ If mongodb: server != localhost, please set usesRemoteMongo = true
+
+ If you only want to run the monitor, please set enableWebService = false
+ and enableSeparateMonitoringService = true
+
+ If autoStartMonitor: false (recommended) and you want to run both
+ services, please set enableSeparateMonitoringService = true
+ '';
+
+ type = types.nullOr types.path;
+
+ default = null;
+ };
+
+ usesRemoteMongo = mkOption {
+ description = "Whether the configuration file specifies a remote mongo instance";
+
+ default = false;
+
+ type = types.bool;
+ };
+
+ enableWebService = mkEnableOption "the uptime monitoring program web service";
+
+ enableSeparateMonitoringService = mkEnableOption "the uptime monitoring service" // { default = cfg.enableWebService; };
+
+ nodeEnv = mkOption {
+ description = "The node environment to run in (development, production, etc.)";
+
+ type = types.str;
+
+ default = "production";
+ };
+ };
+
+ config = mkMerge [ (mkIf cfg.enableWebService {
+ systemd.services.uptime = {
+ description = "uptime web service";
+ wantedBy = [ "multi-user.target" ];
+ environment = {
+ NODE_CONFIG_DIR = configDir;
+ NODE_ENV = cfg.nodeEnv;
+ NODE_PATH = "${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/node_modules";
+ };
+ preStart = "mkdir -p /var/lib/uptime";
+ serviceConfig.ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/app.js";
+ };
+
+ services.mongodb.enable = mkIf (!cfg.usesRemoteMongo) true;
+ }) (mkIf cfg.enableSeparateMonitoringService {
+ systemd.services.uptime-monitor = {
+ description = "uptime monitoring service";
+ wantedBy = [ "multi-user.target" ];
+ requires = optional cfg.enableWebService "uptime.service";
+ after = optional cfg.enableWebService "uptime.service";
+ environment = {
+ NODE_CONFIG_DIR = configDir;
+ NODE_ENV = cfg.nodeEnv;
+ NODE_PATH = "${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/node_modules";
+ };
+ # Ugh, need to wait for web service to be up
+ preStart = if cfg.enableWebService then "sleep 1s" else "mkdir -p /var/lib/uptime";
+ serviceConfig.ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/monitor.js";
+ };
+ }) ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/vnstat.nix b/nixpkgs/nixos/modules/services/monitoring/vnstat.nix
new file mode 100644
index 00000000000..e9bedb704a4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/vnstat.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.vnstat;
+in {
+ options.services.vnstat = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable update of network usage statistics via vnstatd.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.vnstatd = {
+ isSystemUser = true;
+ description = "vnstat daemon user";
+ home = "/var/lib/vnstat";
+ createHome = true;
+ };
+
+ systemd.services.vnstat = {
+ description = "vnStat network traffic monitor";
+ path = [ pkgs.coreutils ];
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ documentation = [
+ "man:vnstatd(1)"
+ "man:vnstat(1)"
+ "man:vnstat.conf(5)"
+ ];
+ preStart = "chmod 755 /var/lib/vnstat";
+ serviceConfig = {
+ ExecStart = "${pkgs.vnstat}/bin/vnstatd -n";
+ ExecReload = "${pkgs.procps}/bin/kill -HUP $MAINPID";
+
+ # Hardening (from upstream example service)
+ ProtectSystem = "strict";
+ StateDirectory = "vnstat";
+ PrivateDevices = true;
+ ProtectKernelTunables = true;
+ ProtectControlGroups = true;
+ ProtectHome = true;
+ ProtectKernelModules = true;
+ PrivateTmp = true;
+ MemoryDenyWriteExecute = true;
+ RestrictRealtime = true;
+ RestrictNamespaces = true;
+
+ User = "vnstatd";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix
new file mode 100644
index 00000000000..856b9432892
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix
@@ -0,0 +1,158 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.zabbixAgent;
+
+ inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+ inherit (lib) attrValues concatMapStringsSep literalExample optionalString types;
+
+ user = "zabbix-agent";
+ group = "zabbix-agent";
+
+ moduleEnv = pkgs.symlinkJoin {
+ name = "zabbix-agent-module-env";
+ paths = attrValues cfg.modules;
+ };
+
+ configFile = pkgs.writeText "zabbix_agent.conf" ''
+ LogType = console
+ Server = ${cfg.server}
+ ListenIP = ${cfg.listen.ip}
+ ListenPort = ${toString cfg.listen.port}
+ ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
+ ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+ # interface
+
+ options = {
+
+ services.zabbixAgent = {
+ enable = mkEnableOption "the Zabbix Agent";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.zabbix.agent;
+ defaultText = "pkgs.zabbix.agent";
+ description = "The Zabbix package to use.";
+ };
+
+ extraPackages = mkOption {
+ type = types.listOf types.package;
+ default = with pkgs; [ nettools ];
+ defaultText = "[ nettools ]";
+ example = "[ nettools mysql ]";
+ description = ''
+ Packages to be added to the Zabbix <envar>PATH</envar>.
+ Typically used to add executables for scripts, but can be anything.
+ '';
+ };
+
+ modules = mkOption {
+ type = types.attrsOf types.package;
+ description = "A set of modules to load.";
+ default = {};
+ example = literalExample ''
+ {
+ "dummy.so" = pkgs.stdenv.mkDerivation {
+ name = "zabbix-dummy-module-''${cfg.package.version}";
+ src = cfg.package.src;
+ buildInputs = [ cfg.package ];
+ sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
+ installPhase = '''
+ mkdir -p $out/lib
+ cp dummy.so $out/lib/
+ ''';
+ };
+ }
+ '';
+ };
+
+ server = mkOption {
+ type = types.str;
+ description = ''
+ The IP address or hostname of the Zabbix server to connect to.
+ '';
+ };
+
+ listen = {
+ ip = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ List of comma delimited IP addresses that the agent should listen on.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 10050;
+ description = ''
+ Agent will listen on this port for connections from the server.
+ '';
+ };
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for the Zabbix Agent.
+ '';
+ };
+
+ # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Configuration that is injected verbatim into the configuration file. Refer to
+ <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_agentd"/>
+ for details on supported values.
+ '';
+ };
+
+ };
+
+ };
+
+ # implementation
+
+ config = mkIf cfg.enable {
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.listen.port ];
+ };
+
+ users.users.${user} = {
+ description = "Zabbix Agent daemon user";
+ inherit group;
+ };
+
+ users.groups.${group} = { };
+
+ systemd.services.zabbix-agent = {
+ description = "Zabbix Agent";
+
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+
+ serviceConfig = {
+ ExecStart = "@${cfg.package}/sbin/zabbix_agentd zabbix_agentd -f --config ${configFile}";
+ Restart = "always";
+ RestartSec = 2;
+
+ User = user;
+ Group = group;
+ PrivateTmp = true;
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix
new file mode 100644
index 00000000000..9d214469c3b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -0,0 +1,299 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.zabbixProxy;
+ pgsql = config.services.postgresql;
+ mysql = config.services.mysql;
+
+ inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+ inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
+
+ user = "zabbix";
+ group = "zabbix";
+ runtimeDir = "/run/zabbix";
+ stateDir = "/var/lib/zabbix";
+ passwordFile = "${runtimeDir}/zabbix-dbpassword.conf";
+
+ moduleEnv = pkgs.symlinkJoin {
+ name = "zabbix-proxy-module-env";
+ paths = attrValues cfg.modules;
+ };
+
+ configFile = pkgs.writeText "zabbix_proxy.conf" ''
+ LogType = console
+ ListenIP = ${cfg.listen.ip}
+ ListenPort = ${toString cfg.listen.port}
+ Server = ${cfg.server}
+ # TODO: set to cfg.database.socket if database type is pgsql?
+ DBHost = ${optionalString (cfg.database.createLocally != true) cfg.database.host}
+ ${optionalString (cfg.database.createLocally != true) "DBPort = ${cfg.database.port}"}
+ DBName = ${cfg.database.name}
+ DBUser = ${cfg.database.user}
+ ${optionalString (cfg.database.passwordFile != null) "Include ${passwordFile}"}
+ ${optionalString (mysqlLocal && cfg.database.socket != null) "DBSocket = ${cfg.database.socket}"}
+ SocketDir = ${runtimeDir}
+ FpingLocation = /run/wrappers/bin/fping
+ ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
+ ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
+ ${cfg.extraConfig}
+ '';
+
+ mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+ pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+in
+
+{
+ # interface
+
+ options = {
+
+ services.zabbixProxy = {
+ enable = mkEnableOption "the Zabbix Proxy";
+
+ server = mkOption {
+ type = types.str;
+ description = ''
+ The IP address or hostname of the Zabbix server to connect to.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default =
+ if cfg.database.type == "mysql" then pkgs.zabbix.proxy-mysql
+ else if cfg.database.type == "pgsql" then pkgs.zabbix.proxy-pgsql
+ else pkgs.zabbix.proxy-sqlite;
+ defaultText = "pkgs.zabbix.proxy-pgsql";
+ description = "The Zabbix package to use.";
+ };
+
+ extraPackages = mkOption {
+ type = types.listOf types.package;
+ default = with pkgs; [ nettools nmap traceroute ];
+ defaultText = "[ nettools nmap traceroute ]";
+ description = ''
+ Packages to be added to the Zabbix <envar>PATH</envar>.
+ Typically used to add executables for scripts, but can be anything.
+ '';
+ };
+
+ modules = mkOption {
+ type = types.attrsOf types.package;
+ description = "A set of modules to load.";
+ default = {};
+ example = literalExample ''
+ {
+ "dummy.so" = pkgs.stdenv.mkDerivation {
+ name = "zabbix-dummy-module-''${cfg.package.version}";
+ src = cfg.package.src;
+ buildInputs = [ cfg.package ];
+ sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
+ installPhase = '''
+ mkdir -p $out/lib
+ cp dummy.so $out/lib/
+ ''';
+ };
+ }
+ '';
+ };
+
+ database = {
+ type = mkOption {
+ type = types.enum [ "mysql" "pgsql" "sqlite" ];
+ example = "mysql";
+ default = "pgsql";
+ description = "Database engine to use.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = if cfg.database.type == "mysql" then mysql.port else pgsql.port;
+ description = "Database host port.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = if cfg.database.type == "sqlite" then "${stateDir}/zabbix.db" else "zabbix";
+ defaultText = "zabbix";
+ description = "Database name.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "zabbix";
+ description = "Database user.";
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/zabbix-dbpassword";
+ description = ''
+ A file containing the password corresponding to
+ <option>database.user</option>.
+ '';
+ };
+
+ socket = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/postgresql";
+ description = "Path to the unix socket file to use for authentication.";
+ };
+
+ createLocally = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to create a local database automatically.";
+ };
+ };
+
+ listen = {
+ ip = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ List of comma delimited IP addresses that the trapper should listen on.
+ Trapper will listen on all network interfaces if this parameter is missing.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 10051;
+ description = ''
+ Listen port for trapper.
+ '';
+ };
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for the Zabbix Proxy.
+ '';
+ };
+
+ # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Configuration that is injected verbatim into the configuration file. Refer to
+ <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_proxy"/>
+ for details on supported values.
+ '';
+ };
+
+ };
+
+ };
+
+ # implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = !config.services.zabbixServer.enable;
+ message = "Please choose one of services.zabbixServer or services.zabbixProxy.";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.user == user;
+ message = "services.zabbixProxy.database.user must be set to ${user} if services.zabbixProxy.database.createLocally is set true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+ message = "a password cannot be specified if services.zabbixProxy.database.createLocally is set to true";
+ }
+ ];
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.listen.port ];
+ };
+
+ services.mysql = optionalAttrs mysqlLocal {
+ enable = true;
+ package = mkDefault pkgs.mariadb;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ services.postgresql = optionalAttrs pgsqlLocal {
+ enable = true;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ users.users.${user} = {
+ description = "Zabbix daemon user";
+ uid = config.ids.uids.zabbix;
+ inherit group;
+ };
+
+ users.groups.${group} = {
+ gid = config.ids.gids.zabbix;
+ };
+
+ security.wrappers = {
+ fping.source = "${pkgs.fping}/bin/fping";
+ };
+
+ systemd.services.zabbix-proxy = {
+ description = "Zabbix Proxy";
+
+ wantedBy = [ "multi-user.target" ];
+ after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+ path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+ preStart = optionalString pgsqlLocal ''
+ if ! test -e "${stateDir}/db-created"; then
+ cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+ touch "${stateDir}/db-created"
+ fi
+ '' + optionalString mysqlLocal ''
+ if ! test -e "${stateDir}/db-created"; then
+ cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+ touch "${stateDir}/db-created"
+ fi
+ '' + optionalString (cfg.database.type == "sqlite") ''
+ if ! test -e "${cfg.database.name}"; then
+ ${pkgs.sqlite}/bin/sqlite3 "${cfg.database.name}" < ${cfg.package}/share/zabbix/database/sqlite3/schema.sql
+ fi
+ '' + optionalString (cfg.database.passwordFile != null) ''
+ # create a copy of the supplied password file in a format zabbix can consume
+ touch ${passwordFile}
+ chmod 0600 ${passwordFile}
+ echo -n "DBPassword = " > ${passwordFile}
+ cat ${cfg.database.passwordFile} >> ${passwordFile}
+ '';
+
+ serviceConfig = {
+ ExecStart = "@${cfg.package}/sbin/zabbix_proxy zabbix_proxy -f --config ${configFile}";
+ Restart = "always";
+ RestartSec = 2;
+
+ User = user;
+ Group = group;
+ RuntimeDirectory = "zabbix";
+ StateDirectory = "zabbix";
+ PrivateTmp = true;
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix
new file mode 100644
index 00000000000..4b4049ed360
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix
@@ -0,0 +1,293 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.zabbixServer;
+ pgsql = config.services.postgresql;
+ mysql = config.services.mysql;
+
+ inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+ inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
+
+ user = "zabbix";
+ group = "zabbix";
+ runtimeDir = "/run/zabbix";
+ stateDir = "/var/lib/zabbix";
+ passwordFile = "${runtimeDir}/zabbix-dbpassword.conf";
+
+ moduleEnv = pkgs.symlinkJoin {
+ name = "zabbix-server-module-env";
+ paths = attrValues cfg.modules;
+ };
+
+ configFile = pkgs.writeText "zabbix_server.conf" ''
+ LogType = console
+ ListenIP = ${cfg.listen.ip}
+ ListenPort = ${toString cfg.listen.port}
+ # TODO: set to cfg.database.socket if database type is pgsql?
+ DBHost = ${optionalString (cfg.database.createLocally != true) cfg.database.host}
+ ${optionalString (cfg.database.createLocally != true) "DBPort = ${cfg.database.port}"}
+ DBName = ${cfg.database.name}
+ DBUser = ${cfg.database.user}
+ ${optionalString (cfg.database.passwordFile != null) "Include ${passwordFile}"}
+ ${optionalString (mysqlLocal && cfg.database.socket != null) "DBSocket = ${cfg.database.socket}"}
+ SocketDir = ${runtimeDir}
+ FpingLocation = /run/wrappers/bin/fping
+ ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
+ ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
+ ${cfg.extraConfig}
+ '';
+
+ mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+ pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+in
+
+{
+ # interface
+
+ options = {
+
+ services.zabbixServer = {
+ enable = mkEnableOption "the Zabbix Server";
+
+ package = mkOption {
+ type = types.package;
+ default = if cfg.database.type == "mysql" then pkgs.zabbix.server-mysql else pkgs.zabbix.server-pgsql;
+ defaultText = "pkgs.zabbix.server-pgsql";
+ description = "The Zabbix package to use.";
+ };
+
+ extraPackages = mkOption {
+ type = types.listOf types.package;
+ default = with pkgs; [ nettools nmap traceroute ];
+ defaultText = "[ nettools nmap traceroute ]";
+ description = ''
+ Packages to be added to the Zabbix <envar>PATH</envar>.
+ Typically used to add executables for scripts, but can be anything.
+ '';
+ };
+
+ modules = mkOption {
+ type = types.attrsOf types.package;
+ description = "A set of modules to load.";
+ default = {};
+ example = literalExample ''
+ {
+ "dummy.so" = pkgs.stdenv.mkDerivation {
+ name = "zabbix-dummy-module-''${cfg.package.version}";
+ src = cfg.package.src;
+ buildInputs = [ cfg.package ];
+ sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
+ installPhase = '''
+ mkdir -p $out/lib
+ cp dummy.so $out/lib/
+ ''';
+ };
+ }
+ '';
+ };
+
+ database = {
+ type = mkOption {
+ type = types.enum [ "mysql" "pgsql" ];
+ example = "mysql";
+ default = "pgsql";
+ description = "Database engine to use.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = if cfg.database.type == "mysql" then mysql.port else pgsql.port;
+ description = "Database host port.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "zabbix";
+ description = "Database name.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "zabbix";
+ description = "Database user.";
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/zabbix-dbpassword";
+ description = ''
+ A file containing the password corresponding to
+ <option>database.user</option>.
+ '';
+ };
+
+ socket = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/postgresql";
+ description = "Path to the unix socket file to use for authentication.";
+ };
+
+ createLocally = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to create a local database automatically.";
+ };
+ };
+
+ listen = {
+ ip = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ List of comma delimited IP addresses that the trapper should listen on.
+ Trapper will listen on all network interfaces if this parameter is missing.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 10051;
+ description = ''
+ Listen port for trapper.
+ '';
+ };
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for the Zabbix Server.
+ '';
+ };
+
+ # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Configuration that is injected verbatim into the configuration file. Refer to
+ <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_server"/>
+ for details on supported values.
+ '';
+ };
+
+ };
+
+ };
+
+ # implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.database.createLocally -> cfg.database.user == user;
+ message = "services.zabbixServer.database.user must be set to ${user} if services.zabbixServer.database.createLocally is set true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+ message = "a password cannot be specified if services.zabbixServer.database.createLocally is set to true";
+ }
+ ];
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.listen.port ];
+ };
+
+ services.mysql = optionalAttrs mysqlLocal {
+ enable = true;
+ package = mkDefault pkgs.mariadb;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ services.postgresql = optionalAttrs pgsqlLocal {
+ enable = true;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ users.users.${user} = {
+ description = "Zabbix daemon user";
+ uid = config.ids.uids.zabbix;
+ inherit group;
+ };
+
+ users.groups.${group} = {
+ gid = config.ids.gids.zabbix;
+ };
+
+ security.wrappers = {
+ fping.source = "${pkgs.fping}/bin/fping";
+ };
+
+ systemd.services.zabbix-server = {
+ description = "Zabbix Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+ path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+ preStart = ''
+ # pre 19.09 compatibility
+ if test -e "${runtimeDir}/db-created"; then
+ mv "${runtimeDir}/db-created" "${stateDir}/"
+ fi
+ '' + optionalString pgsqlLocal ''
+ if ! test -e "${stateDir}/db-created"; then
+ cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+ cat ${cfg.package}/share/zabbix/database/postgresql/images.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+ cat ${cfg.package}/share/zabbix/database/postgresql/data.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+ touch "${stateDir}/db-created"
+ fi
+ '' + optionalString mysqlLocal ''
+ if ! test -e "${stateDir}/db-created"; then
+ cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+ cat ${cfg.package}/share/zabbix/database/mysql/images.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+ cat ${cfg.package}/share/zabbix/database/mysql/data.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+ touch "${stateDir}/db-created"
+ fi
+ '' + optionalString (cfg.database.passwordFile != null) ''
+ # create a copy of the supplied password file in a format zabbix can consume
+ touch ${passwordFile}
+ chmod 0600 ${passwordFile}
+ echo -n "DBPassword = " > ${passwordFile}
+ cat ${cfg.database.passwordFile} >> ${passwordFile}
+ '';
+
+ serviceConfig = {
+ ExecStart = "@${cfg.package}/sbin/zabbix_server zabbix_server -f --config ${configFile}";
+ Restart = "always";
+ RestartSec = 2;
+
+ User = user;
+ Group = group;
+ RuntimeDirectory = "zabbix";
+ StateDirectory = "zabbix";
+ PrivateTmp = true;
+ };
+ };
+
+ systemd.services.httpd.after =
+ optional (config.services.zabbixWeb.enable && mysqlLocal) "mysql.service" ++
+ optional (config.services.zabbixWeb.enable && pgsqlLocal) "postgresql.service";
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/beegfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/beegfs.nix
new file mode 100644
index 00000000000..2e03a422665
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/beegfs.nix
@@ -0,0 +1,357 @@
+{ config, lib, pkgs, ...} :
+
+with lib;
+
+let
+ cfg = config.services.beegfs;
+
+ # functions for the generations of config files
+
+ configMgmtd = name: cfg: pkgs.writeText "mgmt-${name}.conf" ''
+ storeMgmtdDirectory = ${cfg.mgmtd.storeDir}
+ storeAllowFirstRunInit = false
+ connAuthFile = ${cfg.connAuthFile}
+ connPortShift = ${toString cfg.connPortShift}
+
+ ${cfg.mgmtd.extraConfig}
+ '';
+
+ configAdmon = name: cfg: pkgs.writeText "admon-${name}.conf" ''
+ sysMgmtdHost = ${cfg.mgmtdHost}
+ connAuthFile = ${cfg.connAuthFile}
+ connPortShift = ${toString cfg.connPortShift}
+
+ ${cfg.admon.extraConfig}
+ '';
+
+ configMeta = name: cfg: pkgs.writeText "meta-${name}.conf" ''
+ storeMetaDirectory = ${cfg.meta.storeDir}
+ sysMgmtdHost = ${cfg.mgmtdHost}
+ connAuthFile = ${cfg.connAuthFile}
+ connPortShift = ${toString cfg.connPortShift}
+ storeAllowFirstRunInit = false
+
+ ${cfg.meta.extraConfig}
+ '';
+
+ configStorage = name: cfg: pkgs.writeText "storage-${name}.conf" ''
+ storeStorageDirectory = ${cfg.storage.storeDir}
+ sysMgmtdHost = ${cfg.mgmtdHost}
+ connAuthFile = ${cfg.connAuthFile}
+ connPortShift = ${toString cfg.connPortShift}
+ storeAllowFirstRunInit = false
+
+ ${cfg.storage.extraConfig}
+ '';
+
+ configHelperd = name: cfg: pkgs.writeText "helperd-${name}.conf" ''
+ connAuthFile = ${cfg.connAuthFile}
+ ${cfg.helperd.extraConfig}
+ '';
+
+ configClientFilename = name : "/etc/beegfs/client-${name}.conf";
+
+ configClient = name: cfg: ''
+ sysMgmtdHost = ${cfg.mgmtdHost}
+ connAuthFile = ${cfg.connAuthFile}
+ connPortShift = ${toString cfg.connPortShift}
+
+ ${cfg.client.extraConfig}
+ '';
+
+ serviceList = [
+ { service = "admon"; cfgFile = configAdmon; }
+ { service = "meta"; cfgFile = configMeta; }
+ { service = "mgmtd"; cfgFile = configMgmtd; }
+ { service = "storage"; cfgFile = configStorage; }
+ ];
+
+ # functions to generate systemd.service entries
+
+ systemdEntry = service: cfgFile: (mapAttrs' ( name: cfg:
+ (nameValuePair "beegfs-${service}-${name}" (mkIf cfg.${service}.enable {
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+ serviceConfig = rec {
+ ExecStart = ''
+ ${pkgs.beegfs}/bin/beegfs-${service} \
+ cfgFile=${cfgFile name cfg} \
+ pidFile=${PIDFile}
+ '';
+ PIDFile = "/run/beegfs-${service}-${name}.pid";
+ TimeoutStopSec = "300";
+ };
+ }))) cfg);
+
+ systemdHelperd = mapAttrs' ( name: cfg:
+ (nameValuePair "beegfs-helperd-${name}" (mkIf cfg.client.enable {
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+ serviceConfig = rec {
+ ExecStart = ''
+ ${pkgs.beegfs}/bin/beegfs-helperd \
+ cfgFile=${configHelperd name cfg} \
+ pidFile=${PIDFile}
+ '';
+ PIDFile = "/run/beegfs-helperd-${name}.pid";
+ TimeoutStopSec = "300";
+ };
+ }))) cfg;
+
+ # wrappers to beegfs tools. Avoid typing path of config files
+ utilWrappers = mapAttrsToList ( name: cfg:
+ ( pkgs.runCommand "beegfs-utils-${name}" {
+ nativeBuildInputs = [ pkgs.makeWrapper ];
+ preferLocalBuild = true;
+ } ''
+ mkdir -p $out/bin
+
+ makeWrapper ${pkgs.beegfs}/bin/beegfs-check-servers \
+ $out/bin/beegfs-check-servers-${name} \
+ --add-flags "-c ${configClientFilename name}" \
+ --prefix PATH : ${lib.makeBinPath [ pkgs.beegfs ]}
+
+ makeWrapper ${pkgs.beegfs}/bin/beegfs-ctl \
+ $out/bin/beegfs-ctl-${name} \
+ --add-flags "--cfgFile=${configClientFilename name}"
+
+ makeWrapper ${pkgs.beegfs}/bin/beegfs-ctl \
+ $out/bin/beegfs-df-${name} \
+ --add-flags "--cfgFile=${configClientFilename name}" \
+ --add-flags --listtargets \
+ --add-flags --hidenodeid \
+ --add-flags --pools \
+ --add-flags --spaceinfo
+
+ makeWrapper ${pkgs.beegfs}/bin/beegfs-fsck \
+ $out/bin/beegfs-fsck-${name} \
+ --add-flags "--cfgFile=${configClientFilename name}"
+ ''
+ )) cfg;
+in
+{
+ ###### interface
+
+ options = {
+ services.beegfsEnable = mkEnableOption "BeeGFS";
+
+ services.beegfs = mkOption {
+ default = {};
+ description = ''
+ BeeGFS configurations. Every mount point requires a separate configuration.
+ '';
+ type = with types; attrsOf (submodule ({ ... } : {
+ options = {
+ mgmtdHost = mkOption {
+ type = types.str;
+ default = null;
+ example = "master";
+ description = ''Hostname of managament host.'';
+ };
+
+ connAuthFile = mkOption {
+ type = types.str;
+ default = "";
+ example = "/etc/my.key";
+ description = "File containing shared secret authentication.";
+ };
+
+ connPortShift = mkOption {
+ type = types.int;
+ default = 0;
+ example = 5;
+ description = ''
+ For each additional beegfs configuration shift all
+ service TCP/UDP ports by at least 5.
+ '';
+ };
+
+ client = {
+ enable = mkEnableOption "BeeGFS client";
+
+ mount = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Create fstab entry automatically";
+ };
+
+ mountPoint = mkOption {
+ type = types.str;
+ default = "/run/beegfs";
+ description = ''
+ Mount point under which the beegfs filesytem should be mounted.
+ If mounted manually the mount option specifing the config file is needed:
+ cfgFile=/etc/beegfs/beegfs-client-&lt;name&gt;.conf
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional lines for beegfs-client.conf.
+ See documentation for further details.
+ '';
+ };
+ };
+
+ helperd = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable the BeeGFS helperd.
+ The helpered is need for logging purposes on the client.
+ Disabling <literal>helperd</literal> allows for runing the client
+ with <literal>allowUnfree = false</literal>.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional lines for beegfs-helperd.conf. See documentation
+ for further details.
+ '';
+ };
+ };
+
+ mgmtd = {
+ enable = mkEnableOption "BeeGFS mgmtd daemon";
+
+ storeDir = mkOption {
+ type = types.path;
+ default = null;
+ example = "/data/beegfs-mgmtd";
+ description = ''
+ Data directory for mgmtd.
+ Must not be shared with other beegfs daemons.
+ This directory must exist and it must be initialized
+ with beegfs-setup-mgmtd, e.g. "beegfs-setup-mgmtd -C -p &lt;storeDir&gt;"
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional lines for beegfs-mgmtd.conf. See documentation
+ for further details.
+ '';
+ };
+ };
+
+ admon = {
+ enable = mkEnableOption "BeeGFS admon daemon";
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional lines for beegfs-admon.conf. See documentation
+ for further details.
+ '';
+ };
+ };
+
+ meta = {
+ enable = mkEnableOption "BeeGFS meta data daemon";
+
+ storeDir = mkOption {
+ type = types.path;
+ default = null;
+ example = "/data/beegfs-meta";
+ description = ''
+ Data directory for meta data service.
+ Must not be shared with other beegfs daemons.
+ The underlying filesystem must be mounted with xattr turned on.
+ This directory must exist and it must be initialized
+ with beegfs-setup-meta, e.g.
+ "beegfs-setup-meta -C -s &lt;serviceID&gt; -p &lt;storeDir&gt;"
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Additional lines for beegfs-meta.conf. See documentation
+ for further details.
+ '';
+ };
+ };
+
+ storage = {
+ enable = mkEnableOption "BeeGFS storage daemon";
+
+ storeDir = mkOption {
+ type = types.path;
+ default = null;
+ example = "/data/beegfs-storage";
+ description = ''
+ Data directories for storage service.
+ Must not be shared with other beegfs daemons.
+ The underlying filesystem must be mounted with xattr turned on.
+ This directory must exist and it must be initialized
+ with beegfs-setup-storage, e.g.
+ "beegfs-setup-storage -C -s &lt;serviceID&gt; -i &lt;storageTargetID&gt; -p &lt;storeDir&gt;"
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Addional lines for beegfs-storage.conf. See documentation
+ for further details.
+ '';
+ };
+ };
+ };
+ }));
+ };
+ };
+
+ ###### implementation
+
+ config =
+ mkIf config.services.beegfsEnable {
+
+ environment.systemPackages = utilWrappers;
+
+ # Put the client.conf files in /etc since they are needed
+ # by the commandline tools
+ environment.etc = mapAttrs' ( name: cfg:
+ (nameValuePair "beegfs/client-${name}.conf" (mkIf (cfg.client.enable)
+ {
+ enable = true;
+ text = configClient name cfg;
+ }))) cfg;
+
+ # Kernel module, we need it only once per host.
+ boot = mkIf (
+ foldr (a: b: a || b) false
+ (map (x: x.client.enable) (collect (x: x ? client) cfg)))
+ {
+ kernelModules = [ "beegfs" ];
+ extraModulePackages = [ pkgs.linuxPackages.beegfs-module ];
+ };
+
+ # generate fstab entries
+ fileSystems = mapAttrs' (name: cfg:
+ (nameValuePair cfg.client.mountPoint (optionalAttrs cfg.client.mount (mkIf cfg.client.enable {
+ device = "beegfs_nodev";
+ fsType = "beegfs";
+ mountPoint = cfg.client.mountPoint;
+ options = [ "cfgFile=${configClientFilename name}" "_netdev" ];
+ })))) cfg;
+
+ # generate systemd services
+ systemd.services = systemdHelperd //
+ foldr (a: b: a // b) {}
+ (map (x: systemdEntry x.service x.cfgFile) serviceList);
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/cachefilesd.nix b/nixpkgs/nixos/modules/services/network-filesystems/cachefilesd.nix
new file mode 100644
index 00000000000..61981340840
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/cachefilesd.nix
@@ -0,0 +1,59 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.cachefilesd;
+
+ cfgFile = pkgs.writeText "cachefilesd.conf" ''
+ dir ${cfg.cacheDir}
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+ options = {
+ services.cachefilesd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable cachefilesd network filesystems caching daemon.";
+ };
+
+ cacheDir = mkOption {
+ type = types.str;
+ default = "/var/cache/fscache";
+ description = "Directory to contain filesystem cache.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "brun 10%";
+ description = "Additional configuration file entries. See cachefilesd.conf(5) for more information.";
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.cachefilesd = {
+ description = "Local network file caching management daemon";
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.kmod pkgs.cachefilesd ];
+ script = ''
+ modprobe -qab cachefiles
+ mkdir -p ${cfg.cacheDir}
+ chmod 700 ${cfg.cacheDir}
+ exec cachefilesd -n -f ${cfgFile}
+ '';
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix b/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix
new file mode 100644
index 00000000000..656a2d21b86
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix
@@ -0,0 +1,407 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.ceph;
+
+ # function that translates "camelCaseOptions" to "camel case options", credits to tilpner in #nixos@freenode
+ expandCamelCase = replaceStrings upperChars (map (s: " ${s}") lowerChars);
+ expandCamelCaseAttrs = mapAttrs' (name: value: nameValuePair (expandCamelCase name) value);
+
+ makeServices = (daemonType: daemonIds: extraServiceConfig:
+ mkMerge (map (daemonId:
+ { "ceph-${daemonType}-${daemonId}" = makeService daemonType daemonId cfg.global.clusterName pkgs.ceph extraServiceConfig; })
+ daemonIds));
+
+ makeService = (daemonType: daemonId: clusterName: ceph: extraServiceConfig: {
+ enable = true;
+ description = "Ceph ${builtins.replaceStrings lowerChars upperChars daemonType} daemon ${daemonId}";
+ after = [ "network-online.target" "time-sync.target" ] ++ optional (daemonType == "osd") "ceph-mon.target";
+ wants = [ "network-online.target" "time-sync.target" ];
+ partOf = [ "ceph-${daemonType}.target" ];
+ wantedBy = [ "ceph-${daemonType}.target" ];
+
+ serviceConfig = {
+ LimitNOFILE = 1048576;
+ LimitNPROC = 1048576;
+ Environment = "CLUSTER=${clusterName}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ PrivateDevices = "yes";
+ PrivateTmp = "true";
+ ProtectHome = "true";
+ ProtectSystem = "full";
+ Restart = "on-failure";
+ StartLimitBurst = "5";
+ StartLimitInterval = "30min";
+ ExecStart = ''${ceph.out}/bin/${if daemonType == "rgw" then "radosgw" else "ceph-${daemonType}"} \
+ -f --cluster ${clusterName} --id ${daemonId} --setuser ceph \
+ --setgroup ${if daemonType == "osd" then "disk" else "ceph"}'';
+ } // extraServiceConfig
+ // optionalAttrs (daemonType == "osd") { ExecStartPre = ''${ceph.lib}/libexec/ceph/ceph-osd-prestart.sh \
+ --id ${daemonId} --cluster ${clusterName}''; };
+ } // optionalAttrs (builtins.elem daemonType [ "mds" "mon" "rgw" "mgr" ]) {
+ preStart = ''
+ daemonPath="/var/lib/ceph/${if daemonType == "rgw" then "radosgw" else daemonType}/${clusterName}-${daemonId}"
+ if [ ! -d $daemonPath ]; then
+ mkdir -m 755 -p $daemonPath
+ chown -R ceph:ceph $daemonPath
+ fi
+ '';
+ } // optionalAttrs (daemonType == "osd") { path = [ pkgs.getopt ]; }
+ );
+
+ makeTarget = (daemonType:
+ {
+ "ceph-${daemonType}" = {
+ description = "Ceph target allowing to start/stop all ceph-${daemonType} services at once";
+ partOf = [ "ceph.target" ];
+ wantedBy = [ "ceph.target" ];
+ before = [ "ceph.target" ];
+ };
+ }
+ );
+in
+{
+ options.services.ceph = {
+ # Ceph has a monolithic configuration file but different sections for
+ # each daemon, a separate client section and a global section
+ enable = mkEnableOption "Ceph global configuration";
+
+ global = {
+ fsid = mkOption {
+ type = types.str;
+ example = ''
+ 433a2193-4f8a-47a0-95d2-209d7ca2cca5
+ '';
+ description = ''
+ Filesystem ID, a generated uuid, its must be generated and set before
+ attempting to start a cluster
+ '';
+ };
+
+ clusterName = mkOption {
+ type = types.str;
+ default = "ceph";
+ description = ''
+ Name of cluster
+ '';
+ };
+
+ mgrModulePath = mkOption {
+ type = types.path;
+ default = "${pkgs.ceph.lib}/lib/ceph/mgr";
+ description = ''
+ Path at which to find ceph-mgr modules.
+ '';
+ };
+
+ monInitialMembers = mkOption {
+ type = with types; nullOr commas;
+ default = null;
+ example = ''
+ node0, node1, node2
+ '';
+ description = ''
+ List of hosts that will be used as monitors at startup.
+ '';
+ };
+
+ monHost = mkOption {
+ type = with types; nullOr commas;
+ default = null;
+ example = ''
+ 10.10.0.1, 10.10.0.2, 10.10.0.3
+ '';
+ description = ''
+ List of hostname shortnames/IP addresses of the initial monitors.
+ '';
+ };
+
+ maxOpenFiles = mkOption {
+ type = types.int;
+ default = 131072;
+ description = ''
+ Max open files for each OSD daemon.
+ '';
+ };
+
+ authClusterRequired = mkOption {
+ type = types.enum [ "cephx" "none" ];
+ default = "cephx";
+ description = ''
+ Enables requiring daemons to authenticate with eachother in the cluster.
+ '';
+ };
+
+ authServiceRequired = mkOption {
+ type = types.enum [ "cephx" "none" ];
+ default = "cephx";
+ description = ''
+ Enables requiring clients to authenticate with the cluster to access services in the cluster (e.g. radosgw, mds or osd).
+ '';
+ };
+
+ authClientRequired = mkOption {
+ type = types.enum [ "cephx" "none" ];
+ default = "cephx";
+ description = ''
+ Enables requiring the cluster to authenticate itself to the client.
+ '';
+ };
+
+ publicNetwork = mkOption {
+ type = with types; nullOr commas;
+ default = null;
+ example = ''
+ 10.20.0.0/24, 192.168.1.0/24
+ '';
+ description = ''
+ A comma-separated list of subnets that will be used as public networks in the cluster.
+ '';
+ };
+
+ clusterNetwork = mkOption {
+ type = with types; nullOr commas;
+ default = null;
+ example = ''
+ 10.10.0.0/24, 192.168.0.0/24
+ '';
+ description = ''
+ A comma-separated list of subnets that will be used as cluster networks in the cluster.
+ '';
+ };
+
+ rgwMimeTypesFile = mkOption {
+ type = with types; nullOr path;
+ default = "${pkgs.mime-types}/etc/mime.types";
+ description = ''
+ Path to mime types used by radosgw.
+ '';
+ };
+ };
+
+ extraConfig = mkOption {
+ type = with types; attrsOf str;
+ default = {};
+ example = ''
+ {
+ "ms bind ipv6" = "true";
+ };
+ '';
+ description = ''
+ Extra configuration to add to the global section. Use for setting values that are common for all daemons in the cluster.
+ '';
+ };
+
+ mgr = {
+ enable = mkEnableOption "Ceph MGR daemon";
+ daemons = mkOption {
+ type = with types; listOf str;
+ default = [];
+ example = ''
+ [ "name1" "name2" ];
+ '';
+ description = ''
+ A list of names for manager daemons that should have a service created. The names correspond
+ to the id part in ceph i.e. [ "name1" ] would result in mgr.name1
+ '';
+ };
+ extraConfig = mkOption {
+ type = with types; attrsOf str;
+ default = {};
+ description = ''
+ Extra configuration to add to the global section for manager daemons.
+ '';
+ };
+ };
+
+ mon = {
+ enable = mkEnableOption "Ceph MON daemon";
+ daemons = mkOption {
+ type = with types; listOf str;
+ default = [];
+ example = ''
+ [ "name1" "name2" ];
+ '';
+ description = ''
+ A list of monitor daemons that should have a service created. The names correspond
+ to the id part in ceph i.e. [ "name1" ] would result in mon.name1
+ '';
+ };
+ extraConfig = mkOption {
+ type = with types; attrsOf str;
+ default = {};
+ description = ''
+ Extra configuration to add to the monitor section.
+ '';
+ };
+ };
+
+ osd = {
+ enable = mkEnableOption "Ceph OSD daemon";
+ daemons = mkOption {
+ type = with types; listOf str;
+ default = [];
+ example = ''
+ [ "name1" "name2" ];
+ '';
+ description = ''
+ A list of OSD daemons that should have a service created. The names correspond
+ to the id part in ceph i.e. [ "name1" ] would result in osd.name1
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = with types; attrsOf str;
+ default = {
+ "osd journal size" = "10000";
+ "osd pool default size" = "3";
+ "osd pool default min size" = "2";
+ "osd pool default pg num" = "200";
+ "osd pool default pgp num" = "200";
+ "osd crush chooseleaf type" = "1";
+ };
+ description = ''
+ Extra configuration to add to the OSD section.
+ '';
+ };
+ };
+
+ mds = {
+ enable = mkEnableOption "Ceph MDS daemon";
+ daemons = mkOption {
+ type = with types; listOf str;
+ default = [];
+ example = ''
+ [ "name1" "name2" ];
+ '';
+ description = ''
+ A list of metadata service daemons that should have a service created. The names correspond
+ to the id part in ceph i.e. [ "name1" ] would result in mds.name1
+ '';
+ };
+ extraConfig = mkOption {
+ type = with types; attrsOf str;
+ default = {};
+ description = ''
+ Extra configuration to add to the MDS section.
+ '';
+ };
+ };
+
+ rgw = {
+ enable = mkEnableOption "Ceph RadosGW daemon";
+ daemons = mkOption {
+ type = with types; listOf str;
+ default = [];
+ example = ''
+ [ "name1" "name2" ];
+ '';
+ description = ''
+ A list of rados gateway daemons that should have a service created. The names correspond
+ to the id part in ceph i.e. [ "name1" ] would result in client.name1, radosgw daemons
+ aren't daemons to cluster in the sense that OSD, MGR or MON daemons are. They are simply
+ daemons, from ceph, that uses the cluster as a backend.
+ '';
+ };
+ };
+
+ client = {
+ enable = mkEnableOption "Ceph client configuration";
+ extraConfig = mkOption {
+ type = with types; attrsOf str;
+ default = {};
+ example = ''
+ {
+ # This would create a section for a radosgw daemon named node0 and related
+ # configuration for it
+ "client.radosgw.node0" = { "some config option" = "true"; };
+ };
+ '';
+ description = ''
+ Extra configuration to add to the client section. Configuration for rados gateways
+ would be added here, with their own sections, see example.
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.ceph.enable {
+ assertions = [
+ { assertion = cfg.global.fsid != "";
+ message = "fsid has to be set to a valid uuid for the cluster to function";
+ }
+ { assertion = cfg.mon.enable == true -> cfg.mon.daemons != [];
+ message = "have to set id of atleast one MON if you're going to enable Monitor";
+ }
+ { assertion = cfg.mds.enable == true -> cfg.mds.daemons != [];
+ message = "have to set id of atleast one MDS if you're going to enable Metadata Service";
+ }
+ { assertion = cfg.osd.enable == true -> cfg.osd.daemons != [];
+ message = "have to set id of atleast one OSD if you're going to enable OSD";
+ }
+ { assertion = cfg.mgr.enable == true -> cfg.mgr.daemons != [];
+ message = "have to set id of atleast one MGR if you're going to enable MGR";
+ }
+ ];
+
+ warnings = optional (cfg.global.monInitialMembers == null)
+ ''Not setting up a list of members in monInitialMembers requires that you set the host variable for each mon daemon or else the cluster won't function'';
+
+ environment.etc."ceph/ceph.conf".text = let
+ # Merge the extraConfig set for mgr daemons, as mgr don't have their own section
+ globalSection = expandCamelCaseAttrs (cfg.global // cfg.extraConfig // optionalAttrs cfg.mgr.enable cfg.mgr.extraConfig);
+ # Remove all name-value pairs with null values from the attribute set to avoid making empty sections in the ceph.conf
+ globalSection' = filterAttrs (name: value: value != null) globalSection;
+ totalConfig = {
+ global = globalSection';
+ } // optionalAttrs (cfg.mon.enable && cfg.mon.extraConfig != {}) { mon = cfg.mon.extraConfig; }
+ // optionalAttrs (cfg.mds.enable && cfg.mds.extraConfig != {}) { mds = cfg.mds.extraConfig; }
+ // optionalAttrs (cfg.osd.enable && cfg.osd.extraConfig != {}) { osd = cfg.osd.extraConfig; }
+ // optionalAttrs (cfg.client.enable && cfg.client.extraConfig != {}) cfg.client.extraConfig;
+ in
+ generators.toINI {} totalConfig;
+
+ users.users = singleton {
+ name = "ceph";
+ uid = config.ids.uids.ceph;
+ description = "Ceph daemon user";
+ group = "ceph";
+ extraGroups = [ "disk" ];
+ };
+ users.groups = singleton {
+ name = "ceph";
+ gid = config.ids.gids.ceph;
+ };
+
+ systemd.services = let
+ services = []
+ ++ optional cfg.mon.enable (makeServices "mon" cfg.mon.daemons { RestartSec = "10"; })
+ ++ optional cfg.mds.enable (makeServices "mds" cfg.mds.daemons { StartLimitBurst = "3"; })
+ ++ optional cfg.osd.enable (makeServices "osd" cfg.osd.daemons { StartLimitBurst = "30";
+ RestartSec = "20s";
+ PrivateDevices = "no"; # osd needs disk access
+ })
+ ++ optional cfg.rgw.enable (makeServices "rgw" cfg.rgw.daemons { })
+ ++ optional cfg.mgr.enable (makeServices "mgr" cfg.mgr.daemons { StartLimitBurst = "3"; });
+ in
+ mkMerge services;
+
+ systemd.targets = let
+ targets = [
+ { ceph = { description = "Ceph target allowing to start/stop all ceph service instances at once";
+ wantedBy = [ "multi-user.target" ]; }; }
+ ] ++ optional cfg.mon.enable (makeTarget "mon")
+ ++ optional cfg.mds.enable (makeTarget "mds")
+ ++ optional cfg.osd.enable (makeTarget "osd")
+ ++ optional cfg.rgw.enable (makeTarget "rgw")
+ ++ optional cfg.mgr.enable (makeTarget "mgr");
+ in
+ mkMerge targets;
+
+ systemd.tmpfiles.rules = [
+ "d /run/ceph 0770 ceph ceph -"
+ ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/davfs2.nix b/nixpkgs/nixos/modules/services/network-filesystems/davfs2.nix
new file mode 100644
index 00000000000..100d458d536
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/davfs2.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.davfs2;
+ cfgFile = pkgs.writeText "davfs2.conf" ''
+ dav_user ${cfg.davUser}
+ dav_group ${cfg.davGroup}
+ ${cfg.extraConfig}
+ '';
+in
+{
+ options.services.davfs2 = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable davfs2.
+ '';
+ };
+
+ davUser = mkOption {
+ type = types.str;
+ default = "davfs2";
+ description = ''
+ When invoked by root the mount.davfs daemon will run as this user.
+ Value must be given as name, not as numerical id.
+ '';
+ };
+
+ davGroup = mkOption {
+ type = types.str;
+ default = "davfs2";
+ description = ''
+ The group of the running mount.davfs daemon. Ordinary users must be
+ member of this group in order to mount a davfs2 file system. Value must
+ be given as name, not as numerical id.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ kernel_fs coda
+ proxy foo.bar:8080
+ use_locks 0
+ '';
+ description = ''
+ Extra lines appended to the configuration of davfs2.
+ '' ;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.davfs2 ];
+ environment.etc."davfs2/davfs2.conf".source = cfgFile;
+
+ users.groups = optionalAttrs (cfg.davGroup == "davfs2") (singleton {
+ name = "davfs2";
+ gid = config.ids.gids.davfs2;
+ });
+
+ users.users = optionalAttrs (cfg.davUser == "davfs2") (singleton {
+ name = "davfs2";
+ createHome = false;
+ group = cfg.davGroup;
+ uid = config.ids.uids.davfs2;
+ description = "davfs2 user";
+ });
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/diod.nix b/nixpkgs/nixos/modules/services/network-filesystems/diod.nix
new file mode 100644
index 00000000000..063bae6ddb1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/diod.nix
@@ -0,0 +1,159 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.diod;
+
+ diodBool = b: if b then "1" else "0";
+
+ diodConfig = pkgs.writeText "diod.conf" ''
+ allsquash = ${diodBool cfg.allsquash}
+ auth_required = ${diodBool cfg.authRequired}
+ exportall = ${diodBool cfg.exportall}
+ exportopts = "${concatStringsSep "," cfg.exportopts}"
+ exports = { ${concatStringsSep ", " (map (s: ''"${s}"'' ) cfg.exports)} }
+ listen = { ${concatStringsSep ", " (map (s: ''"${s}"'' ) cfg.listen)} }
+ logdest = "${cfg.logdest}"
+ nwthreads = ${toString cfg.nwthreads}
+ squashuser = "${cfg.squashuser}"
+ statfs_passthru = ${diodBool cfg.statfsPassthru}
+ userdb = ${diodBool cfg.userdb}
+ ${cfg.extraConfig}
+ '';
+in
+{
+ options = {
+ services.diod = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the diod 9P file server.";
+ };
+
+ listen = mkOption {
+ type = types.listOf types.str;
+ default = [ "0.0.0.0:564" ];
+ description = ''
+ [ "IP:PORT" [,"IP:PORT",...] ]
+ List the interfaces and ports that diod should listen on.
+ '';
+ };
+
+ exports = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ List the file systems that clients will be allowed to mount. All paths should
+ be fully qualified. The exports table can include two types of element:
+ a string element (as above),
+ or an alternate table element form { path="/path", opts="ro" }.
+ In the alternate form, the (optional) opts attribute is a comma-separated list
+ of export options. The two table element forms can be mixed in the exports
+ table. Note that although diod will not traverse file system boundaries for a
+ given mount due to inode uniqueness constraints, subdirectories of a file
+ system can be separately exported.
+ '';
+ };
+
+ exportall = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Export all file systems listed in /proc/mounts. If new file systems are mounted
+ after diod has started, they will become immediately mountable. If there is a
+ duplicate entry for a file system in the exports list, any options listed in
+ the exports entry will apply.
+ '';
+ };
+
+ exportopts = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Establish a default set of export options. These are overridden, not appended
+ to, by opts attributes in an "exports" entry.
+ '';
+ };
+
+ nwthreads = mkOption {
+ type = types.int;
+ default = 16;
+ description = ''
+ Sets the (fixed) number of worker threads created to handle 9P
+ requests for a unique aname.
+ '';
+ };
+
+ authRequired = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow clients to connect without authentication, i.e. without a valid MUNGE credential.
+ '';
+ };
+
+ userdb = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This option disables password/group lookups. It allows any uid to attach and
+ assumes gid=uid, and supplementary groups contain only the primary gid.
+ '';
+ };
+
+ allsquash = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Remap all users to "nobody". The attaching user need not be present in the
+ password file.
+ '';
+ };
+
+ squashuser = mkOption {
+ type = types.str;
+ default = "nobody";
+ description = ''
+ Change the squash user. The squash user must be present in the password file.
+ '';
+ };
+
+ logdest = mkOption {
+ type = types.str;
+ default = "syslog:daemon:err";
+ description = ''
+ Set the destination for logging.
+ The value has the form of "syslog:facility:level" or "filename".
+ '';
+ };
+
+
+ statfsPassthru = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This option configures statfs to return the host file system's type
+ rather than V9FS_MAGIC.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra configuration options for diod.conf.";
+ };
+ };
+ };
+
+ config = mkIf config.services.diod.enable {
+ environment.systemPackages = [ pkgs.diod ];
+
+ systemd.services.diod = {
+ description = "diod 9P file server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.diod}/sbin/diod -f -c ${diodConfig}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/drbd.nix b/nixpkgs/nixos/modules/services/network-filesystems/drbd.nix
new file mode 100644
index 00000000000..4ab74ed8e1c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/drbd.nix
@@ -0,0 +1,67 @@
+# Support for DRBD, the Distributed Replicated Block Device.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.drbd; in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.drbd.enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable support for DRBD, the Distributed Replicated
+ Block Device.
+ '';
+ };
+
+ services.drbd.config = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Contents of the <filename>drbd.conf</filename> configuration file.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.drbd ];
+
+ services.udev.packages = [ pkgs.drbd ];
+
+ boot.kernelModules = [ "drbd" ];
+
+ boot.extraModprobeConfig =
+ ''
+ options drbd usermode_helper=/run/current-system/sw/bin/drbdadm
+ '';
+
+ environment.etc = singleton
+ { source = pkgs.writeText "drbd.conf" cfg.config;
+ target = "drbd.conf";
+ };
+
+ systemd.services.drbd = {
+ after = [ "systemd-udev.settle.service" "network.target" ];
+ wants = [ "systemd-udev.settle.service" ];
+ wantedBy = [ "multi-user.target" ];
+ script = ''
+ ${pkgs.drbd}/sbin/drbdadm up all
+ '';
+ serviceConfig.ExecStop = ''
+ ${pkgs.drbd}/sbin/drbdadm down all
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix
new file mode 100644
index 00000000000..d70092999f6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix
@@ -0,0 +1,211 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ inherit (pkgs) glusterfs rsync;
+
+ tlsCmd = if (cfg.tlsSettings != null) then
+ ''
+ mkdir -p /var/lib/glusterd
+ touch /var/lib/glusterd/secure-access
+ ''
+ else
+ ''
+ rm -f /var/lib/glusterd/secure-access
+ '';
+
+ restartTriggers = if (cfg.tlsSettings != null) then [
+ config.environment.etc."ssl/glusterfs.pem".source
+ config.environment.etc."ssl/glusterfs.key".source
+ config.environment.etc."ssl/glusterfs.ca".source
+ ] else [];
+
+ cfg = config.services.glusterfs;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.glusterfs = {
+
+ enable = mkEnableOption "GlusterFS Daemon";
+
+ logLevel = mkOption {
+ type = types.enum ["DEBUG" "INFO" "WARNING" "ERROR" "CRITICAL" "TRACE" "NONE"];
+ description = "Log level used by the GlusterFS daemon";
+ default = "INFO";
+ };
+
+ useRpcbind = mkOption {
+ type = types.bool;
+ description = ''
+ Enable use of rpcbind. This is required for Gluster's NFS functionality.
+
+ You may want to turn it off to reduce the attack surface for DDoS reflection attacks.
+
+ See https://davelozier.com/glusterfs-and-rpcbind-portmap-ddos-reflection-attacks/
+ and https://bugzilla.redhat.com/show_bug.cgi?id=1426842 for details.
+ '';
+ default = true;
+ };
+
+ enableGlustereventsd = mkOption {
+ type = types.bool;
+ description = "Whether to enable the GlusterFS Events Daemon";
+ default = true;
+ };
+
+ killMode = mkOption {
+ type = types.enum ["control-group" "process" "mixed" "none"];
+ description = ''
+ The systemd KillMode to use for glusterd.
+
+ glusterd spawns other daemons like gsyncd.
+ If you want these to stop when glusterd is stopped (e.g. to ensure
+ that NixOS config changes are reflected even for these sub-daemons),
+ set this to 'control-group'.
+ If however you want running volume processes (glusterfsd) and thus
+ gluster mounts not be interrupted when glusterd is restarted
+ (for example, when you want to restart them manually at a later time),
+ set this to 'process'.
+ '';
+ default = "control-group";
+ };
+
+ stopKillTimeout = mkOption {
+ type = types.str;
+ description = ''
+ The systemd TimeoutStopSec to use.
+
+ After this time after having been asked to shut down, glusterd
+ (and depending on the killMode setting also its child processes)
+ are killed by systemd.
+
+ The default is set low because GlusterFS (as of 3.10) is known to
+ not tell its children (like gsyncd) to terminate at all.
+ '';
+ default = "5s";
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ description = "Extra flags passed to the GlusterFS daemon";
+ default = [];
+ };
+
+ tlsSettings = mkOption {
+ description = ''
+ Make the server communicate via TLS.
+ This means it will only connect to other gluster
+ servers having certificates signed by the same CA.
+
+ Enabling this will create a file <filename>/var/lib/glusterd/secure-access</filename>.
+ Disabling will delete this file again.
+
+ See also: https://gluster.readthedocs.io/en/latest/Administrator%20Guide/SSL/
+ '';
+ default = null;
+ type = types.nullOr (types.submodule {
+ options = {
+ tlsKeyPath = mkOption {
+ default = null;
+ type = types.str;
+ description = "Path to the private key used for TLS.";
+ };
+
+ tlsPem = mkOption {
+ default = null;
+ type = types.path;
+ description = "Path to the certificate used for TLS.";
+ };
+
+ caCert = mkOption {
+ default = null;
+ type = types.path;
+ description = "Path certificate authority used to sign the cluster certificates.";
+ };
+ };
+ });
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.glusterfs ];
+
+ services.rpcbind.enable = cfg.useRpcbind;
+
+ environment.etc = mkIf (cfg.tlsSettings != null) {
+ "ssl/glusterfs.pem".source = cfg.tlsSettings.tlsPem;
+ "ssl/glusterfs.key".source = cfg.tlsSettings.tlsKeyPath;
+ "ssl/glusterfs.ca".source = cfg.tlsSettings.caCert;
+ };
+
+ systemd.services.glusterd = {
+ inherit restartTriggers;
+
+ description = "GlusterFS, a clustered file-system server";
+
+ wantedBy = [ "multi-user.target" ];
+
+ requires = lib.optional cfg.useRpcbind "rpcbind.service";
+ after = [ "network.target" ] ++ lib.optional cfg.useRpcbind "rpcbind.service";
+
+ preStart = ''
+ install -m 0755 -d /var/log/glusterfs
+ ''
+ # The copying of hooks is due to upstream bug https://bugzilla.redhat.com/show_bug.cgi?id=1452761
+ + ''
+ mkdir -p /var/lib/glusterd/hooks/
+ ${rsync}/bin/rsync -a ${glusterfs}/var/lib/glusterd/hooks/ /var/lib/glusterd/hooks/
+
+ ${tlsCmd}
+ ''
+ # `glusterfind` needs dirs that upstream installs at `make install` phase
+ # https://github.com/gluster/glusterfs/blob/v3.10.2/tools/glusterfind/Makefile.am#L16-L17
+ + ''
+ mkdir -p /var/lib/glusterd/glusterfind/.keys
+ mkdir -p /var/lib/glusterd/hooks/1/delete/post/
+ '';
+
+ serviceConfig = {
+ LimitNOFILE=65536;
+ ExecStart="${glusterfs}/sbin/glusterd --no-daemon --log-level=${cfg.logLevel} ${toString cfg.extraFlags}";
+ KillMode=cfg.killMode;
+ TimeoutStopSec=cfg.stopKillTimeout;
+ };
+ };
+
+ systemd.services.glustereventsd = mkIf cfg.enableGlustereventsd {
+ inherit restartTriggers;
+
+ description = "Gluster Events Notifier";
+
+ wantedBy = [ "multi-user.target" ];
+
+ after = [ "syslog.target" "network.target" ];
+
+ preStart = ''
+ install -m 0755 -d /var/log/glusterfs
+ '';
+
+ # glustereventsd uses the `gluster` executable
+ path = [ glusterfs ];
+
+ serviceConfig = {
+ Type="simple";
+ PIDFile="/run/glustereventsd.pid";
+ ExecStart="${glusterfs}/sbin/glustereventsd --pid-file /run/glustereventsd.pid";
+ ExecReload="/bin/kill -SIGUSR2 $MAINPID";
+ KillMode="control-group";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix
new file mode 100644
index 00000000000..b6d881afd7b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix
@@ -0,0 +1,284 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ inherit (pkgs) ipfs runCommand makeWrapper;
+
+ cfg = config.services.ipfs;
+
+ ipfsFlags = toString ([
+ (optionalString cfg.autoMount "--mount")
+ #(optionalString cfg.autoMigrate "--migrate")
+ (optionalString cfg.enableGC "--enable-gc")
+ (optionalString (cfg.serviceFdlimit != null) "--manage-fdlimit=false")
+ (optionalString (cfg.defaultMode == "offline") "--offline")
+ (optionalString (cfg.defaultMode == "norouting") "--routing=none")
+ ] ++ cfg.extraFlags);
+
+ defaultDataDir = if versionAtLeast config.system.stateVersion "17.09" then
+ "/var/lib/ipfs" else
+ "/var/lib/ipfs/.ipfs";
+
+ # Wrapping the ipfs binary with the environment variable IPFS_PATH set to dataDir because we can't set it in the user environment
+ wrapped = runCommand "ipfs" { buildInputs = [ makeWrapper ]; preferLocalBuild = true; } ''
+ mkdir -p "$out/bin"
+ makeWrapper "${ipfs}/bin/ipfs" "$out/bin/ipfs" \
+ --set IPFS_PATH ${cfg.dataDir} \
+ --prefix PATH : /run/wrappers/bin
+ '';
+
+
+ commonEnv = {
+ environment.IPFS_PATH = cfg.dataDir;
+ path = [ wrapped ];
+ serviceConfig.User = cfg.user;
+ serviceConfig.Group = cfg.group;
+ };
+
+ baseService = recursiveUpdate commonEnv {
+ wants = [ "ipfs-init.service" ];
+ # NB: migration must be performed prior to pre-start, else we get the failure message!
+ preStart = ''
+ ipfs repo fsck # workaround for BUG #4212 (https://github.com/ipfs/go-ipfs/issues/4214)
+ '' + optionalString cfg.autoMount ''
+ ipfs --local config Mounts.FuseAllowOther --json true
+ ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir}
+ ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir}
+ '' + concatStringsSep "\n" (collect
+ isString
+ (mapAttrsRecursive
+ (path: value:
+ # Using heredoc below so that the value is never improperly quoted
+ ''
+ read value <<EOF
+ ${builtins.toJSON value}
+ EOF
+ ipfs --local config --json "${concatStringsSep "." path}" "$value"
+ '')
+ ({ Addresses.API = cfg.apiAddress;
+ Addresses.Gateway = cfg.gatewayAddress;
+ Addresses.Swarm = cfg.swarmAddress;
+ } //
+ cfg.extraConfig))
+ );
+ serviceConfig = {
+ ExecStart = "${wrapped}/bin/ipfs daemon ${ipfsFlags}";
+ Restart = "on-failure";
+ RestartSec = 1;
+ } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
+ };
+in {
+
+ ###### interface
+
+ options = {
+
+ services.ipfs = {
+
+ enable = mkEnableOption "Interplanetary File System (WARNING: may cause severe network degredation)";
+
+ user = mkOption {
+ type = types.str;
+ default = "ipfs";
+ description = "User under which the IPFS daemon runs";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "ipfs";
+ description = "Group under which the IPFS daemon runs";
+ };
+
+ dataDir = mkOption {
+ type = types.str;
+ default = defaultDataDir;
+ description = "The data dir for IPFS";
+ };
+
+ defaultMode = mkOption {
+ type = types.enum [ "online" "offline" "norouting" ];
+ default = "online";
+ description = "systemd service that is enabled by default";
+ };
+
+ /*
+ autoMigrate = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether IPFS should try to migrate the file system automatically.
+
+ The daemon will need to be able to download a binary from https://ipfs.io to perform the migration.
+ '';
+ };
+ */
+
+ autoMount = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether IPFS should try to mount /ipfs and /ipns at startup.";
+ };
+
+ ipfsMountDir = mkOption {
+ type = types.str;
+ default = "/ipfs";
+ description = "Where to mount the IPFS namespace to";
+ };
+
+ ipnsMountDir = mkOption {
+ type = types.str;
+ default = "/ipns";
+ description = "Where to mount the IPNS namespace to";
+ };
+
+ gatewayAddress = mkOption {
+ type = types.str;
+ default = "/ip4/127.0.0.1/tcp/8080";
+ description = "Where the IPFS Gateway can be reached";
+ };
+
+ apiAddress = mkOption {
+ type = types.str;
+ default = "/ip4/127.0.0.1/tcp/5001";
+ description = "Where IPFS exposes its API to";
+ };
+
+ swarmAddress = mkOption {
+ type = types.listOf types.str;
+ default = [ "/ip4/0.0.0.0/tcp/4001" "/ip6/::/tcp/4001" ];
+ description = "Where IPFS listens for incoming p2p connections";
+ };
+
+ enableGC = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable automatic garbage collection";
+ };
+
+ emptyRepo = mkOption {
+ type = types.bool;
+ default = false;
+ description = "If set to true, the repo won't be initialized with help files";
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ description = ''
+ Attrset of daemon configuration to set using <command>ipfs config</command>, every time the daemon starts.
+ These are applied last, so may override configuration set by other options in this module.
+ Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!
+ '';
+ default = {};
+ example = {
+ Datastore.StorageMax = "100GB";
+ Discovery.MDNS.Enabled = false;
+ Bootstrap = [
+ "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
+ "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
+ ];
+ Swarm.AddrFilters = null;
+ };
+
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ description = "Extra flags passed to the IPFS daemon";
+ default = [];
+ };
+
+ localDiscovery = mkOption {
+ type = types.bool;
+ description = ''Whether to enable local discovery for the ipfs daemon.
+ This will allow ipfs to scan ports on your local network. Some hosting services will ban you if you do this.
+ '';
+ default = true;
+ };
+
+ serviceFdlimit = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "The fdlimit for the IPFS systemd unit or <literal>null</literal> to have the daemon attempt to manage it";
+ example = 64*1024;
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ wrapped ];
+ programs.fuse = mkIf cfg.autoMount {
+ userAllowOther = true;
+ };
+
+ users.users = mkIf (cfg.user == "ipfs") {
+ ipfs = {
+ group = cfg.group;
+ home = cfg.dataDir;
+ createHome = false;
+ uid = config.ids.uids.ipfs;
+ description = "IPFS daemon user";
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "ipfs") {
+ ipfs.gid = config.ids.gids.ipfs;
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+ ] ++ optionals cfg.autoMount [
+ "d '${cfg.ipfsMountDir}' - ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.ipfs-init = recursiveUpdate commonEnv {
+ description = "IPFS Initializer";
+
+ before = [ "ipfs.service" "ipfs-offline.service" "ipfs-norouting.service" ];
+
+ script = ''
+ if [[ ! -f ${cfg.dataDir}/config ]]; then
+ ipfs init ${optionalString cfg.emptyRepo "-e"} \
+ ${optionalString (! cfg.localDiscovery) "--profile=server"}
+ else
+ ${if cfg.localDiscovery
+ then "ipfs config profile apply local-discovery"
+ else "ipfs config profile apply server"
+ }
+ fi
+ '';
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ };
+
+ # TODO These 3 definitions possibly be further abstracted through use of a function
+ # like: mutexServices "ipfs" [ "", "offline", "norouting" ] { ... shared conf here ... }
+
+ systemd.services.ipfs = recursiveUpdate baseService {
+ description = "IPFS Daemon";
+ wantedBy = mkIf (cfg.defaultMode == "online") [ "multi-user.target" ];
+ after = [ "network.target" "ipfs-init.service" ];
+ conflicts = [ "ipfs-offline.service" "ipfs-norouting.service"];
+ };
+
+ systemd.services.ipfs-offline = recursiveUpdate baseService {
+ description = "IPFS Daemon (offline mode)";
+ wantedBy = mkIf (cfg.defaultMode == "offline") [ "multi-user.target" ];
+ after = [ "ipfs-init.service" ];
+ conflicts = [ "ipfs.service" "ipfs-norouting.service"];
+ };
+
+ systemd.services.ipfs-norouting = recursiveUpdate baseService {
+ description = "IPFS Daemon (no routing mode)";
+ wantedBy = mkIf (cfg.defaultMode == "norouting") [ "multi-user.target" ];
+ after = [ "ipfs-init.service" ];
+ conflicts = [ "ipfs.service" "ipfs-offline.service"];
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/kbfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/kbfs.nix
new file mode 100644
index 00000000000..263b70d04a5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/kbfs.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.kbfs;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.kbfs = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to mount the Keybase filesystem.";
+ };
+
+ mountPoint = mkOption {
+ type = types.str;
+ default = "%h/keybase";
+ example = "/keybase";
+ description = "Mountpoint for the Keybase filesystem.";
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [
+ "-label kbfs"
+ "-mount-type normal"
+ ];
+ description = ''
+ Additional flags to pass to the Keybase filesystem on launch.
+ '';
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.user.services.kbfs = {
+ description = "Keybase File System";
+ requires = [ "keybase.service" ];
+ after = [ "keybase.service" ];
+ path = [ "/run/wrappers" ];
+ unitConfig.ConditionUser = "!@system";
+ serviceConfig = {
+ ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${cfg.mountPoint}";
+ ExecStart = "${pkgs.kbfs}/bin/kbfsfuse ${toString cfg.extraFlags} ${cfg.mountPoint}";
+ ExecStopPost = "/run/wrappers/bin/fusermount -u ${cfg.mountPoint}";
+ Restart = "on-failure";
+ PrivateTmp = true;
+ };
+ wantedBy = [ "default.target" ];
+ };
+
+ services.keybase.enable = true;
+
+ environment.systemPackages = [ pkgs.kbfs ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix b/nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix
new file mode 100644
index 00000000000..1dd869043f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix
@@ -0,0 +1,150 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.netatalk;
+
+ extmapFile = pkgs.writeText "extmap.conf" cfg.extmap;
+
+ afpToString = x: if builtins.typeOf x == "bool"
+ then boolToString x
+ else toString x;
+
+ volumeConfig = name:
+ let vol = getAttr name cfg.volumes; in
+ "[${name}]\n " + (toString (
+ map
+ (key: "${key} = ${afpToString (getAttr key vol)}\n")
+ (attrNames vol)
+ ));
+
+ afpConf = ''[Global]
+ extmap file = ${extmapFile}
+ afp port = ${toString cfg.port}
+
+ ${cfg.extraConfig}
+
+ ${if cfg.homes.enable then ''[Homes]
+ ${optionalString (cfg.homes.path != "") "path = ${cfg.homes.path}"}
+ basedir regex = ${cfg.homes.basedirRegex}
+ ${cfg.homes.extraConfig}
+ '' else ""}
+
+ ${toString (map volumeConfig (attrNames cfg.volumes))}
+ '';
+
+ afpConfFile = pkgs.writeText "afp.conf" afpConf;
+
+in
+
+{
+ options = {
+ services.netatalk = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable the Netatalk AFP fileserver.";
+ };
+
+ port = mkOption {
+ default = 548;
+ description = "TCP port to be used for AFP.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "uam list = uams_guest.so";
+ description = ''
+ Lines of configuration to add to the <literal>[Global]</literal> section.
+ See <literal>man apf.conf</literal> for more information.
+ '';
+ };
+
+ homes = {
+ enable = mkOption {
+ default = false;
+ description = "Enable sharing of the UNIX server user home directories.";
+ };
+
+ path = mkOption {
+ default = "";
+ example = "afp-data";
+ description = "Share not the whole user home but this subdirectory path.";
+ };
+
+ basedirRegex = mkOption {
+ example = "/home";
+ description = "Regex which matches the parent directory of the user homes.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Lines of configuration to add to the <literal>[Homes]</literal> section.
+ See <literal>man apf.conf</literal> for more information.
+ '';
+ };
+ };
+
+ volumes = mkOption {
+ default = { };
+ type = types.attrsOf (types.attrsOf types.unspecified);
+ description =
+ ''
+ Set of AFP volumes to export.
+ See <literal>man apf.conf</literal> for more information.
+ '';
+ example =
+ { srv =
+ { path = "/srv";
+ "read only" = true;
+ "hosts allow" = "10.1.0.0/16 10.2.1.100 2001:0db8:1234::/48";
+ };
+ };
+ };
+
+ extmap = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ File name extension mappings.
+ See <literal>man extmap.conf</literal> for more information.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.netatalk = {
+ description = "Netatalk AFP fileserver for Macintosh clients";
+ unitConfig.Documentation = "man:afp.conf(5) man:netatalk(8) man:afpd(8) man:cnid_metad(8) man:cnid_dbd(8)";
+ after = [ "network.target" "avahi-daemon.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ pkgs.netatalk ];
+
+ serviceConfig = {
+ Type = "forking";
+ GuessMainPID = "no";
+ PIDFile = "/run/lock/netatalk";
+ ExecStartPre = "${pkgs.coreutils}/bin/mkdir -m 0755 -p /var/lib/netatalk/CNID";
+ ExecStart = "${pkgs.netatalk}/sbin/netatalk -F ${afpConfFile}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ ExecStop = "${pkgs.coreutils}/bin/kill -TERM $MAINPID";
+ Restart = "always";
+ RestartSec = 1;
+ };
+
+ };
+
+ security.pam.services.netatalk.unixAuth = true;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/nfsd.nix b/nixpkgs/nixos/modules/services/network-filesystems/nfsd.nix
new file mode 100644
index 00000000000..1a78f9a76a3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/nfsd.nix
@@ -0,0 +1,171 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.nfs.server;
+
+ exports = pkgs.writeText "exports" cfg.exports;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.nfs = {
+
+ server = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the kernel's NFS server.
+ '';
+ };
+
+ extraNfsdConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Extra configuration options for the [nfsd] section of /etc/nfs.conf.
+ '';
+ };
+
+ exports = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Contents of the /etc/exports file. See
+ <citerefentry><refentrytitle>exports</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for the format.
+ '';
+ };
+
+ hostName = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Hostname or address on which NFS requests will be accepted.
+ Default is all. See the <option>-H</option> option in
+ <citerefentry><refentrytitle>nfsd</refentrytitle>
+ <manvolnum>8</manvolnum></citerefentry>.
+ '';
+ };
+
+ nproc = mkOption {
+ type = types.int;
+ default = 8;
+ description = ''
+ Number of NFS server threads. Defaults to the recommended value of 8.
+ '';
+ };
+
+ createMountPoints = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to create the mount points in the exports file at startup time.";
+ };
+
+ mountdPort = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 4002;
+ description = ''
+ Use fixed port for rpc.mountd, useful if server is behind firewall.
+ '';
+ };
+
+ lockdPort = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 4001;
+ description = ''
+ Use a fixed port for the NFS lock manager kernel module
+ (<literal>lockd/nlockmgr</literal>). This is useful if the
+ NFS server is behind a firewall.
+ '';
+ };
+
+ statdPort = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 4000;
+ description = ''
+ Use a fixed port for <command>rpc.statd</command>. This is
+ useful if the NFS server is behind a firewall.
+ '';
+ };
+
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.nfs.extraConfig = ''
+ [nfsd]
+ threads=${toString cfg.nproc}
+ ${optionalString (cfg.hostName != null) "host=${cfg.hostName}"}
+ ${cfg.extraNfsdConfig}
+
+ [mountd]
+ ${optionalString (cfg.mountdPort != null) "port=${toString cfg.mountdPort}"}
+
+ [statd]
+ ${optionalString (cfg.statdPort != null) "port=${toString cfg.statdPort}"}
+
+ [lockd]
+ ${optionalString (cfg.lockdPort != null) ''
+ port=${toString cfg.lockdPort}
+ udp-port=${toString cfg.lockdPort}
+ ''}
+ '';
+
+ services.rpcbind.enable = true;
+
+ boot.supportedFilesystems = [ "nfs" ]; # needed for statd and idmapd
+
+ environment.etc.exports.source = exports;
+
+ systemd.services.nfs-server =
+ { enable = true;
+ wantedBy = [ "multi-user.target" ];
+
+ preStart =
+ ''
+ mkdir -p /var/lib/nfs/v4recovery
+ '';
+ };
+
+ systemd.services.nfs-mountd =
+ { enable = true;
+ restartTriggers = [ exports ];
+
+ preStart =
+ ''
+ mkdir -p /var/lib/nfs
+
+ ${optionalString cfg.createMountPoints
+ ''
+ # create export directories:
+ # skip comments, take first col which may either be a quoted
+ # "foo bar" or just foo (-> man export)
+ sed '/^#.*/d;s/^"\([^"]*\)".*/\1/;t;s/[ ].*//' ${exports} \
+ | xargs -d '\n' mkdir -p
+ ''
+ }
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/openafs/client.nix b/nixpkgs/nixos/modules/services/network-filesystems/openafs/client.nix
new file mode 100644
index 00000000000..79c4b7aee06
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/openafs/client.nix
@@ -0,0 +1,253 @@
+{ config, lib, pkgs, ... }:
+
+# openafsMod, openafsBin, mkCellServDB
+with import ./lib.nix { inherit config lib pkgs; };
+
+let
+ inherit (lib) getBin mkOption mkIf optionalString singleton types;
+
+ cfg = config.services.openafsClient;
+
+ cellServDB = pkgs.fetchurl {
+ url = http://dl.central.org/dl/cellservdb/CellServDB.2018-05-14;
+ sha256 = "1wmjn6mmyy2r8p10nlbdzs4nrqxy8a9pjyrdciy5nmppg4053rk2";
+ };
+
+ clientServDB = pkgs.writeText "client-cellServDB-${cfg.cellName}" (mkCellServDB cfg.cellName cfg.cellServDB);
+
+ afsConfig = pkgs.runCommand "afsconfig" { preferLocalBuild = true; } ''
+ mkdir -p $out
+ echo ${cfg.cellName} > $out/ThisCell
+ cat ${cellServDB} ${clientServDB} > $out/CellServDB
+ echo "${cfg.mountPoint}:${cfg.cache.directory}:${toString cfg.cache.blocks}" > $out/cacheinfo
+ '';
+
+in
+{
+ ###### interface
+
+ options = {
+
+ services.openafsClient = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to enable the OpenAFS client.";
+ };
+
+ afsdb = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Resolve cells via AFSDB DNS records.";
+ };
+
+ cellName = mkOption {
+ default = "";
+ type = types.str;
+ description = "Cell name.";
+ example = "grand.central.org";
+ };
+
+ cellServDB = mkOption {
+ default = [];
+ type = with types; listOf (submodule { options = cellServDBConfig; });
+ description = ''
+ This cell's database server records, added to the global
+ CellServDB. See CellServDB(5) man page for syntax. Ignored when
+ <literal>afsdb</literal> is set to <literal>true</literal>.
+ '';
+ example = ''
+ [ { ip = "1.2.3.4"; dnsname = "first.afsdb.server.dns.fqdn.org"; }
+ { ip = "2.3.4.5"; dnsname = "second.afsdb.server.dns.fqdn.org"; }
+ ]
+ '';
+ };
+
+ cache = {
+ blocks = mkOption {
+ default = 100000;
+ type = types.int;
+ description = "Cache size in 1KB blocks.";
+ };
+
+ chunksize = mkOption {
+ default = 0;
+ type = types.ints.between 0 30;
+ description = ''
+ Size of each cache chunk given in powers of
+ 2. <literal>0</literal> resets the chunk size to its default
+ values (13 (8 KB) for memcache, 18-20 (256 KB to 1 MB) for
+ diskcache). Maximum value is 30. Important performance
+ parameter. Set to higher values when dealing with large files.
+ '';
+ };
+
+ directory = mkOption {
+ default = "/var/cache/openafs";
+ type = types.str;
+ description = "Cache directory.";
+ };
+
+ diskless = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Use in-memory cache for diskless machines. Has no real
+ performance benefit anymore.
+ '';
+ };
+ };
+
+ crypt = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether to enable (weak) protocol encryption.";
+ };
+
+ daemons = mkOption {
+ default = 2;
+ type = types.int;
+ description = ''
+ Number of daemons to serve user requests. Numbers higher than 6
+ usually do no increase performance. Default is sufficient for up
+ to five concurrent users.
+ '';
+ };
+
+ fakestat = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Return fake data on stat() calls. If <literal>true</literal>,
+ always do so. If <literal>false</literal>, only do so for
+ cross-cell mounts (as these are potentially expensive).
+ '';
+ };
+
+ inumcalc = mkOption {
+ default = "compat";
+ type = types.strMatching "compat|md5";
+ description = ''
+ Inode calculation method. <literal>compat</literal> is
+ computationally less expensive, but <literal>md5</literal> greatly
+ reduces the likelihood of inode collisions in larger scenarios
+ involving multiple cells mounted into one AFS space.
+ '';
+ };
+
+ mountPoint = mkOption {
+ default = "/afs";
+ type = types.str;
+ description = ''
+ Mountpoint of the AFS file tree, conventionally
+ <literal>/afs</literal>. When set to a different value, only
+ cross-cells that use the same value can be accessed.
+ '';
+ };
+
+ packages = {
+ module = mkOption {
+ default = config.boot.kernelPackages.openafs;
+ defaultText = "config.boot.kernelPackages.openafs";
+ type = types.package;
+ description = "OpenAFS kernel module package. MUST match the userland package!";
+ };
+ programs = mkOption {
+ default = getBin pkgs.openafs;
+ defaultText = "getBin pkgs.openafs";
+ type = types.package;
+ description = "OpenAFS programs package. MUST match the kernel module package!";
+ };
+ };
+
+ sparse = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Minimal cell list in /afs.";
+ };
+
+ startDisconnected = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Start up in disconnected mode. You need to execute
+ <literal>fs disco online</literal> (as root) to switch to
+ connected mode. Useful for roaming devices.
+ '';
+ };
+
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.afsdb || cfg.cellServDB != [];
+ message = "You should specify all cell-local database servers in config.services.openafsClient.cellServDB or set config.services.openafsClient.afsdb.";
+ }
+ { assertion = cfg.cellName != "";
+ message = "You must specify the local cell name in config.services.openafsClient.cellName.";
+ }
+ ];
+
+ environment.systemPackages = [ openafsBin ];
+
+ environment.etc = {
+ clientCellServDB = {
+ source = pkgs.runCommand "CellServDB" { preferLocalBuild = true; } ''
+ cat ${cellServDB} ${clientServDB} > $out
+ '';
+ target = "openafs/CellServDB";
+ mode = "0644";
+ };
+ clientCell = {
+ text = ''
+ ${cfg.cellName}
+ '';
+ target = "openafs/ThisCell";
+ mode = "0644";
+ };
+ };
+
+ systemd.services.afsd = {
+ description = "AFS client";
+ wantedBy = [ "multi-user.target" ];
+ after = singleton (if cfg.startDisconnected then "network.target" else "network-online.target");
+ serviceConfig = { RemainAfterExit = true; };
+ restartIfChanged = false;
+
+ preStart = ''
+ mkdir -p -m 0755 ${cfg.mountPoint}
+ mkdir -m 0700 -p ${cfg.cache.directory}
+ ${pkgs.kmod}/bin/insmod ${openafsMod}/lib/modules/*/extra/openafs/libafs.ko.xz
+ ${openafsBin}/sbin/afsd \
+ -mountdir ${cfg.mountPoint} \
+ -confdir ${afsConfig} \
+ ${optionalString (!cfg.cache.diskless) "-cachedir ${cfg.cache.directory}"} \
+ -blocks ${toString cfg.cache.blocks} \
+ -chunksize ${toString cfg.cache.chunksize} \
+ ${optionalString cfg.cache.diskless "-memcache"} \
+ -inumcalc ${cfg.inumcalc} \
+ ${if cfg.fakestat then "-fakestat-all" else "-fakestat"} \
+ ${if cfg.sparse then "-dynroot-sparse" else "-dynroot"} \
+ ${optionalString cfg.afsdb "-afsdb"}
+ ${openafsBin}/bin/fs setcrypt ${if cfg.crypt then "on" else "off"}
+ ${optionalString cfg.startDisconnected "${openafsBin}/bin/fs discon offline"}
+ '';
+
+ # Doing this in preStop, because after these commands AFS is basically
+ # stopped, so systemd has nothing to do, just noticing it. If done in
+ # postStop, then we get a hang + kernel oops, because AFS can't be
+ # stopped simply by sending signals to processes.
+ preStop = ''
+ ${pkgs.utillinux}/bin/umount ${cfg.mountPoint}
+ ${openafsBin}/sbin/afsd -shutdown
+ ${pkgs.kmod}/sbin/rmmod libafs
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix b/nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix
new file mode 100644
index 00000000000..e068ee761c2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix
@@ -0,0 +1,33 @@
+{ config, lib, ...}:
+
+let
+ inherit (lib) concatStringsSep mkOption types;
+
+in {
+
+ mkCellServDB = cellName: db: ''
+ >${cellName}
+ '' + (concatStringsSep "\n" (map (dbm: if (dbm.ip != "" && dbm.dnsname != "") then dbm.ip + " #" + dbm.dnsname else "")
+ db))
+ + "\n";
+
+ # CellServDB configuration type
+ cellServDBConfig = {
+ ip = mkOption {
+ type = types.str;
+ default = "";
+ example = "1.2.3.4";
+ description = "IP Address of a database server";
+ };
+ dnsname = mkOption {
+ type = types.str;
+ default = "";
+ example = "afs.example.org";
+ description = "DNS full-qualified domain name of a database server";
+ };
+ };
+
+ openafsMod = config.services.openafsClient.packages.module;
+ openafsBin = config.services.openafsClient.packages.programs;
+ openafsSrv = config.services.openafsServer.package;
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix b/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix
new file mode 100644
index 00000000000..095024d2c8a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix
@@ -0,0 +1,269 @@
+{ config, lib, pkgs, ... }:
+
+# openafsBin, openafsSrv, mkCellServDB
+with import ./lib.nix { inherit config lib pkgs; };
+
+let
+ inherit (lib) concatStringsSep mkIf mkOption optionalString types;
+
+ bosConfig = pkgs.writeText "BosConfig" (''
+ restrictmode 1
+ restarttime 16 0 0 0 0
+ checkbintime 3 0 5 0 0
+ '' + (optionalString cfg.roles.database.enable ''
+ bnode simple vlserver 1
+ parm ${openafsSrv}/libexec/openafs/vlserver ${optionalString cfg.dottedPrincipals "-allow-dotted-principals"} ${cfg.roles.database.vlserverArgs}
+ end
+ bnode simple ptserver 1
+ parm ${openafsSrv}/libexec/openafs/ptserver ${optionalString cfg.dottedPrincipals "-allow-dotted-principals"} ${cfg.roles.database.ptserverArgs}
+ end
+ '') + (optionalString cfg.roles.fileserver.enable ''
+ bnode dafs dafs 1
+ parm ${openafsSrv}/libexec/openafs/dafileserver ${optionalString cfg.dottedPrincipals "-allow-dotted-principals"} -udpsize ${udpSizeStr} ${cfg.roles.fileserver.fileserverArgs}
+ parm ${openafsSrv}/libexec/openafs/davolserver ${optionalString cfg.dottedPrincipals "-allow-dotted-principals"} -udpsize ${udpSizeStr} ${cfg.roles.fileserver.volserverArgs}
+ parm ${openafsSrv}/libexec/openafs/salvageserver ${cfg.roles.fileserver.salvageserverArgs}
+ parm ${openafsSrv}/libexec/openafs/dasalvager ${cfg.roles.fileserver.salvagerArgs}
+ end
+ '') + (optionalString (cfg.roles.database.enable && cfg.roles.backup.enable) ''
+ bnode simple buserver 1
+ parm ${openafsSrv}/libexec/openafs/buserver ${cfg.roles.backup.buserverArgs} ${optionalString (cfg.roles.backup.cellServDB != []) "-cellservdb /etc/openafs/backup/"}
+ end
+ ''));
+
+ netInfo = if (cfg.advertisedAddresses != []) then
+ pkgs.writeText "NetInfo" ((concatStringsSep "\nf " cfg.advertisedAddresses) + "\n")
+ else null;
+
+ buCellServDB = pkgs.writeText "backup-cellServDB-${cfg.cellName}" (mkCellServDB cfg.cellName cfg.roles.backup.cellServDB);
+
+ cfg = config.services.openafsServer;
+
+ udpSizeStr = toString cfg.udpPacketSize;
+
+in {
+
+ options = {
+
+ services.openafsServer = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable the OpenAFS server. An OpenAFS server needs a
+ complex setup. So, be aware that enabling this service and setting
+ some options does not give you a turn-key-ready solution. You need
+ at least a running Kerberos 5 setup, as OpenAFS relies on it for
+ authentication. See the Guide "QuickStartUnix" coming with
+ <literal>pkgs.openafs.doc</literal> for complete setup
+ instructions.
+ '';
+ };
+
+ advertisedAddresses = mkOption {
+ default = [];
+ description = "List of IP addresses this server is advertised under. See NetInfo(5)";
+ };
+
+ cellName = mkOption {
+ default = "";
+ type = types.str;
+ description = "Cell name, this server will serve.";
+ example = "grand.central.org";
+ };
+
+ cellServDB = mkOption {
+ default = [];
+ type = with types; listOf (submodule [ { options = cellServDBConfig;} ]);
+ description = "Definition of all cell-local database server machines.";
+ };
+
+ package = mkOption {
+ default = pkgs.openafs.server or pkgs.openafs;
+ defaultText = "pkgs.openafs.server or pkgs.openafs";
+ type = types.package;
+ description = "OpenAFS package for the server binaries";
+ };
+
+ roles = {
+ fileserver = {
+ enable = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Fileserver role, serves files and volumes from its local storage.";
+ };
+
+ fileserverArgs = mkOption {
+ default = "-vattachpar 128 -vhashsize 11 -L -rxpck 400 -cb 1000000";
+ type = types.str;
+ description = "Arguments to the dafileserver process. See its man page.";
+ };
+
+ volserverArgs = mkOption {
+ default = "";
+ type = types.str;
+ description = "Arguments to the davolserver process. See its man page.";
+ example = "-sync never";
+ };
+
+ salvageserverArgs = mkOption {
+ default = "";
+ type = types.str;
+ description = "Arguments to the salvageserver process. See its man page.";
+ example = "-showlog";
+ };
+
+ salvagerArgs = mkOption {
+ default = "";
+ type = types.str;
+ description = "Arguments to the dasalvager process. See its man page.";
+ example = "-showlog -showmounts";
+ };
+ };
+
+ database = {
+ enable = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Database server role, maintains the Volume Location Database,
+ Protection Database (and Backup Database, see
+ <literal>backup</literal> role). There can be multiple
+ servers in the database role for replication, which then need
+ reliable network connection to each other.
+
+ Servers in this role appear in AFSDB DNS records or the
+ CellServDB.
+ '';
+ };
+
+ vlserverArgs = mkOption {
+ default = "";
+ type = types.str;
+ description = "Arguments to the vlserver process. See its man page.";
+ example = "-rxbind";
+ };
+
+ ptserverArgs = mkOption {
+ default = "";
+ type = types.str;
+ description = "Arguments to the ptserver process. See its man page.";
+ example = "-restricted -default_access S---- S-M---";
+ };
+ };
+
+ backup = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Backup server role. Use in conjunction with the
+ <literal>database</literal> role to maintain the Backup
+ Database. Normally only used in conjunction with tape storage
+ or IBM's Tivoli Storage Manager.
+ '';
+ };
+
+ buserverArgs = mkOption {
+ default = "";
+ type = types.str;
+ description = "Arguments to the buserver process. See its man page.";
+ example = "-p 8";
+ };
+
+ cellServDB = mkOption {
+ default = [];
+ type = with types; listOf (submodule [ { options = cellServDBConfig;} ]);
+ description = ''
+ Definition of all cell-local backup database server machines.
+ Use this when your cell uses less backup database servers than
+ other database server machines.
+ '';
+ };
+ };
+ };
+
+ dottedPrincipals= mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If enabled, allow principal names containing (.) dots. Enabling
+ this has security implications!
+ '';
+ };
+
+ udpPacketSize = mkOption {
+ default = 1310720;
+ type = types.int;
+ description = ''
+ UDP packet size to use in Bytes. Higher values can speed up
+ communications. The default of 1 MB is a sufficient in most
+ cases. Make sure to increase the kernel's UDP buffer size
+ accordingly via <literal>net.core(w|r|opt)mem_max</literal>
+ sysctl.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.cellServDB != [];
+ message = "You must specify all cell-local database servers in config.services.openafsServer.cellServDB.";
+ }
+ { assertion = cfg.cellName != "";
+ message = "You must specify the local cell name in config.services.openafsServer.cellName.";
+ }
+ ];
+
+ environment.systemPackages = [ openafsBin ];
+
+ environment.etc = {
+ bosConfig = {
+ source = bosConfig;
+ target = "openafs/BosConfig";
+ mode = "0644";
+ };
+ cellServDB = {
+ text = mkCellServDB cfg.cellName cfg.cellServDB;
+ target = "openafs/server/CellServDB";
+ mode = "0644";
+ };
+ thisCell = {
+ text = cfg.cellName;
+ target = "openafs/server/ThisCell";
+ mode = "0644";
+ };
+ buCellServDB = {
+ enable = (cfg.roles.backup.cellServDB != []);
+ text = mkCellServDB cfg.cellName cfg.roles.backup.cellServDB;
+ target = "openafs/backup/CellServDB";
+ };
+ };
+
+ systemd.services = {
+ openafs-server = {
+ description = "OpenAFS server";
+ after = [ "syslog.target" "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ restartIfChanged = false;
+ unitConfig.ConditionPathExists = [
+ "|/etc/openafs/server/rxkad.keytab"
+ "|/etc/openafs/server/KeyFileExt"
+ ];
+ preStart = ''
+ mkdir -m 0755 -p /var/openafs
+ ${optionalString (netInfo != null) "cp ${netInfo} /var/openafs/netInfo"}
+ ${optionalString (cfg.roles.backup.cellServDB != []) "cp ${buCellServDB}"}
+ '';
+ serviceConfig = {
+ ExecStart = "${openafsBin}/bin/bosserver -nofork";
+ ExecStop = "${openafsBin}/bin/bos shutdown localhost -wait -localauth";
+ };
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix b/nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix
new file mode 100644
index 00000000000..b17ec3aa930
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix
@@ -0,0 +1,124 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.rsyncd;
+
+ motdFile = builtins.toFile "rsyncd-motd" cfg.motd;
+
+ foreach = attrs: f:
+ concatStringsSep "\n" (mapAttrsToList f attrs);
+
+ cfgFile = ''
+ ${optionalString (cfg.motd != "") "motd file = ${motdFile}"}
+ ${optionalString (cfg.address != "") "address = ${cfg.address}"}
+ ${optionalString (cfg.port != 873) "port = ${toString cfg.port}"}
+ ${cfg.extraConfig}
+ ${foreach cfg.modules (name: module: ''
+ [${name}]
+ ${foreach module (k: v:
+ "${k} = ${v}"
+ )}
+ '')}
+ '';
+in
+
+{
+ options = {
+ services.rsyncd = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable the rsync daemon.";
+ };
+
+ motd = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Message of the day to display to clients on each connect.
+ This usually contains site information and any legal notices.
+ '';
+ };
+
+ port = mkOption {
+ default = 873;
+ type = types.int;
+ description = "TCP port the daemon will listen on.";
+ };
+
+ address = mkOption {
+ default = "";
+ example = "192.168.1.2";
+ description = ''
+ IP address the daemon will listen on; rsyncd will listen on
+ all addresses if this is not specified.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Lines of configuration to add to rsyncd globally.
+ See <command>man rsyncd.conf</command> for options.
+ '';
+ };
+
+ modules = mkOption {
+ default = {};
+ description = ''
+ A set describing exported directories.
+ See <command>man rsyncd.conf</command> for options.
+ '';
+ type = types.attrsOf (types.attrsOf types.str);
+ example =
+ { srv =
+ { path = "/srv";
+ "read only" = "yes";
+ comment = "Public rsync share.";
+ };
+ };
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "root";
+ description = ''
+ The user to run the daemon as.
+ By default the daemon runs as root.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "root";
+ description = ''
+ The group to run the daemon as.
+ By default the daemon runs as root.
+ '';
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.etc."rsyncd.conf".text = cfgFile;
+
+ systemd.services.rsyncd = {
+ description = "Rsync daemon";
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ config.environment.etc."rsyncd.conf".source ];
+ serviceConfig = {
+ ExecStart = "${pkgs.rsync}/bin/rsync --daemon --no-detach";
+ User = cfg.user;
+ Group = cfg.group;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/samba.nix b/nixpkgs/nixos/modules/services/network-filesystems/samba.nix
new file mode 100644
index 00000000000..055508a3224
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/samba.nix
@@ -0,0 +1,253 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ smbToString = x: if builtins.typeOf x == "bool"
+ then boolToString x
+ else toString x;
+
+ cfg = config.services.samba;
+
+ samba = cfg.package;
+
+ setupScript =
+ ''
+ mkdir -p /var/lock/samba /var/log/samba /var/cache/samba /var/lib/samba/private
+ '';
+
+ shareConfig = name:
+ let share = getAttr name cfg.shares; in
+ "[${name}]\n " + (smbToString (
+ map
+ (key: "${key} = ${smbToString (getAttr key share)}\n")
+ (attrNames share)
+ ));
+
+ configFile = pkgs.writeText "smb.conf"
+ (if cfg.configText != null then cfg.configText else
+ ''
+ [global]
+ security = ${cfg.securityType}
+ passwd program = /run/wrappers/bin/passwd %u
+ pam password change = ${smbToString cfg.syncPasswordsByPam}
+ invalid users = ${smbToString cfg.invalidUsers}
+
+ ${cfg.extraConfig}
+
+ ${smbToString (map shareConfig (attrNames cfg.shares))}
+ '');
+
+ # This may include nss_ldap, needed for samba if it has to use ldap.
+ nssModulesPath = config.system.nssModules.path;
+
+ daemonService = appName: args:
+ { description = "Samba Service Daemon ${appName}";
+
+ requiredBy = [ "samba.target" ];
+ partOf = [ "samba.target" ];
+
+ environment = {
+ LD_LIBRARY_PATH = nssModulesPath;
+ LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
+ };
+
+ serviceConfig = {
+ ExecStart = "${samba}/sbin/${appName} --foreground --no-process-group ${args}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ LimitNOFILE = 16384;
+ PIDFile = "/run/${appName}.pid";
+ Type = "notify";
+ NotifyAccess = "all"; #may not do anything...
+ };
+
+ restartTriggers = [ configFile ];
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ # !!! clean up the descriptions.
+
+ services.samba = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Samba, which provides file and print
+ services to Windows clients through the SMB/CIFS protocol.
+
+ <note>
+ <para>If you use the firewall consider adding the following:</para>
+ <programlisting>
+ networking.firewall.allowedTCPPorts = [ 139 445 ];
+ networking.firewall.allowedUDPPorts = [ 137 138 ];
+ </programlisting>
+ </note>
+ '';
+ };
+
+ enableNmbd = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable Samba's nmbd, which replies to NetBIOS over IP name
+ service requests. It also participates in the browsing protocols
+ which make up the Windows "Network Neighborhood" view.
+ '';
+ };
+
+ enableWinbindd = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable Samba's winbindd, which provides a number of services
+ to the Name Service Switch capability found in most modern C libraries,
+ to arbitrary applications via PAM and ntlm_auth and to Samba itself.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.samba;
+ defaultText = "pkgs.samba";
+ example = literalExample "pkgs.samba3";
+ description = ''
+ Defines which package should be used for the samba server.
+ '';
+ };
+
+ syncPasswordsByPam = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enabling this will add a line directly after pam_unix.so.
+ Whenever a password is changed the samba password will be updated as well.
+ However, you still have to add the samba password once, using smbpasswd -a user.
+ If you don't want to maintain an extra password database, you still can send plain text
+ passwords which is not secure.
+ '';
+ };
+
+ invalidUsers = mkOption {
+ type = types.listOf types.str;
+ default = [ "root" ];
+ description = ''
+ List of users who are denied to login via Samba.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional global section and extra section lines go in here.
+ '';
+ example = ''
+ guest account = nobody
+ map to guest = bad user
+ '';
+ };
+
+ configText = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Verbatim contents of smb.conf. If null (default), use the
+ autogenerated file from NixOS instead.
+ '';
+ };
+
+ securityType = mkOption {
+ type = types.str;
+ default = "user";
+ example = "share";
+ description = "Samba security type";
+ };
+
+ nsswins = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable the WINS NSS (Name Service Switch) plug-in.
+ Enabling it allows applications to resolve WINS/NetBIOS names (a.k.a.
+ Windows machine names) by transparently querying the winbindd daemon.
+ '';
+ };
+
+ shares = mkOption {
+ default = {};
+ description = ''
+ A set describing shared resources.
+ See <command>man smb.conf</command> for options.
+ '';
+ type = types.attrsOf (types.attrsOf types.unspecified);
+ example =
+ { public =
+ { path = "/srv/public";
+ "read only" = true;
+ browseable = "yes";
+ "guest ok" = "yes";
+ comment = "Public samba share.";
+ };
+ };
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkMerge
+ [ { assertions =
+ [ { assertion = cfg.nsswins -> cfg.enableWinbindd;
+ message = "If samba.nsswins is enabled, then samba.enableWinbindd must also be enabled";
+ }
+ ];
+ # Always provide a smb.conf to shut up programs like smbclient and smbspool.
+ environment.etc."samba/smb.conf".source = mkOptionDefault (
+ if cfg.enable then configFile
+ else pkgs.writeText "smb-dummy.conf" "# Samba is disabled."
+ );
+ }
+
+ (mkIf cfg.enable {
+
+ system.nssModules = optional cfg.nsswins samba;
+
+ systemd = {
+ targets.samba = {
+ description = "Samba Server";
+ requires = [ "samba-setup.service" ];
+ after = [ "samba-setup.service" "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ };
+ # Refer to https://github.com/samba-team/samba/tree/master/packaging/systemd
+ # for correct use with systemd
+ services = {
+ samba-smbd = daemonService "smbd" "";
+ samba-nmbd = mkIf cfg.enableNmbd (daemonService "nmbd" "");
+ samba-winbindd = mkIf cfg.enableWinbindd (daemonService "winbindd" "");
+ samba-setup = {
+ description = "Samba Setup Task";
+ script = setupScript;
+ unitConfig.RequiresMountsFor = "/var/lib/samba";
+ };
+ };
+ };
+
+ security.pam.services.samba = {};
+
+ })
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix b/nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix
new file mode 100644
index 00000000000..7d75eb28610
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix
@@ -0,0 +1,368 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.tahoe;
+in
+ {
+ options.services.tahoe = {
+ introducers = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule {
+ options = {
+ nickname = mkOption {
+ type = types.str;
+ description = ''
+ The nickname of this Tahoe introducer.
+ '';
+ };
+ tub.port = mkOption {
+ default = 3458;
+ type = types.int;
+ description = ''
+ The port on which the introducer will listen.
+ '';
+ };
+ tub.location = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The external location that the introducer should listen on.
+
+ If specified, the port should be included.
+ '';
+ };
+ package = mkOption {
+ default = pkgs.tahoelafs;
+ defaultText = "pkgs.tahoelafs";
+ type = types.package;
+ example = literalExample "pkgs.tahoelafs";
+ description = ''
+ The package to use for the Tahoe LAFS daemon.
+ '';
+ };
+ };
+ });
+ description = ''
+ The Tahoe introducers.
+ '';
+ };
+ nodes = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule {
+ options = {
+ nickname = mkOption {
+ type = types.str;
+ description = ''
+ The nickname of this Tahoe node.
+ '';
+ };
+ tub.port = mkOption {
+ default = 3457;
+ type = types.int;
+ description = ''
+ The port on which the tub will listen.
+
+ This is the correct setting to tweak if you want Tahoe's storage
+ system to listen on a different port.
+ '';
+ };
+ tub.location = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The external location that the node should listen on.
+
+ This is the setting to tweak if there are multiple interfaces
+ and you want to alter which interface Tahoe is advertising.
+
+ If specified, the port should be included.
+ '';
+ };
+ web.port = mkOption {
+ default = 3456;
+ type = types.int;
+ description = ''
+ The port on which the Web server will listen.
+
+ This is the correct setting to tweak if you want Tahoe's WUI to
+ listen on a different port.
+ '';
+ };
+ client.introducer = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The furl for a Tahoe introducer node.
+
+ Like all furls, keep this safe and don't share it.
+ '';
+ };
+ client.helper = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The furl for a Tahoe helper node.
+
+ Like all furls, keep this safe and don't share it.
+ '';
+ };
+ client.shares.needed = mkOption {
+ default = 3;
+ type = types.int;
+ description = ''
+ The number of shares required to reconstitute a file.
+ '';
+ };
+ client.shares.happy = mkOption {
+ default = 7;
+ type = types.int;
+ description = ''
+ The number of distinct storage nodes required to store
+ a file.
+ '';
+ };
+ client.shares.total = mkOption {
+ default = 10;
+ type = types.int;
+ description = ''
+ The number of shares required to store a file.
+ '';
+ };
+ storage.enable = mkEnableOption "storage service";
+ storage.reservedSpace = mkOption {
+ default = "1G";
+ type = types.str;
+ description = ''
+ The amount of filesystem space to not use for storage.
+ '';
+ };
+ helper.enable = mkEnableOption "helper service";
+ sftpd.enable = mkEnableOption "SFTP service";
+ sftpd.port = mkOption {
+ default = null;
+ type = types.nullOr types.int;
+ description = ''
+ The port on which the SFTP server will listen.
+
+ This is the correct setting to tweak if you want Tahoe's SFTP
+ daemon to listen on a different port.
+ '';
+ };
+ sftpd.hostPublicKeyFile = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = ''
+ Path to the SSH host public key.
+ '';
+ };
+ sftpd.hostPrivateKeyFile = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = ''
+ Path to the SSH host private key.
+ '';
+ };
+ sftpd.accounts.file = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = ''
+ Path to the accounts file.
+ '';
+ };
+ sftpd.accounts.url = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ URL of the accounts server.
+ '';
+ };
+ package = mkOption {
+ default = pkgs.tahoelafs;
+ defaultText = "pkgs.tahoelafs";
+ type = types.package;
+ example = literalExample "pkgs.tahoelafs";
+ description = ''
+ The package to use for the Tahoe LAFS daemon.
+ '';
+ };
+ };
+ });
+ description = ''
+ The Tahoe nodes.
+ '';
+ };
+ };
+ config = mkMerge [
+ (mkIf (cfg.introducers != {}) {
+ environment = {
+ etc = flip mapAttrs' cfg.introducers (node: settings:
+ nameValuePair "tahoe-lafs/introducer-${node}.cfg" {
+ mode = "0444";
+ text = ''
+ # This configuration is generated by Nix. Edit at your own
+ # peril; here be dragons.
+
+ [node]
+ nickname = ${settings.nickname}
+ tub.port = ${toString settings.tub.port}
+ ${optionalString (settings.tub.location != null)
+ "tub.location = ${settings.tub.location}"}
+ '';
+ });
+ # Actually require Tahoe, so that we will have it installed.
+ systemPackages = flip mapAttrsToList cfg.introducers (node: settings:
+ settings.package
+ );
+ };
+ # Open up the firewall.
+ # networking.firewall.allowedTCPPorts = flip mapAttrsToList cfg.introducers
+ # (node: settings: settings.tub.port);
+ systemd.services = flip mapAttrs' cfg.introducers (node: settings:
+ let
+ pidfile = "/run/tahoe.introducer-${node}.pid";
+ # This is a directory, but it has no trailing slash. Tahoe commands
+ # get antsy when there's a trailing slash.
+ nodedir = "/var/db/tahoe-lafs/introducer-${node}";
+ in nameValuePair "tahoe.introducer-${node}" {
+ description = "Tahoe LAFS node ${node}";
+ wantedBy = [ "multi-user.target" ];
+ path = [ settings.package ];
+ restartTriggers = [
+ config.environment.etc."tahoe-lafs/introducer-${node}.cfg".source ];
+ serviceConfig = {
+ Type = "simple";
+ PIDFile = pidfile;
+ # Believe it or not, Tahoe is very brittle about the order of
+ # arguments to $(tahoe run). The node directory must come first,
+ # and arguments which alter Twisted's behavior come afterwards.
+ ExecStart = ''
+ ${settings.package}/bin/tahoe run ${lib.escapeShellArg nodedir} --pidfile=${lib.escapeShellArg pidfile}
+ '';
+ };
+ preStart = ''
+ if [ ! -d ${lib.escapeShellArg nodedir} ]; then
+ mkdir -p /var/db/tahoe-lafs
+ # See https://github.com/NixOS/nixpkgs/issues/25273
+ tahoe create-introducer \
+ --hostname="${config.networking.hostName}" \
+ ${lib.escapeShellArg nodedir}
+ fi
+
+ # Tahoe has created a predefined tahoe.cfg which we must now
+ # scribble over.
+ # XXX I thought that a symlink would work here, but it doesn't, so
+ # we must do this on every prestart. Fixes welcome.
+ # rm ${nodedir}/tahoe.cfg
+ # ln -s /etc/tahoe-lafs/introducer-${node}.cfg ${nodedir}/tahoe.cfg
+ cp /etc/tahoe-lafs/introducer-"${node}".cfg ${lib.escapeShellArg nodedir}/tahoe.cfg
+ '';
+ });
+ users.users = flip mapAttrs' cfg.introducers (node: _:
+ nameValuePair "tahoe.introducer-${node}" {
+ description = "Tahoe node user for introducer ${node}";
+ isSystemUser = true;
+ });
+ })
+ (mkIf (cfg.nodes != {}) {
+ environment = {
+ etc = flip mapAttrs' cfg.nodes (node: settings:
+ nameValuePair "tahoe-lafs/${node}.cfg" {
+ mode = "0444";
+ text = ''
+ # This configuration is generated by Nix. Edit at your own
+ # peril; here be dragons.
+
+ [node]
+ nickname = ${settings.nickname}
+ tub.port = ${toString settings.tub.port}
+ ${optionalString (settings.tub.location != null)
+ "tub.location = ${settings.tub.location}"}
+ # This is a Twisted endpoint. Twisted Web doesn't work on
+ # non-TCP. ~ C.
+ web.port = tcp:${toString settings.web.port}
+
+ [client]
+ ${optionalString (settings.client.introducer != null)
+ "introducer.furl = ${settings.client.introducer}"}
+ ${optionalString (settings.client.helper != null)
+ "helper.furl = ${settings.client.helper}"}
+
+ shares.needed = ${toString settings.client.shares.needed}
+ shares.happy = ${toString settings.client.shares.happy}
+ shares.total = ${toString settings.client.shares.total}
+
+ [storage]
+ enabled = ${boolToString settings.storage.enable}
+ reserved_space = ${settings.storage.reservedSpace}
+
+ [helper]
+ enabled = ${boolToString settings.helper.enable}
+
+ [sftpd]
+ enabled = ${boolToString settings.sftpd.enable}
+ ${optionalString (settings.sftpd.port != null)
+ "port = ${toString settings.sftpd.port}"}
+ ${optionalString (settings.sftpd.hostPublicKeyFile != null)
+ "host_pubkey_file = ${settings.sftpd.hostPublicKeyFile}"}
+ ${optionalString (settings.sftpd.hostPrivateKeyFile != null)
+ "host_privkey_file = ${settings.sftpd.hostPrivateKeyFile}"}
+ ${optionalString (settings.sftpd.accounts.file != null)
+ "accounts.file = ${settings.sftpd.accounts.file}"}
+ ${optionalString (settings.sftpd.accounts.url != null)
+ "accounts.url = ${settings.sftpd.accounts.url}"}
+ '';
+ });
+ # Actually require Tahoe, so that we will have it installed.
+ systemPackages = flip mapAttrsToList cfg.nodes (node: settings:
+ settings.package
+ );
+ };
+ # Open up the firewall.
+ # networking.firewall.allowedTCPPorts = flip mapAttrsToList cfg.nodes
+ # (node: settings: settings.tub.port);
+ systemd.services = flip mapAttrs' cfg.nodes (node: settings:
+ let
+ pidfile = "/run/tahoe.${node}.pid";
+ # This is a directory, but it has no trailing slash. Tahoe commands
+ # get antsy when there's a trailing slash.
+ nodedir = "/var/db/tahoe-lafs/${node}";
+ in nameValuePair "tahoe.${node}" {
+ description = "Tahoe LAFS node ${node}";
+ wantedBy = [ "multi-user.target" ];
+ path = [ settings.package ];
+ restartTriggers = [
+ config.environment.etc."tahoe-lafs/${node}.cfg".source ];
+ serviceConfig = {
+ Type = "simple";
+ PIDFile = pidfile;
+ # Believe it or not, Tahoe is very brittle about the order of
+ # arguments to $(tahoe run). The node directory must come first,
+ # and arguments which alter Twisted's behavior come afterwards.
+ ExecStart = ''
+ ${settings.package}/bin/tahoe run ${lib.escapeShellArg nodedir} --pidfile=${lib.escapeShellArg pidfile}
+ '';
+ };
+ preStart = ''
+ if [ ! -d ${lib.escapeShellArg nodedir} ]; then
+ mkdir -p /var/db/tahoe-lafs
+ tahoe create-node --hostname=localhost ${lib.escapeShellArg nodedir}
+ fi
+
+ # Tahoe has created a predefined tahoe.cfg which we must now
+ # scribble over.
+ # XXX I thought that a symlink would work here, but it doesn't, so
+ # we must do this on every prestart. Fixes welcome.
+ # rm ${nodedir}/tahoe.cfg
+ # ln -s /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${nodedir}/tahoe.cfg
+ cp /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${lib.escapeShellArg nodedir}/tahoe.cfg
+ '';
+ });
+ users.users = flip mapAttrs' cfg.nodes (node: _:
+ nameValuePair "tahoe.${node}" {
+ description = "Tahoe node user for node ${node}";
+ isSystemUser = true;
+ });
+ })
+ ];
+ }
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/u9fs.nix b/nixpkgs/nixos/modules/services/network-filesystems/u9fs.nix
new file mode 100644
index 00000000000..77961b78cad
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/u9fs.nix
@@ -0,0 +1,78 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.u9fs;
+in
+{
+
+ options = {
+
+ services.u9fs = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to run the u9fs 9P server for Unix.";
+ };
+
+ listenStreams = mkOption {
+ type = types.listOf types.str;
+ default = [ "564" ];
+ example = [ "192.168.16.1:564" ];
+ description = ''
+ Sockets to listen for clients on.
+ See <command>man 5 systemd.socket</command> for socket syntax.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "nobody";
+ description =
+ "User to run u9fs under.";
+ };
+
+ extraArgs = mkOption {
+ type = types.str;
+ default = "";
+ example = "-a none";
+ description =
+ ''
+ Extra arguments to pass on invocation,
+ see <command>man 4 u9fs</command>
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd = {
+ sockets.u9fs = {
+ description = "U9fs Listening Socket";
+ wantedBy = [ "sockets.target" ];
+ after = [ "network.target" ];
+ inherit (cfg) listenStreams;
+ socketConfig.Accept = "yes";
+ };
+ services."u9fs@" = {
+ description = "9P Protocol Server";
+ reloadIfChanged = true;
+ requires = [ "u9fs.socket" ];
+ serviceConfig =
+ { ExecStart = "-${pkgs.u9fs}/bin/u9fs ${cfg.extraArgs}";
+ StandardInput = "socket";
+ StandardError = "journal";
+ User = cfg.user;
+ AmbientCapabilities = "cap_setuid cap_setgid";
+ };
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix
new file mode 100644
index 00000000000..c93e201da56
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix
@@ -0,0 +1,474 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xtreemfs;
+
+ xtreemfs = pkgs.xtreemfs;
+
+ home = cfg.homeDir;
+
+ startupScript = class: configPath: pkgs.writeScript "xtreemfs-osd.sh" ''
+ #! ${pkgs.runtimeShell}
+ JAVA_HOME="${pkgs.jdk}"
+ JAVADIR="${xtreemfs}/share/java"
+ JAVA_CALL="$JAVA_HOME/bin/java -ea -cp $JAVADIR/XtreemFS.jar:$JAVADIR/BabuDB.jar:$JAVADIR/Flease.jar:$JAVADIR/protobuf-java-2.5.0.jar:$JAVADIR/Foundation.jar:$JAVADIR/jdmkrt.jar:$JAVADIR/jdmktk.jar:$JAVADIR/commons-codec-1.3.jar"
+ $JAVA_CALL ${class} ${configPath}
+ '';
+
+ dirReplicationConfig = pkgs.writeText "xtreemfs-dir-replication-plugin.properties" ''
+ babudb.repl.backupDir = ${home}/server-repl-dir
+ plugin.jar = ${xtreemfs}/share/java/BabuDB_replication_plugin.jar
+ babudb.repl.dependency.0 = ${xtreemfs}/share/java/Flease.jar
+
+ ${cfg.dir.replication.extraConfig}
+ '';
+
+ dirConfig = pkgs.writeText "xtreemfs-dir-config.properties" ''
+ uuid = ${cfg.dir.uuid}
+ listen.port = ${toString cfg.dir.port}
+ ${optionalString (cfg.dir.address != "") "listen.address = ${cfg.dir.address}"}
+ http_port = ${toString cfg.dir.httpPort}
+ babudb.baseDir = ${home}/dir/database
+ babudb.logDir = ${home}/dir/db-log
+ babudb.sync = ${if cfg.dir.replication.enable then "FDATASYNC" else cfg.dir.syncMode}
+
+ ${optionalString cfg.dir.replication.enable "babudb.plugin.0 = ${dirReplicationConfig}"}
+
+ ${cfg.dir.extraConfig}
+ '';
+
+ mrcReplicationConfig = pkgs.writeText "xtreemfs-mrc-replication-plugin.properties" ''
+ babudb.repl.backupDir = ${home}/server-repl-mrc
+ plugin.jar = ${xtreemfs}/share/java/BabuDB_replication_plugin.jar
+ babudb.repl.dependency.0 = ${xtreemfs}/share/java/Flease.jar
+
+ ${cfg.mrc.replication.extraConfig}
+ '';
+
+ mrcConfig = pkgs.writeText "xtreemfs-mrc-config.properties" ''
+ uuid = ${cfg.mrc.uuid}
+ listen.port = ${toString cfg.mrc.port}
+ ${optionalString (cfg.mrc.address != "") "listen.address = ${cfg.mrc.address}"}
+ http_port = ${toString cfg.mrc.httpPort}
+ babudb.baseDir = ${home}/mrc/database
+ babudb.logDir = ${home}/mrc/db-log
+ babudb.sync = ${if cfg.mrc.replication.enable then "FDATASYNC" else cfg.mrc.syncMode}
+
+ ${optionalString cfg.mrc.replication.enable "babudb.plugin.0 = ${mrcReplicationConfig}"}
+
+ ${cfg.mrc.extraConfig}
+ '';
+
+ osdConfig = pkgs.writeText "xtreemfs-osd-config.properties" ''
+ uuid = ${cfg.osd.uuid}
+ listen.port = ${toString cfg.osd.port}
+ ${optionalString (cfg.osd.address != "") "listen.address = ${cfg.osd.address}"}
+ http_port = ${toString cfg.osd.httpPort}
+ object_dir = ${home}/osd/
+
+ ${cfg.osd.extraConfig}
+ '';
+
+ optionalDir = optionals cfg.dir.enable ["xtreemfs-dir.service"];
+
+ systemdOptionalDependencies = {
+ after = [ "network.target" ] ++ optionalDir;
+ wantedBy = [ "multi-user.target" ] ++ optionalDir;
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.xtreemfs = {
+
+ enable = mkEnableOption "XtreemFS";
+
+ homeDir = mkOption {
+ default = "/var/lib/xtreemfs";
+ description = ''
+ XtreemFS home dir for the xtreemfs user.
+ '';
+ };
+
+ dir = {
+ enable = mkOption {
+ default = true;
+ description = ''
+ Whether to enable XtreemFS DIR service.
+ '';
+ };
+ uuid = mkOption {
+ example = "eacb6bab-f444-4ebf-a06a-3f72d7465e40";
+ description = ''
+ Must be set to a unique identifier, preferably a UUID according to
+ RFC 4122. UUIDs can be generated with `uuidgen` command, found in
+ the `utillinux` package.
+ '';
+ };
+ port = mkOption {
+ default = 32638;
+ description = ''
+ The port to listen on for incoming connections (TCP).
+ '';
+ };
+ address = mkOption {
+ example = "127.0.0.1";
+ default = "";
+ description = ''
+ If specified, it defines the interface to listen on. If not
+ specified, the service will listen on all interfaces (any).
+ '';
+ };
+ httpPort = mkOption {
+ default = 30638;
+ description = ''
+ Specifies the listen port for the HTTP service that returns the
+ status page.
+ '';
+ };
+ syncMode = mkOption {
+ default = "FSYNC";
+ example = "FDATASYNC";
+ description = ''
+ The sync mode influences how operations are committed to the disk
+ log before the operation is acknowledged to the caller.
+
+ -ASYNC mode the writes to the disk log are buffered in memory by the operating system. This is the fastest mode but will lead to data loss in case of a crash, kernel panic or power failure.
+ -SYNC_WRITE_METADATA opens the file with O_SYNC, the system will not buffer any writes. The operation will be acknowledged when data has been safely written to disk. This mode is slow but offers maximum data safety. However, BabuDB cannot influence the disk drive caches, this depends on the OS and hard disk model.
+ -SYNC_WRITE similar to SYNC_WRITE_METADATA but opens file with O_DSYNC which means that only the data is commit to disk. This can lead to some data loss depending on the implementation of the underlying file system. Linux does not implement this mode.
+ -FDATASYNC is similar to SYNC_WRITE but opens the file in asynchronous mode and calls fdatasync() after writing the data to disk.
+ -FSYNC is similar to SYNC_WRITE_METADATA but opens the file in asynchronous mode and calls fsync() after writing the data to disk.
+
+ For best throughput use ASYNC, for maximum data safety use FSYNC.
+
+ (If xtreemfs.dir.replication.enable is true then FDATASYNC is forced)
+ '';
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ # specify whether SSL is required
+ ssl.enabled = true
+ ssl.service_creds.pw = passphrase
+ ssl.service_creds.container = pkcs12
+ ssl.service_creds = /etc/xos/xtreemfs/truststore/certs/dir.p12
+ ssl.trusted_certs = /etc/xos/xtreemfs/truststore/certs/trusted.jks
+ ssl.trusted_certs.pw = jks_passphrase
+ ssl.trusted_certs.container = jks
+ '';
+ description = ''
+ Configuration of XtreemFS DIR service.
+ WARNING: configuration is saved as plaintext inside nix store.
+ For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+ '';
+ };
+ replication = {
+ enable = mkEnableOption "XtreemFS DIR replication plugin";
+ extraConfig = mkOption {
+ type = types.lines;
+ example = ''
+ # participants of the replication including this replica
+ babudb.repl.participant.0 = 192.168.0.10
+ babudb.repl.participant.0.port = 35676
+ babudb.repl.participant.1 = 192.168.0.11
+ babudb.repl.participant.1.port = 35676
+ babudb.repl.participant.2 = 192.168.0.12
+ babudb.repl.participant.2.port = 35676
+
+ # number of servers that at least have to be up to date
+ # To have a fault-tolerant system, this value has to be set to the
+ # majority of nodes i.e., if you have three replicas, set this to 2
+ # Please note that a setup with two nodes provides no fault-tolerance.
+ babudb.repl.sync.n = 2
+
+ # specify whether SSL is required
+ babudb.ssl.enabled = true
+
+ babudb.ssl.protocol = tlsv12
+
+ # server credentials for SSL handshakes
+ babudb.ssl.service_creds = /etc/xos/xtreemfs/truststore/certs/osd.p12
+ babudb.ssl.service_creds.pw = passphrase
+ babudb.ssl.service_creds.container = pkcs12
+
+ # trusted certificates for SSL handshakes
+ babudb.ssl.trusted_certs = /etc/xos/xtreemfs/truststore/certs/trusted.jks
+ babudb.ssl.trusted_certs.pw = jks_passphrase
+ babudb.ssl.trusted_certs.container = jks
+
+ babudb.ssl.authenticationWithoutEncryption = false
+ '';
+ description = ''
+ Configuration of XtreemFS DIR replication plugin.
+ WARNING: configuration is saved as plaintext inside nix store.
+ For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+ '';
+ };
+ };
+ };
+
+ mrc = {
+ enable = mkOption {
+ default = true;
+ description = ''
+ Whether to enable XtreemFS MRC service.
+ '';
+ };
+ uuid = mkOption {
+ example = "eacb6bab-f444-4ebf-a06a-3f72d7465e41";
+ description = ''
+ Must be set to a unique identifier, preferably a UUID according to
+ RFC 4122. UUIDs can be generated with `uuidgen` command, found in
+ the `utillinux` package.
+ '';
+ };
+ port = mkOption {
+ default = 32636;
+ description = ''
+ The port to listen on for incoming connections (TCP).
+ '';
+ };
+ address = mkOption {
+ example = "127.0.0.1";
+ default = "";
+ description = ''
+ If specified, it defines the interface to listen on. If not
+ specified, the service will listen on all interfaces (any).
+ '';
+ };
+ httpPort = mkOption {
+ default = 30636;
+ description = ''
+ Specifies the listen port for the HTTP service that returns the
+ status page.
+ '';
+ };
+ syncMode = mkOption {
+ default = "FSYNC";
+ example = "FDATASYNC";
+ description = ''
+ The sync mode influences how operations are committed to the disk
+ log before the operation is acknowledged to the caller.
+
+ -ASYNC mode the writes to the disk log are buffered in memory by the operating system. This is the fastest mode but will lead to data loss in case of a crash, kernel panic or power failure.
+ -SYNC_WRITE_METADATA opens the file with O_SYNC, the system will not buffer any writes. The operation will be acknowledged when data has been safely written to disk. This mode is slow but offers maximum data safety. However, BabuDB cannot influence the disk drive caches, this depends on the OS and hard disk model.
+ -SYNC_WRITE similar to SYNC_WRITE_METADATA but opens file with O_DSYNC which means that only the data is commit to disk. This can lead to some data loss depending on the implementation of the underlying file system. Linux does not implement this mode.
+ -FDATASYNC is similar to SYNC_WRITE but opens the file in asynchronous mode and calls fdatasync() after writing the data to disk.
+ -FSYNC is similar to SYNC_WRITE_METADATA but opens the file in asynchronous mode and calls fsync() after writing the data to disk.
+
+ For best throughput use ASYNC, for maximum data safety use FSYNC.
+
+ (If xtreemfs.mrc.replication.enable is true then FDATASYNC is forced)
+ '';
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ example = ''
+ osd_check_interval = 300
+ no_atime = true
+ local_clock_renewal = 0
+ remote_time_sync = 30000
+ authentication_provider = org.xtreemfs.common.auth.NullAuthProvider
+
+ # shared secret between the MRC and all OSDs
+ capability_secret = iNG8UuQJrJ6XVDTe
+
+ dir_service.host = 192.168.0.10
+ dir_service.port = 32638
+
+ # if replication is enabled
+ dir_service.1.host = 192.168.0.11
+ dir_service.1.port = 32638
+ dir_service.2.host = 192.168.0.12
+ dir_service.2.port = 32638
+
+ # specify whether SSL is required
+ ssl.enabled = true
+ ssl.protocol = tlsv12
+ ssl.service_creds.pw = passphrase
+ ssl.service_creds.container = pkcs12
+ ssl.service_creds = /etc/xos/xtreemfs/truststore/certs/mrc.p12
+ ssl.trusted_certs = /etc/xos/xtreemfs/truststore/certs/trusted.jks
+ ssl.trusted_certs.pw = jks_passphrase
+ ssl.trusted_certs.container = jks
+ '';
+ description = ''
+ Configuration of XtreemFS MRC service.
+ WARNING: configuration is saved as plaintext inside nix store.
+ For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+ '';
+ };
+ replication = {
+ enable = mkEnableOption "XtreemFS MRC replication plugin";
+ extraConfig = mkOption {
+ type = types.lines;
+ example = ''
+ # participants of the replication including this replica
+ babudb.repl.participant.0 = 192.168.0.10
+ babudb.repl.participant.0.port = 35678
+ babudb.repl.participant.1 = 192.168.0.11
+ babudb.repl.participant.1.port = 35678
+ babudb.repl.participant.2 = 192.168.0.12
+ babudb.repl.participant.2.port = 35678
+
+ # number of servers that at least have to be up to date
+ # To have a fault-tolerant system, this value has to be set to the
+ # majority of nodes i.e., if you have three replicas, set this to 2
+ # Please note that a setup with two nodes provides no fault-tolerance.
+ babudb.repl.sync.n = 2
+
+ # specify whether SSL is required
+ babudb.ssl.enabled = true
+
+ babudb.ssl.protocol = tlsv12
+
+ # server credentials for SSL handshakes
+ babudb.ssl.service_creds = /etc/xos/xtreemfs/truststore/certs/osd.p12
+ babudb.ssl.service_creds.pw = passphrase
+ babudb.ssl.service_creds.container = pkcs12
+
+ # trusted certificates for SSL handshakes
+ babudb.ssl.trusted_certs = /etc/xos/xtreemfs/truststore/certs/trusted.jks
+ babudb.ssl.trusted_certs.pw = jks_passphrase
+ babudb.ssl.trusted_certs.container = jks
+
+ babudb.ssl.authenticationWithoutEncryption = false
+ '';
+ description = ''
+ Configuration of XtreemFS MRC replication plugin.
+ WARNING: configuration is saved as plaintext inside nix store.
+ For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+ '';
+ };
+ };
+ };
+
+ osd = {
+ enable = mkOption {
+ default = true;
+ description = ''
+ Whether to enable XtreemFS OSD service.
+ '';
+ };
+ uuid = mkOption {
+ example = "eacb6bab-f444-4ebf-a06a-3f72d7465e42";
+ description = ''
+ Must be set to a unique identifier, preferably a UUID according to
+ RFC 4122. UUIDs can be generated with `uuidgen` command, found in
+ the `utillinux` package.
+ '';
+ };
+ port = mkOption {
+ default = 32640;
+ description = ''
+ The port to listen on for incoming connections (TCP and UDP).
+ '';
+ };
+ address = mkOption {
+ example = "127.0.0.1";
+ default = "";
+ description = ''
+ If specified, it defines the interface to listen on. If not
+ specified, the service will listen on all interfaces (any).
+ '';
+ };
+ httpPort = mkOption {
+ default = 30640;
+ description = ''
+ Specifies the listen port for the HTTP service that returns the
+ status page.
+ '';
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ example = ''
+ local_clock_renewal = 0
+ remote_time_sync = 30000
+ report_free_space = true
+ capability_secret = iNG8UuQJrJ6XVDTe
+
+ dir_service.host = 192.168.0.10
+ dir_service.port = 32638
+
+ # if replication is used
+ dir_service.1.host = 192.168.0.11
+ dir_service.1.port = 32638
+ dir_service.2.host = 192.168.0.12
+ dir_service.2.port = 32638
+
+ # specify whether SSL is required
+ ssl.enabled = true
+ ssl.service_creds.pw = passphrase
+ ssl.service_creds.container = pkcs12
+ ssl.service_creds = /etc/xos/xtreemfs/truststore/certs/osd.p12
+ ssl.trusted_certs = /etc/xos/xtreemfs/truststore/certs/trusted.jks
+ ssl.trusted_certs.pw = jks_passphrase
+ ssl.trusted_certs.container = jks
+ '';
+ description = ''
+ Configuration of XtreemFS OSD service.
+ WARNING: configuration is saved as plaintext inside nix store.
+ For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+ '';
+ };
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = lib.mkIf cfg.enable {
+
+ environment.systemPackages = [ xtreemfs ];
+
+ users.users.xtreemfs =
+ { uid = config.ids.uids.xtreemfs;
+ description = "XtreemFS user";
+ createHome = true;
+ home = home;
+ };
+
+ users.groups.xtreemfs =
+ { gid = config.ids.gids.xtreemfs;
+ };
+
+ systemd.services.xtreemfs-dir = mkIf cfg.dir.enable {
+ description = "XtreemFS-DIR Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "xtreemfs";
+ ExecStart = "${startupScript "org.xtreemfs.dir.DIR" dirConfig}";
+ };
+ };
+
+ systemd.services.xtreemfs-mrc = mkIf cfg.mrc.enable ({
+ description = "XtreemFS-MRC Server";
+ serviceConfig = {
+ User = "xtreemfs";
+ ExecStart = "${startupScript "org.xtreemfs.mrc.MRC" mrcConfig}";
+ };
+ } // systemdOptionalDependencies);
+
+ systemd.services.xtreemfs-osd = mkIf cfg.osd.enable ({
+ description = "XtreemFS-OSD Server";
+ serviceConfig = {
+ User = "xtreemfs";
+ ExecStart = "${startupScript "org.xtreemfs.osd.OSD" osdConfig}";
+ };
+ } // systemdOptionalDependencies);
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix b/nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix
new file mode 100644
index 00000000000..0aa01ef9e6d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix
@@ -0,0 +1,113 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.yandex-disk;
+
+ dir = "/var/lib/yandex-disk";
+
+ u = if cfg.user != null then cfg.user else "yandexdisk";
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.yandex-disk = {
+
+ enable = mkOption {
+ default = false;
+ description = "
+ Whether to enable Yandex-disk client. See https://disk.yandex.ru/
+ ";
+ };
+
+ username = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Your yandex.com login name.
+ '';
+ };
+
+ password = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Your yandex.com password. Warning: it will be world-readable in /nix/store.
+ '';
+ };
+
+ user = mkOption {
+ default = null;
+ description = ''
+ The user the yandex-disk daemon should run as.
+ '';
+ };
+
+ directory = mkOption {
+ default = "/home/Yandex.Disk";
+ description = "The directory to use for Yandex.Disk storage";
+ };
+
+ excludes = mkOption {
+ default = "";
+ type = types.commas;
+ example = "data,backup";
+ description = ''
+ Comma-separated list of directories which are excluded from synchronization.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = mkIf (cfg.user == null) [ {
+ name = u;
+ uid = config.ids.uids.yandexdisk;
+ group = "nogroup";
+ home = dir;
+ } ];
+
+ systemd.services.yandex-disk = {
+ description = "Yandex-disk server";
+
+ after = [ "network.target" ];
+
+ wantedBy = [ "multi-user.target" ];
+
+ # FIXME: have to specify ${directory} here as well
+ unitConfig.RequiresMountsFor = dir;
+
+ script = ''
+ mkdir -p -m 700 ${dir}
+ chown ${u} ${dir}
+
+ if ! test -d "${cfg.directory}" ; then
+ (mkdir -p -m 755 ${cfg.directory} && chown ${u} ${cfg.directory}) ||
+ exit 1
+ fi
+
+ ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${u} \
+ -c '${pkgs.yandex-disk}/bin/yandex-disk token -p ${cfg.password} ${cfg.username} ${dir}/token'
+
+ ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${u} \
+ -c '${pkgs.yandex-disk}/bin/yandex-disk start --no-daemon -a ${dir}/token -d ${cfg.directory} --exclude-dirs=${cfg.excludes}'
+ '';
+
+ };
+ };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/amuled.nix b/nixpkgs/nixos/modules/services/networking/amuled.nix
new file mode 100644
index 00000000000..57f02542eaf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/amuled.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.amule;
+ user = if cfg.user != null then cfg.user else "amule";
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.amule = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to run the AMule daemon. You need to manually run "amuled --ec-config" to configure the service for the first time.
+ '';
+ };
+
+ dataDir = mkOption {
+ default = ''/home/${user}/'';
+ description = ''
+ The directory holding configuration, incoming and temporary files.
+ '';
+ };
+
+ user = mkOption {
+ default = null;
+ description = ''
+ The user the AMule daemon should run as.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = mkIf (cfg.user == null) [
+ { name = "amule";
+ description = "AMule daemon";
+ group = "amule";
+ uid = config.ids.uids.amule;
+ } ];
+
+ users.groups = mkIf (cfg.user == null) [
+ { name = "amule";
+ gid = config.ids.gids.amule;
+ } ];
+
+ systemd.services.amuled = {
+ description = "AMule daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ preStart = ''
+ mkdir -p ${cfg.dataDir}
+ chown ${user} ${cfg.dataDir}
+ '';
+
+ script = ''
+ ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${user} \
+ -c 'HOME="${cfg.dataDir}" ${pkgs.amuleDaemon}/bin/amuled'
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/aria2.nix b/nixpkgs/nixos/modules/services/networking/aria2.nix
new file mode 100644
index 00000000000..156fef14479
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/aria2.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.aria2;
+
+ homeDir = "/var/lib/aria2";
+
+ settingsDir = "${homeDir}";
+ sessionFile = "${homeDir}/aria2.session";
+ downloadDir = "${homeDir}/Downloads";
+
+ rangesToStringList = map (x: builtins.toString x.from +"-"+ builtins.toString x.to);
+
+ settingsFile = pkgs.writeText "aria2.conf"
+ ''
+ dir=${cfg.downloadDir}
+ listen-port=${concatStringsSep "," (rangesToStringList cfg.listenPortRange)}
+ rpc-listen-port=${toString cfg.rpcListenPort}
+ rpc-secret=${cfg.rpcSecret}
+ '';
+
+in
+{
+ options = {
+ services.aria2 = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether or not to enable the headless Aria2 daemon service.
+
+ Aria2 daemon can be controlled via the RPC interface using
+ one of many WebUI (http://localhost:6800/ by default).
+
+ Targets are downloaded to ${downloadDir} by default and are
+ accessible to users in the "aria2" group.
+ '';
+ };
+ openPorts = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open listen and RPC ports found in listenPortRange and rpcListenPort
+ options in the firewall.
+ '';
+ };
+ downloadDir = mkOption {
+ type = types.path;
+ default = downloadDir;
+ description = ''
+ Directory to store downloaded files.
+ '';
+ };
+ listenPortRange = mkOption {
+ type = types.listOf types.attrs;
+ default = [ { from = 6881; to = 6999; } ];
+ description = ''
+ Set UDP listening port range used by DHT(IPv4, IPv6) and UDP tracker.
+ '';
+ };
+ rpcListenPort = mkOption {
+ type = types.int;
+ default = 6800;
+ description = "Specify a port number for JSON-RPC/XML-RPC server to listen to. Possible Values: 1024-65535";
+ };
+ rpcSecret = mkOption {
+ type = types.str;
+ default = "aria2rpc";
+ description = ''
+ Set RPC secret authorization token.
+ Read https://aria2.github.io/manual/en/html/aria2c.html#rpc-auth to know how this option value is used.
+ '';
+ };
+ extraArguments = mkOption {
+ type = types.separatedString " ";
+ example = "--rpc-listen-all --remote-time=true";
+ default = "";
+ description = ''
+ Additional arguments to be passed to Aria2.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ # Need to open ports for proper functioning
+ networking.firewall = mkIf cfg.openPorts {
+ allowedUDPPortRanges = config.services.aria2.listenPortRange;
+ allowedTCPPorts = [ config.services.aria2.rpcListenPort ];
+ };
+
+ users.users.aria2 = {
+ group = "aria2";
+ uid = config.ids.uids.aria2;
+ description = "aria2 user";
+ home = homeDir;
+ createHome = false;
+ };
+
+ users.groups.aria2.gid = config.ids.gids.aria2;
+
+ systemd.tmpfiles.rules = [
+ "d '${homeDir}' 0770 aria2 aria2 - -"
+ "d '${config.services.aria2.downloadDir}' 0770 aria2 aria2 - -"
+ ];
+
+ systemd.services.aria2 = {
+ description = "aria2 Service";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ if [[ ! -e "${sessionFile}" ]]
+ then
+ touch "${sessionFile}"
+ fi
+ cp -f "${settingsFile}" "${settingsDir}/aria2.conf"
+ '';
+
+ serviceConfig = {
+ Restart = "on-abort";
+ ExecStart = "${pkgs.aria2}/bin/aria2c --enable-rpc --conf-path=${settingsDir}/aria2.conf ${config.services.aria2.extraArguments} --save-session=${sessionFile}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ User = "aria2";
+ Group = "aria2";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/asterisk.nix b/nixpkgs/nixos/modules/services/networking/asterisk.nix
new file mode 100644
index 00000000000..03a2544b9a7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/asterisk.nix
@@ -0,0 +1,264 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.asterisk;
+
+ asteriskUser = "asterisk";
+ asteriskGroup = "asterisk";
+
+ varlibdir = "/var/lib/asterisk";
+ spooldir = "/var/spool/asterisk";
+ logdir = "/var/log/asterisk";
+
+ # Add filecontents from files of useTheseDefaultConfFiles to confFiles, do not override
+ defaultConfFiles = subtractLists (attrNames cfg.confFiles) cfg.useTheseDefaultConfFiles;
+ allConfFiles =
+ cfg.confFiles //
+ builtins.listToAttrs (map (x: { name = x;
+ value = builtins.readFile (cfg.package + "/etc/asterisk/" + x); })
+ defaultConfFiles);
+
+ asteriskEtc = pkgs.stdenv.mkDerivation
+ ((mapAttrs' (name: value: nameValuePair
+ # Fudge the names to make bash happy
+ ((replaceChars ["."] ["_"] name) + "_")
+ (value)
+ ) allConfFiles) //
+ {
+ confFilesString = concatStringsSep " " (
+ attrNames allConfFiles
+ );
+
+ name = "asterisk-etc";
+
+ # Default asterisk.conf file
+ # (Notice that astetcdir will be set to the path of this derivation)
+ asteriskConf = ''
+ [directories]
+ astetcdir => /etc/asterisk
+ astmoddir => ${cfg.package}/lib/asterisk/modules
+ astvarlibdir => /var/lib/asterisk
+ astdbdir => /var/lib/asterisk
+ astkeydir => /var/lib/asterisk
+ astdatadir => /var/lib/asterisk
+ astagidir => /var/lib/asterisk/agi-bin
+ astspooldir => /var/spool/asterisk
+ astrundir => /run/asterisk
+ astlogdir => /var/log/asterisk
+ astsbindir => ${cfg.package}/sbin
+ '';
+ extraConf = cfg.extraConfig;
+
+ # Loading all modules by default is considered sensible by the authors of
+ # "Asterisk: The Definitive Guide". Secure sites will likely want to
+ # specify their own "modules.conf" in the confFiles option.
+ modulesConf = ''
+ [modules]
+ autoload=yes
+ '';
+
+ # Use syslog for logging so logs can be viewed with journalctl
+ loggerConf = ''
+ [general]
+
+ [logfiles]
+ syslog.local0 => notice,warning,error
+ '';
+
+ buildCommand = ''
+ mkdir -p "$out"
+
+ # Create asterisk.conf, pointing astetcdir to the path of this derivation
+ echo "$asteriskConf" | sed "s|@out@|$out|g" > "$out"/asterisk.conf
+ echo "$extraConf" >> "$out"/asterisk.conf
+
+ echo "$modulesConf" > "$out"/modules.conf
+
+ echo "$loggerConf" > "$out"/logger.conf
+
+ # Config files specified in confFiles option override all other files
+ for i in $confFilesString; do
+ conf=$(echo "$i"_ | sed 's/\./_/g')
+ echo "''${!conf}" > "$out"/"$i"
+ done
+ '';
+ });
+in
+
+{
+ options = {
+ services.asterisk = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the Asterisk PBX server.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ example = ''
+ [options]
+ verbose=3
+ debug=3
+ '';
+ description = ''
+ Extra configuration options appended to the default
+ <literal>asterisk.conf</literal> file.
+ '';
+ };
+
+ confFiles = mkOption {
+ default = {};
+ type = types.attrsOf types.str;
+ example = literalExample
+ ''
+ {
+ "extensions.conf" = '''
+ [tests]
+ ; Dial 100 for "hello, world"
+ exten => 100,1,Answer()
+ same => n,Wait(1)
+ same => n,Playback(hello-world)
+ same => n,Hangup()
+
+ [softphones]
+ include => tests
+
+ [unauthorized]
+ ''';
+ "sip.conf" = '''
+ [general]
+ allowguest=no ; Require authentication
+ context=unauthorized ; Send unauthorized users to /dev/null
+ srvlookup=no ; Don't do DNS lookup
+ udpbindaddr=0.0.0.0 ; Listen on all interfaces
+ nat=force_rport,comedia ; Assume device is behind NAT
+
+ [softphone](!)
+ type=friend ; Match on username first, IP second
+ context=softphones ; Send to softphones context in
+ ; extensions.conf file
+ host=dynamic ; Device will register with asterisk
+ disallow=all ; Manually specify codecs to allow
+ allow=g722
+ allow=ulaw
+ allow=alaw
+
+ [myphone](softphone)
+ secret=GhoshevFew ; Change this password!
+ ''';
+ "logger.conf" = '''
+ [general]
+
+ [logfiles]
+ ; Add debug output to log
+ syslog.local0 => notice,warning,error,debug
+ ''';
+ }
+ '';
+ description = ''
+ Sets the content of config files (typically ending with
+ <literal>.conf</literal>) in the Asterisk configuration directory.
+
+ Note that if you want to change <literal>asterisk.conf</literal>, it
+ is preferable to use the <option>services.asterisk.extraConfig</option>
+ option over this option. If <literal>"asterisk.conf"</literal> is
+ specified with the <option>confFiles</option> option (not recommended),
+ you must be prepared to set your own <literal>astetcdir</literal>
+ path.
+
+ See
+ <link xlink:href="http://www.asterisk.org/community/documentation"/>
+ for more examples of what is possible here.
+ '';
+ };
+
+ useTheseDefaultConfFiles = mkOption {
+ default = [ "ari.conf" "acl.conf" "agents.conf" "amd.conf" "calendar.conf" "cdr.conf" "cdr_syslog.conf" "cdr_custom.conf" "cel.conf" "cel_custom.conf" "cli_aliases.conf" "confbridge.conf" "dundi.conf" "features.conf" "hep.conf" "iax.conf" "pjsip.conf" "pjsip_wizard.conf" "phone.conf" "phoneprov.conf" "queues.conf" "res_config_sqlite3.conf" "res_parking.conf" "statsd.conf" "udptl.conf" "unistim.conf" ];
+ type = types.listOf types.str;
+ example = [ "sip.conf" "dundi.conf" ];
+ description = ''Sets these config files to the default content. The default value for
+ this option contains all necesscary files to avoid errors at startup.
+ This does not override settings via <option>services.asterisk.confFiles</option>.
+ '';
+ };
+
+ extraArguments = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ example =
+ [ "-vvvddd" "-e" "1024" ];
+ description = ''
+ Additional command line arguments to pass to Asterisk.
+ '';
+ };
+ package = mkOption {
+ type = types.package;
+ default = pkgs.asterisk;
+ defaultText = "pkgs.asterisk";
+ description = "The Asterisk package to use.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ cfg.package ];
+
+ environment.etc.asterisk.source = asteriskEtc;
+
+ users.users.asterisk =
+ { name = asteriskUser;
+ group = asteriskGroup;
+ uid = config.ids.uids.asterisk;
+ description = "Asterisk daemon user";
+ home = varlibdir;
+ };
+
+ users.groups.asterisk =
+ { name = asteriskGroup;
+ gid = config.ids.gids.asterisk;
+ };
+
+ systemd.services.asterisk = {
+ description = ''
+ Asterisk PBX server
+ '';
+
+ wantedBy = [ "multi-user.target" ];
+
+ # Do not restart, to avoid disruption of running calls. Restart unit by yourself!
+ restartIfChanged = false;
+
+ preStart = ''
+ # Copy skeleton directory tree to /var
+ for d in '${varlibdir}' '${spooldir}' '${logdir}'; do
+ # TODO: Make exceptions for /var directories that likely should be updated
+ if [ ! -e "$d" ]; then
+ mkdir -p "$d"
+ cp --recursive ${cfg.package}/"$d"/* "$d"/
+ chown --recursive ${asteriskUser}:${asteriskGroup} "$d"
+ find "$d" -type d | xargs chmod 0755
+ fi
+ done
+ '';
+
+ serviceConfig = {
+ ExecStart =
+ let
+ # FIXME: This doesn't account for arguments with spaces
+ argString = concatStringsSep " " cfg.extraArguments;
+ in
+ "${cfg.package}/bin/asterisk -U ${asteriskUser} -C /etc/asterisk/asterisk.conf ${argString} -F";
+ ExecReload = ''${cfg.package}/bin/asterisk -x "core reload"
+ '';
+ Type = "forking";
+ PIDFile = "/run/asterisk/asterisk.pid";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/atftpd.nix b/nixpkgs/nixos/modules/services/networking/atftpd.nix
new file mode 100644
index 00000000000..e7fd48c99a8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/atftpd.nix
@@ -0,0 +1,65 @@
+# NixOS module for atftpd TFTP server
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.atftpd;
+
+in
+
+{
+
+ options = {
+
+ services.atftpd = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable the atftpd TFTP server. By default, the server
+ binds to address 0.0.0.0.
+ '';
+ };
+
+ extraOptions = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ example = literalExample ''
+ [ "--bind-address 192.168.9.1"
+ "--verbose=7"
+ ]
+ '';
+ description = ''
+ Extra command line arguments to pass to atftp.
+ '';
+ };
+
+ root = mkOption {
+ default = "/srv/tftp";
+ type = types.path;
+ description = ''
+ Document root directory for the atftpd.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.atftpd = {
+ description = "TFTP Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ # runs as nobody
+ serviceConfig.ExecStart = "${pkgs.atftp}/sbin/atftpd --daemon --no-fork ${lib.concatStringsSep " " cfg.extraOptions} ${cfg.root}";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/autossh.nix b/nixpkgs/nixos/modules/services/networking/autossh.nix
new file mode 100644
index 00000000000..a8d9a027e9f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/autossh.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.autossh;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.autossh = {
+
+ sessions = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ name = mkOption {
+ type = types.str;
+ example = "socks-peer";
+ description = "Name of the local AutoSSH session";
+ };
+ user = mkOption {
+ type = types.str;
+ example = "bill";
+ description = "Name of the user the AutoSSH session should run as";
+ };
+ monitoringPort = mkOption {
+ type = types.int;
+ default = 0;
+ example = 20000;
+ description = ''
+ Port to be used by AutoSSH for peer monitoring. Note, that
+ AutoSSH also uses mport+1. Value of 0 disables the keep-alive
+ style monitoring
+ '';
+ };
+ extraArguments = mkOption {
+ type = types.separatedString " ";
+ example = "-N -D4343 bill@socks.example.net";
+ description = ''
+ Arguments to be passed to AutoSSH and retransmitted to SSH
+ process. Some meaningful options include -N (don't run remote
+ command), -D (open SOCKS proxy on local port), -R (forward
+ remote port), -L (forward local port), -v (Enable debug). Check
+ ssh manual for the complete list.
+ '';
+ };
+ };
+ });
+
+ default = [];
+ description = ''
+ List of AutoSSH sessions to start as systemd services. Each service is
+ named 'autossh-{session.name}'.
+ '';
+
+ example = [
+ {
+ name="socks-peer";
+ user="bill";
+ monitoringPort = 20000;
+ extraArguments="-N -D4343 billremote@socks.host.net";
+ }
+ ];
+
+ };
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf (cfg.sessions != []) {
+
+ systemd.services =
+
+ lib.fold ( s : acc : acc //
+ {
+ "autossh-${s.name}" =
+ let
+ mport = if s ? monitoringPort then s.monitoringPort else 0;
+ in
+ {
+ description = "AutoSSH session (" + s.name + ")";
+
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ # To be able to start the service with no network connection
+ environment.AUTOSSH_GATETIME="0";
+
+ # How often AutoSSH checks the network, in seconds
+ environment.AUTOSSH_POLL="30";
+
+ serviceConfig = {
+ User = "${s.user}";
+ # AutoSSH may exit with 0 code if the SSH session was
+ # gracefully terminated by either local or remote side.
+ Restart = "on-success";
+ ExecStart = "${pkgs.autossh}/bin/autossh -M ${toString mport} ${s.extraArguments}";
+ };
+ };
+ }) {} cfg.sessions;
+
+ environment.systemPackages = [ pkgs.autossh ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/avahi-daemon.nix b/nixpkgs/nixos/modules/services/networking/avahi-daemon.nix
new file mode 100644
index 00000000000..ddcfe3d77e2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/avahi-daemon.nix
@@ -0,0 +1,281 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.avahi;
+
+ yesNo = yes : if yes then "yes" else "no";
+
+ avahiDaemonConf = with cfg; pkgs.writeText "avahi-daemon.conf" ''
+ [server]
+ ${# Users can set `networking.hostName' to the empty string, when getting
+ # a host name from DHCP. In that case, let Avahi take whatever the
+ # current host name is; setting `host-name' to the empty string in
+ # `avahi-daemon.conf' would be invalid.
+ optionalString (hostName != "") "host-name=${hostName}"}
+ browse-domains=${concatStringsSep ", " browseDomains}
+ use-ipv4=${yesNo ipv4}
+ use-ipv6=${yesNo ipv6}
+ ${optionalString (interfaces!=null) "allow-interfaces=${concatStringsSep "," interfaces}"}
+ ${optionalString (domainName!=null) "domain-name=${domainName}"}
+ allow-point-to-point=${yesNo allowPointToPoint}
+ ${optionalString (cacheEntriesMax!=null) "cache-entries-max=${toString cacheEntriesMax}"}
+
+ [wide-area]
+ enable-wide-area=${yesNo wideArea}
+
+ [publish]
+ disable-publishing=${yesNo (!publish.enable)}
+ disable-user-service-publishing=${yesNo (!publish.userServices)}
+ publish-addresses=${yesNo (publish.userServices || publish.addresses)}
+ publish-hinfo=${yesNo publish.hinfo}
+ publish-workstation=${yesNo publish.workstation}
+ publish-domain=${yesNo publish.domain}
+
+ [reflector]
+ enable-reflector=${yesNo reflector}
+ ${extraConfig}
+ '';
+in
+{
+ options.services.avahi = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run the Avahi daemon, which allows Avahi clients
+ to use Avahi's service discovery facilities and also allows
+ the local machine to advertise its presence and services
+ (through the mDNS responder implemented by `avahi-daemon').
+ '';
+ };
+
+ hostName = mkOption {
+ type = types.str;
+ default = config.networking.hostName;
+ defaultText = literalExample "config.networking.hostName";
+ description = ''
+ Host name advertised on the LAN. If not set, avahi will use the value
+ of <option>config.networking.hostName</option>.
+ '';
+ };
+
+ domainName = mkOption {
+ type = types.str;
+ default = "local";
+ description = ''
+ Domain name for all advertisements.
+ '';
+ };
+
+ browseDomains = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "0pointer.de" "zeroconf.org" ];
+ description = ''
+ List of non-local DNS domains to be browsed.
+ '';
+ };
+
+ ipv4 = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to use IPv4.";
+ };
+
+ ipv6 = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to use IPv6.";
+ };
+
+ interfaces = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = ''
+ List of network interfaces that should be used by the <command>avahi-daemon</command>.
+ Other interfaces will be ignored. If <literal>null</literal>, all local interfaces
+ except loopback and point-to-point will be used.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to open the firewall for UDP port 5353.
+ '';
+ };
+
+ allowPointToPoint = mkOption {
+ type = types.bool;
+ default = false;
+ description= ''
+ Whether to use POINTTOPOINT interfaces. Might make mDNS unreliable due to usually large
+ latencies with such links and opens a potential security hole by allowing mDNS access from Internet
+ connections.
+ '';
+ };
+
+ wideArea = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to enable wide-area service discovery.";
+ };
+
+ reflector = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Reflect incoming mDNS requests to all allowed network interfaces.";
+ };
+
+ extraServiceFiles = mkOption {
+ type = with types; attrsOf (either str path);
+ default = {};
+ example = literalExample ''
+ {
+ ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
+ smb = '''
+ <?xml version="1.0" standalone='no'?><!--*-nxml-*-->
+ <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
+ <service-group>
+ <name replace-wildcards="yes">%h</name>
+ <service>
+ <type>_smb._tcp</type>
+ <port>445</port>
+ </service>
+ </service-group>
+ ''';
+ }
+ '';
+ description = ''
+ Specify custom service definitions which are placed in the avahi service directory.
+ See the <citerefentry><refentrytitle>avahi.service</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> manpage for detailed information.
+ '';
+ };
+
+ publish = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to allow publishing in general.";
+ };
+
+ userServices = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to publish user services. Will set <literal>addresses=true</literal>.";
+ };
+
+ addresses = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to register mDNS address records for all local IP addresses.";
+ };
+
+ hinfo = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to register a mDNS HINFO record which contains information about the
+ local operating system and CPU.
+ '';
+ };
+
+ workstation = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to register a service of type "_workstation._tcp" on the local LAN.
+ '';
+ };
+
+ domain = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to announce the locally used domain name for browsing by other hosts.";
+ };
+ };
+
+ nssmdns = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the mDNS NSS (Name Service Switch) plug-in.
+ Enabling it allows applications to resolve names in the `.local'
+ domain by transparently querying the Avahi daemon.
+ '';
+ };
+
+ cacheEntriesMax = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Number of resource records to be cached per interface. Use 0 to
+ disable caching. Avahi daemon defaults to 4096 if not set.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra config to append to avahi-daemon.conf.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.avahi = {
+ description = "avahi-daemon privilege separation user";
+ home = "/var/empty";
+ group = "avahi";
+ isSystemUser = true;
+ };
+
+ users.groups.avahi = {};
+
+ system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
+
+ environment.systemPackages = [ pkgs.avahi ];
+
+ environment.etc = (mapAttrs' (n: v: nameValuePair
+ "avahi/services/${n}.service"
+ { ${if types.path.check v then "source" else "text"} = v; }
+ ) cfg.extraServiceFiles);
+
+ systemd.sockets.avahi-daemon = {
+ description = "Avahi mDNS/DNS-SD Stack Activation Socket";
+ listenStreams = [ "/run/avahi-daemon/socket" ];
+ wantedBy = [ "sockets.target" ];
+ };
+
+ systemd.tmpfiles.rules = [ "d /run/avahi-daemon - avahi avahi -" ];
+
+ systemd.services.avahi-daemon = {
+ description = "Avahi mDNS/DNS-SD Stack";
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "avahi-daemon.socket" ];
+
+ # Make NSS modules visible so that `avahi_nss_support ()' can
+ # return a sensible value.
+ environment.LD_LIBRARY_PATH = config.system.nssModules.path;
+
+ path = [ pkgs.coreutils pkgs.avahi ];
+
+ serviceConfig = {
+ NotifyAccess = "main";
+ BusName = "org.freedesktop.Avahi";
+ Type = "dbus";
+ ExecStart = "${pkgs.avahi}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}";
+ };
+ };
+
+ services.dbus.enable = true;
+ services.dbus.packages = [ pkgs.avahi ];
+
+ networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ 5353 ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/babeld.nix b/nixpkgs/nixos/modules/services/networking/babeld.nix
new file mode 100644
index 00000000000..de863461eab
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/babeld.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.babeld;
+
+ conditionalBoolToString = value: if (isBool value) then (boolToString value) else (toString value);
+
+ paramsString = params:
+ concatMapStringsSep " " (name: "${name} ${conditionalBoolToString (getAttr name params)}")
+ (attrNames params);
+
+ interfaceConfig = name:
+ let
+ interface = getAttr name cfg.interfaces;
+ in
+ "interface ${name} ${paramsString interface}\n";
+
+ configFile = with cfg; pkgs.writeText "babeld.conf" (
+ (optionalString (cfg.interfaceDefaults != null) ''
+ default ${paramsString cfg.interfaceDefaults}
+ '')
+ + (concatMapStrings interfaceConfig (attrNames cfg.interfaces))
+ + extraConfig);
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.babeld = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to run the babeld network routing daemon.
+ '';
+ };
+
+ interfaceDefaults = mkOption {
+ default = null;
+ description = ''
+ A set describing default parameters for babeld interfaces.
+ See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for options.
+ '';
+ type = types.nullOr (types.attrsOf types.unspecified);
+ example =
+ {
+ type = "tunnel";
+ split-horizon = true;
+ };
+ };
+
+ interfaces = mkOption {
+ default = {};
+ description = ''
+ A set describing babeld interfaces.
+ See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for options.
+ '';
+ type = types.attrsOf (types.attrsOf types.unspecified);
+ example =
+ { enp0s2 =
+ { type = "wired";
+ hello-interval = 5;
+ split-horizon = "auto";
+ };
+ };
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ description = ''
+ Options that will be copied to babeld.conf.
+ See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for details.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.babeld.enable {
+
+ systemd.services.babeld = {
+ description = "Babel routing daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${pkgs.babeld}/bin/babeld -c ${configFile}";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/bind.nix b/nixpkgs/nixos/modules/services/networking/bind.nix
new file mode 100644
index 00000000000..06af4dbcca4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/bind.nix
@@ -0,0 +1,207 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.bind;
+
+ bindUser = "named";
+
+ confFile = pkgs.writeText "named.conf"
+ ''
+ include "/etc/bind/rndc.key";
+ controls {
+ inet 127.0.0.1 allow {localhost;} keys {"rndc-key";};
+ };
+
+ acl cachenetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.cacheNetworks} };
+ acl badnetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.blockedNetworks} };
+
+ options {
+ listen-on { ${concatMapStrings (entry: " ${entry}; ") cfg.listenOn} };
+ listen-on-v6 { ${concatMapStrings (entry: " ${entry}; ") cfg.listenOnIpv6} };
+ allow-query { cachenetworks; };
+ blackhole { badnetworks; };
+ forward first;
+ forwarders { ${concatMapStrings (entry: " ${entry}; ") cfg.forwarders} };
+ directory "/run/named";
+ pid-file "/run/named/named.pid";
+ ${cfg.extraOptions}
+ };
+
+ ${cfg.extraConfig}
+
+ ${ concatMapStrings
+ ({ name, file, master ? true, slaves ? [], masters ? [], extraConfig ? "" }:
+ ''
+ zone "${name}" {
+ type ${if master then "master" else "slave"};
+ file "${file}";
+ ${ if master then
+ ''
+ allow-transfer {
+ ${concatMapStrings (ip: "${ip};\n") slaves}
+ };
+ ''
+ else
+ ''
+ masters {
+ ${concatMapStrings (ip: "${ip};\n") masters}
+ };
+ ''
+ }
+ allow-query { any; };
+ ${extraConfig}
+ };
+ '')
+ cfg.zones }
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.bind = {
+
+ enable = mkOption {
+ default = false;
+ description = "
+ Whether to enable BIND domain name server.
+ ";
+ };
+
+ cacheNetworks = mkOption {
+ default = ["127.0.0.0/24"];
+ description = "
+ What networks are allowed to use us as a resolver.
+ ";
+ };
+
+ blockedNetworks = mkOption {
+ default = [];
+ description = "
+ What networks are just blocked.
+ ";
+ };
+
+ ipv4Only = mkOption {
+ default = false;
+ description = "
+ Only use ipv4, even if the host supports ipv6.
+ ";
+ };
+
+ forwarders = mkOption {
+ default = config.networking.nameservers;
+ description = "
+ List of servers we should forward requests to.
+ ";
+ };
+
+ listenOn = mkOption {
+ default = ["any"];
+ type = types.listOf types.str;
+ description = "
+ Interfaces to listen on.
+ ";
+ };
+
+ listenOnIpv6 = mkOption {
+ default = ["any"];
+ type = types.listOf types.str;
+ description = "
+ Ipv6 interfaces to listen on.
+ ";
+ };
+
+ zones = mkOption {
+ default = [];
+ description = "
+ List of zones we claim authority over.
+ master=false means slave server; slaves means addresses
+ who may request zone transfer.
+ ";
+ example = [{
+ name = "example.com";
+ master = false;
+ file = "/var/dns/example.com";
+ masters = ["192.168.0.1"];
+ slaves = [];
+ extraConfig = "";
+ }];
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "
+ Extra lines to be added verbatim to the generated named configuration file.
+ ";
+ };
+
+ extraOptions = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines to be added verbatim to the options section of the
+ generated named configuration file.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ default = confFile;
+ defaultText = "confFile";
+ description = "
+ Overridable config file to use for named. By default, that
+ generated by nixos.
+ ";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ networking.resolvconf.useLocalResolver = mkDefault true;
+
+ users.users = singleton
+ { name = bindUser;
+ uid = config.ids.uids.bind;
+ description = "BIND daemon user";
+ };
+
+ systemd.services.bind = {
+ description = "BIND Domain Name Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ mkdir -m 0755 -p /etc/bind
+ if ! [ -f "/etc/bind/rndc.key" ]; then
+ ${pkgs.bind.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null
+ fi
+
+ ${pkgs.coreutils}/bin/mkdir -p /run/named
+ chown ${bindUser} /run/named
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkgs.bind.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} -f";
+ ExecReload = "${pkgs.bind.out}/sbin/rndc -k '/etc/bind/rndc.key' reload";
+ ExecStop = "${pkgs.bind.out}/sbin/rndc -k '/etc/bind/rndc.key' stop";
+ };
+
+ unitConfig.Documentation = "man:named(8)";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/bird.nix b/nixpkgs/nixos/modules/services/networking/bird.nix
new file mode 100644
index 00000000000..4ae35875c0f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/bird.nix
@@ -0,0 +1,78 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkEnableOption mkIf mkOption types;
+
+ generic = variant:
+ let
+ cfg = config.services.${variant};
+ pkg = pkgs.${variant};
+ birdBin = if variant == "bird6" then "bird6" else "bird";
+ birdc = if variant == "bird6" then "birdc6" else "birdc";
+ descr =
+ { bird = "1.9.x with IPv4 suport";
+ bird6 = "1.9.x with IPv6 suport";
+ bird2 = "2.x";
+ }.${variant};
+ in {
+ ###### interface
+ options = {
+ services.${variant} = {
+ enable = mkEnableOption "BIRD Internet Routing Daemon (${descr})";
+ config = mkOption {
+ type = types.lines;
+ description = ''
+ BIRD Internet Routing Daemon configuration file.
+ <link xlink:href='http://bird.network.cz/'/>
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkg ];
+
+ environment.etc."bird/${variant}.conf".source = pkgs.writeTextFile {
+ name = "${variant}.conf";
+ text = cfg.config;
+ checkPhase = ''
+ ${pkg}/bin/${birdBin} -d -p -c $out
+ '';
+ };
+
+ systemd.services.${variant} = {
+ description = "BIRD Internet Routing Daemon (${descr})";
+ wantedBy = [ "multi-user.target" ];
+ reloadIfChanged = true;
+ restartTriggers = [ config.environment.etc."bird/${variant}.conf".source ];
+ serviceConfig = {
+ Type = "forking";
+ Restart = "on-failure";
+ ExecStart = "${pkg}/bin/${birdBin} -c /etc/bird/${variant}.conf -u ${variant} -g ${variant}";
+ ExecReload = "${pkg}/bin/${birdc} configure";
+ ExecStop = "${pkg}/bin/${birdc} down";
+ CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_FOWNER" "CAP_DAC_OVERRIDE" "CAP_SETUID" "CAP_SETGID"
+ # see bird/sysdep/linux/syspriv.h
+ "CAP_NET_BIND_SERVICE" "CAP_NET_BROADCAST" "CAP_NET_ADMIN" "CAP_NET_RAW" ];
+ ProtectSystem = "full";
+ ProtectHome = "yes";
+ SystemCallFilter="~@cpu-emulation @debug @keyring @module @mount @obsolete @raw-io";
+ MemoryDenyWriteExecute = "yes";
+ };
+ };
+ users = {
+ users.${variant} = {
+ description = "BIRD Internet Routing Daemon user";
+ group = variant;
+ };
+ groups.${variant} = {};
+ };
+ };
+ };
+
+in
+
+{
+ imports = map generic [ "bird" "bird6" "bird2" ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/bitcoind.nix b/nixpkgs/nixos/modules/services/networking/bitcoind.nix
new file mode 100644
index 00000000000..1439d739da9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/bitcoind.nix
@@ -0,0 +1,195 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.bitcoind;
+ pidFile = "${cfg.dataDir}/bitcoind.pid";
+ configFile = pkgs.writeText "bitcoin.conf" ''
+ ${optionalString cfg.testnet "testnet=1"}
+ ${optionalString (cfg.dbCache != null) "dbcache=${toString cfg.dbCache}"}
+ ${optionalString (cfg.prune != null) "prune=${toString cfg.prune}"}
+
+ # Connection options
+ ${optionalString (cfg.port != null) "port=${toString cfg.port}"}
+
+ # RPC server options
+ ${optionalString (cfg.rpc.port != null) "rpcport=${toString cfg.rpc.port}"}
+ ${concatMapStringsSep "\n"
+ (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}")
+ (attrValues cfg.rpc.users)
+ }
+
+ # Extra config options (from bitcoind nixos service)
+ ${cfg.extraConfig}
+ '';
+ cmdlineOptions = escapeShellArgs [
+ "-conf=${cfg.configFile}"
+ "-datadir=${cfg.dataDir}"
+ "-pid=${pidFile}"
+ ];
+
+ rpcUserOpts = { name, ... }: {
+ options = {
+ name = mkOption {
+ type = types.str;
+ example = "alice";
+ description = ''
+ Username for JSON-RPC connections.
+ '';
+ };
+ passwordHMAC = mkOption {
+ type = with types; uniq (strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
+ example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
+ description = ''
+ Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the
+ format &lt;SALT-HEX&gt;$&lt;HMAC-HEX&gt;.
+ '';
+ };
+ };
+ config = {
+ name = mkDefault name;
+ };
+ };
+in {
+ options = {
+
+ services.bitcoind = {
+ enable = mkEnableOption "Bitcoin daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.bitcoind;
+ defaultText = "pkgs.bitcoind";
+ description = "The package providing bitcoin binaries.";
+ };
+ configFile = mkOption {
+ type = types.path;
+ default = configFile;
+ example = "/etc/bitcoind.conf";
+ description = "The configuration file path to supply bitcoind.";
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ par=16
+ rpcthreads=16
+ logips=1
+ '';
+ description = "Additional configurations to be appended to <filename>bitcoin.conf</filename>.";
+ };
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/bitcoind";
+ description = "The data directory for bitcoind.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "bitcoin";
+ description = "The user as which to run bitcoind.";
+ };
+ group = mkOption {
+ type = types.str;
+ default = cfg.user;
+ description = "The group as which to run bitcoind.";
+ };
+
+ rpc = {
+ port = mkOption {
+ type = types.nullOr types.port;
+ default = null;
+ description = "Override the default port on which to listen for JSON-RPC connections.";
+ };
+ users = mkOption {
+ default = {};
+ example = literalExample ''
+ {
+ alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
+ bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99";
+ }
+ '';
+ type = with types; loaOf (submodule rpcUserOpts);
+ description = ''
+ RPC user information for JSON-RPC connnections.
+ '';
+ };
+ };
+
+ testnet = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to use the test chain.";
+ };
+ port = mkOption {
+ type = types.nullOr types.port;
+ default = null;
+ description = "Override the default port on which to listen for connections.";
+ };
+ dbCache = mkOption {
+ type = types.nullOr (types.ints.between 4 16384);
+ default = null;
+ example = 4000;
+ description = "Override the default database cache size in megabytes.";
+ };
+ prune = mkOption {
+ type = types.nullOr (types.coercedTo
+ (types.enum [ "disable" "manual" ])
+ (x: if x == "disable" then 0 else 1)
+ types.ints.unsigned
+ );
+ default = null;
+ example = 10000;
+ description = ''
+ Reduce storage requirements by enabling pruning (deleting) of old
+ blocks. This allows the pruneblockchain RPC to be called to delete
+ specific blocks, and enables automatic pruning of old blocks if a
+ target size in MiB is provided. This mode is incompatible with -txindex
+ and -rescan. Warning: Reverting this setting requires re-downloading
+ the entire blockchain. ("disable" = disable pruning blocks, "manual"
+ = allow manual pruning via RPC, >=550 = automatically prune block files
+ to stay under the specified target size in MiB)
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ cfg.package ];
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
+ "L '${cfg.dataDir}/bitcoin.conf' - - - - '${cfg.configFile}'"
+ ];
+ systemd.services.bitcoind = {
+ description = "Bitcoin daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${cfg.package}/bin/bitcoind ${cmdlineOptions}";
+ Restart = "on-failure";
+
+ # Hardening measures
+ PrivateTmp = "true";
+ ProtectSystem = "full";
+ NoNewPrivileges = "true";
+ PrivateDevices = "true";
+ MemoryDenyWriteExecute = "true";
+
+ # Permission for preStart
+ PermissionsStartOnly = "true";
+ };
+ };
+ users.users.${cfg.user} = {
+ name = cfg.user;
+ group = cfg.group;
+ description = "Bitcoin daemon user";
+ home = cfg.dataDir;
+ };
+ users.groups.${cfg.group} = {
+ name = cfg.group;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/bitlbee.nix b/nixpkgs/nixos/modules/services/networking/bitlbee.nix
new file mode 100644
index 00000000000..274b3617160
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/bitlbee.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.bitlbee;
+ bitlbeeUid = config.ids.uids.bitlbee;
+
+ bitlbeePkg = pkgs.bitlbee.override {
+ enableLibPurple = cfg.libpurple_plugins != [];
+ enablePam = cfg.authBackend == "pam";
+ };
+
+ bitlbeeConfig = pkgs.writeText "bitlbee.conf"
+ ''
+ [settings]
+ RunMode = Daemon
+ User = bitlbee
+ ConfigDir = ${cfg.configDir}
+ DaemonInterface = ${cfg.interface}
+ DaemonPort = ${toString cfg.portNumber}
+ AuthMode = ${cfg.authMode}
+ AuthBackend = ${cfg.authBackend}
+ Plugindir = ${pkgs.bitlbee-plugins cfg.plugins}/lib/bitlbee
+ ${lib.optionalString (cfg.hostName != "") "HostName = ${cfg.hostName}"}
+ ${lib.optionalString (cfg.protocols != "") "Protocols = ${cfg.protocols}"}
+ ${cfg.extraSettings}
+
+ [defaults]
+ ${cfg.extraDefaults}
+ '';
+
+ purple_plugin_path =
+ lib.concatMapStringsSep ":"
+ (plugin: "${plugin}/lib/pidgin/:${plugin}/lib/purple-2/")
+ cfg.libpurple_plugins
+ ;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.bitlbee = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to run the BitlBee IRC to other chat network gateway.
+ Running it allows you to access the MSN, Jabber, Yahoo! and ICQ chat
+ networks via an IRC client.
+ '';
+ };
+
+ interface = mkOption {
+ default = "127.0.0.1";
+ description = ''
+ The interface the BitlBee deamon will be listening to. If `127.0.0.1',
+ only clients on the local host can connect to it; if `0.0.0.0', clients
+ can access it from any network interface.
+ '';
+ };
+
+ portNumber = mkOption {
+ default = 6667;
+ description = ''
+ Number of the port BitlBee will be listening to.
+ '';
+ };
+
+ authBackend = mkOption {
+ default = "storage";
+ type = types.enum [ "storage" "pam" ];
+ description = ''
+ How users are authenticated
+ storage -- save passwords internally
+ pam -- Linux PAM authentication
+ '';
+ };
+
+ authMode = mkOption {
+ default = "Open";
+ type = types.enum [ "Open" "Closed" "Registered" ];
+ description = ''
+ The following authentication modes are available:
+ Open -- Accept connections from anyone, use NickServ for user authentication.
+ Closed -- Require authorization (using the PASS command during login) before allowing the user to connect at all.
+ Registered -- Only allow registered users to use this server; this disables the register- and the account command until the user identifies himself.
+ '';
+ };
+
+ hostName = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Normally, BitlBee gets a hostname using getsockname(). If you have a nicer
+ alias for your BitlBee daemon, you can set it here and BitlBee will identify
+ itself with that name instead.
+ '';
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "[ pkgs.bitlbee-facebook ]";
+ description = ''
+ The list of bitlbee plugins to install.
+ '';
+ };
+
+ libpurple_plugins = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "[ pkgs.purple-matrix ]";
+ description = ''
+ The list of libpurple plugins to install.
+ '';
+ };
+
+ configDir = mkOption {
+ default = "/var/lib/bitlbee";
+ type = types.path;
+ description = ''
+ Specify an alternative directory to store all the per-user configuration
+ files.
+ '';
+ };
+
+ protocols = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ This option allows to remove the support of protocol, even if compiled
+ in. If nothing is given, there are no restrictions.
+ '';
+ };
+
+ extraSettings = mkOption {
+ default = "";
+ description = ''
+ Will be inserted in the Settings section of the config file.
+ '';
+ };
+
+ extraDefaults = mkOption {
+ default = "";
+ description = ''
+ Will be inserted in the Default section of the config file.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkMerge [
+ (mkIf config.services.bitlbee.enable {
+ users.users = singleton {
+ name = "bitlbee";
+ uid = bitlbeeUid;
+ description = "BitlBee user";
+ home = "/var/lib/bitlbee";
+ createHome = true;
+ };
+
+ users.groups = singleton {
+ name = "bitlbee";
+ gid = config.ids.gids.bitlbee;
+ };
+
+ systemd.services.bitlbee = {
+ environment.PURPLE_PLUGIN_PATH = purple_plugin_path;
+ description = "BitlBee IRC to other chat networks gateway";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.User = "bitlbee";
+ serviceConfig.ExecStart = "${bitlbeePkg}/sbin/bitlbee -F -n -c ${bitlbeeConfig}";
+ };
+
+ environment.systemPackages = [ bitlbeePkg ];
+
+ })
+ (mkIf (config.services.bitlbee.authBackend == "pam") {
+ security.pam.services.bitlbee = {};
+ })
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/charybdis.nix b/nixpkgs/nixos/modules/services/networking/charybdis.nix
new file mode 100644
index 00000000000..da26246e703
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/charybdis.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkEnableOption mkIf mkOption singleton types;
+ inherit (pkgs) coreutils charybdis;
+ cfg = config.services.charybdis;
+
+ configFile = pkgs.writeText "charybdis.conf" ''
+ ${cfg.config}
+ '';
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.charybdis = {
+
+ enable = mkEnableOption "Charybdis IRC daemon";
+
+ config = mkOption {
+ type = types.str;
+ description = ''
+ Charybdis IRC daemon configuration file.
+ '';
+ };
+
+ statedir = mkOption {
+ type = types.path;
+ default = "/var/lib/charybdis";
+ description = ''
+ Location of the state directory of charybdis.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "ircd";
+ description = ''
+ Charybdis IRC daemon user.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "ircd";
+ description = ''
+ Charybdis IRC daemon group.
+ '';
+ };
+
+ motd = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Charybdis MOTD text.
+
+ Charybdis will read its MOTD from /etc/charybdis/ircd.motd .
+ If set, the value of this option will be written to this path.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable (lib.mkMerge [
+ {
+ users.users = singleton {
+ name = cfg.user;
+ description = "Charybdis IRC daemon user";
+ uid = config.ids.uids.ircd;
+ group = cfg.group;
+ };
+
+ users.groups = singleton {
+ name = cfg.group;
+ gid = config.ids.gids.ircd;
+ };
+
+ systemd.tmpfiles.rules = [
+ "d ${cfg.statedir} - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.charybdis = {
+ description = "Charybdis IRC daemon";
+ wantedBy = [ "multi-user.target" ];
+ environment = {
+ BANDB_DBPATH = "${cfg.statedir}/ban.db";
+ };
+ serviceConfig = {
+ ExecStart = "${charybdis}/bin/charybdis -foreground -logfile /dev/stdout -configfile ${configFile}";
+ Group = cfg.group;
+ User = cfg.user;
+ };
+ };
+
+ }
+
+ (mkIf (cfg.motd != null) {
+ environment.etc."charybdis/ircd.motd".text = cfg.motd;
+ })
+ ]);
+}
diff --git a/nixpkgs/nixos/modules/services/networking/cjdns.nix b/nixpkgs/nixos/modules/services/networking/cjdns.nix
new file mode 100644
index 00000000000..3fb85b16cbe
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/cjdns.nix
@@ -0,0 +1,294 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ pkg = pkgs.cjdns;
+
+ cfg = config.services.cjdns;
+
+ connectToSubmodule =
+ { ... }:
+ { options =
+ { password = mkOption {
+ type = types.str;
+ description = "Authorized password to the opposite end of the tunnel.";
+ };
+ publicKey = mkOption {
+ type = types.str;
+ description = "Public key at the opposite end of the tunnel.";
+ };
+ hostname = mkOption {
+ default = "";
+ example = "foobar.hype";
+ type = types.str;
+ description = "Optional hostname to add to /etc/hosts; prevents reverse lookup failures.";
+ };
+ };
+ };
+
+ # Additional /etc/hosts entries for peers with an associated hostname
+ cjdnsExtraHosts = import (pkgs.runCommand "cjdns-hosts" {}
+ # Generate a builder that produces an output usable as a Nix string value
+ ''
+ exec >$out
+ echo \'\'
+ ${concatStringsSep "\n" (mapAttrsToList (k: v:
+ optionalString (v.hostname != "")
+ "echo $(${pkgs.cjdns}/bin/publictoip6 ${v.publicKey}) ${v.hostname}")
+ (cfg.ETHInterface.connectTo // cfg.UDPInterface.connectTo))}
+ echo \'\'
+ '');
+
+ parseModules = x:
+ x // { connectTo = mapAttrs (name: value: { inherit (value) password publicKey; }) x.connectTo; };
+
+ cjdrouteConf = builtins.toJSON ( recursiveUpdate {
+ admin = {
+ bind = cfg.admin.bind;
+ password = "@CJDNS_ADMIN_PASSWORD@";
+ };
+ authorizedPasswords = map (p: { password = p; }) cfg.authorizedPasswords;
+ interfaces = {
+ ETHInterface = if (cfg.ETHInterface.bind != "") then [ (parseModules cfg.ETHInterface) ] else [ ];
+ UDPInterface = if (cfg.UDPInterface.bind != "") then [ (parseModules cfg.UDPInterface) ] else [ ];
+ };
+
+ privateKey = "@CJDNS_PRIVATE_KEY@";
+
+ resetAfterInactivitySeconds = 100;
+
+ router = {
+ interface = { type = "TUNInterface"; };
+ ipTunnel = {
+ allowedConnections = [];
+ outgoingConnections = [];
+ };
+ };
+
+ security = [ { exemptAngel = 1; setuser = "nobody"; } ];
+
+ } cfg.extraConfig);
+
+in
+
+{
+ options = {
+
+ services.cjdns = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the cjdns network encryption
+ and routing engine. A file at /etc/cjdns.keys will
+ be created if it does not exist to contain a random
+ secret key that your IPv6 address will be derived from.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ example = { router.interface.tunDevice = "tun10"; };
+ description = ''
+ Extra configuration, given as attrs, that will be merged recursively
+ with the rest of the JSON generated by this module, at the root node.
+ '';
+ };
+
+ confFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/etc/cjdroute.conf";
+ description = ''
+ Ignore all other cjdns options and load configuration from this file.
+ '';
+ };
+
+ authorizedPasswords = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [
+ "snyrfgkqsc98qh1y4s5hbu0j57xw5s0"
+ "z9md3t4p45mfrjzdjurxn4wuj0d8swv"
+ "49275fut6tmzu354pq70sr5b95qq0vj"
+ ];
+ description = ''
+ Any remote cjdns nodes that offer these passwords on
+ connection will be allowed to route through this node.
+ '';
+ };
+
+ admin = {
+ bind = mkOption {
+ type = types.str;
+ default = "127.0.0.1:11234";
+ description = ''
+ Bind the administration port to this address and port.
+ '';
+ };
+ };
+
+ UDPInterface = {
+ bind = mkOption {
+ type = types.str;
+ default = "";
+ example = "192.168.1.32:43211";
+ description = ''
+ Address and port to bind UDP tunnels to.
+ '';
+ };
+ connectTo = mkOption {
+ type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
+ default = { };
+ example = {
+ "192.168.1.1:27313" = {
+ hostname = "homer.hype";
+ password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
+ publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
+ };
+ };
+ description = ''
+ Credentials for making UDP tunnels.
+ '';
+ };
+ };
+
+ ETHInterface = {
+ bind = mkOption {
+ type = types.str;
+ default = "";
+ example = "eth0";
+ description =
+ ''
+ Bind to this device for native ethernet operation.
+ <literal>all</literal> is a pseudo-name which will try to connect to all devices.
+ '';
+ };
+
+ beacon = mkOption {
+ type = types.int;
+ default = 2;
+ description = ''
+ Auto-connect to other cjdns nodes on the same network.
+ Options:
+ 0: Disabled.
+ 1: Accept beacons, this will cause cjdns to accept incoming
+ beacon messages and try connecting to the sender.
+ 2: Accept and send beacons, this will cause cjdns to broadcast
+ messages on the local network which contain a randomly
+ generated per-session password, other nodes which have this
+ set to 1 or 2 will hear the beacon messages and connect
+ automatically.
+ '';
+ };
+
+ connectTo = mkOption {
+ type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
+ default = { };
+ example = {
+ "01:02:03:04:05:06" = {
+ hostname = "homer.hype";
+ password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
+ publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
+ };
+ };
+ description = ''
+ Credentials for connecting look similar to UDP credientials
+ except they begin with the mac address.
+ '';
+ };
+ };
+
+ addExtraHosts = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to add cjdns peers with an associated hostname to
+ <filename>/etc/hosts</filename>. Beware that enabling this
+ incurs heavy eval-time costs.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ boot.kernelModules = [ "tun" ];
+
+ # networking.firewall.allowedUDPPorts = ...
+
+ systemd.services.cjdns = {
+ description = "cjdns: routing engine designed for security, scalability, speed and ease of use";
+ wantedBy = [ "multi-user.target" "sleep.target"];
+ after = [ "network-online.target" ];
+ bindsTo = [ "network-online.target" ];
+
+ preStart = if cfg.confFile != null then "" else ''
+ [ -e /etc/cjdns.keys ] && source /etc/cjdns.keys
+
+ if [ -z "$CJDNS_PRIVATE_KEY" ]; then
+ shopt -s lastpipe
+ ${pkg}/bin/makekeys | { read private ipv6 public; }
+
+ umask 0077
+ echo "CJDNS_PRIVATE_KEY=$private" >> /etc/cjdns.keys
+ echo -e "CJDNS_IPV6=$ipv6\nCJDNS_PUBLIC_KEY=$public" > /etc/cjdns.public
+
+ chmod 600 /etc/cjdns.keys
+ chmod 444 /etc/cjdns.public
+ fi
+
+ if [ -z "$CJDNS_ADMIN_PASSWORD" ]; then
+ echo "CJDNS_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 96)" \
+ >> /etc/cjdns.keys
+ fi
+ '';
+
+ script = (
+ if cfg.confFile != null then "${pkg}/bin/cjdroute < ${cfg.confFile}" else
+ ''
+ source /etc/cjdns.keys
+ (cat <<'EOF'
+ ${cjdrouteConf}
+ EOF
+ ) | sed \
+ -e "s/@CJDNS_ADMIN_PASSWORD@/$CJDNS_ADMIN_PASSWORD/g" \
+ -e "s/@CJDNS_PRIVATE_KEY@/$CJDNS_PRIVATE_KEY/g" \
+ | ${pkg}/bin/cjdroute
+ ''
+ );
+
+ serviceConfig = {
+ Type = "forking";
+ Restart = "always";
+ StartLimitInterval = 0;
+ RestartSec = 1;
+ CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW CAP_SETUID";
+ ProtectSystem = true;
+ # Doesn't work on i686, causing service to fail
+ MemoryDenyWriteExecute = !pkgs.stdenv.isi686;
+ ProtectHome = true;
+ PrivateTmp = true;
+ };
+ };
+
+ networking.extraHosts = mkIf cfg.addExtraHosts cjdnsExtraHosts;
+
+ assertions = [
+ { assertion = ( cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" || cfg.confFile != null );
+ message = "Neither cjdns.ETHInterface.bind nor cjdns.UDPInterface.bind defined.";
+ }
+ { assertion = config.networking.enableIPv6;
+ message = "networking.enableIPv6 must be enabled for CJDNS to work";
+ }
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/cntlm.nix b/nixpkgs/nixos/modules/services/networking/cntlm.nix
new file mode 100644
index 00000000000..4e4e3104c3a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/cntlm.nix
@@ -0,0 +1,126 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.cntlm;
+
+ configFile = if cfg.configText != "" then
+ pkgs.writeText "cntlm.conf" ''
+ ${cfg.configText}
+ ''
+ else
+ pkgs.writeText "lighttpd.conf" ''
+ # Cntlm Authentication Proxy Configuration
+ Username ${cfg.username}
+ Domain ${cfg.domain}
+ Password ${cfg.password}
+ ${optionalString (cfg.netbios_hostname != "") "Workstation ${cfg.netbios_hostname}"}
+ ${concatMapStrings (entry: "Proxy ${entry}\n") cfg.proxy}
+ ${optionalString (cfg.noproxy != []) "NoProxy ${concatStringsSep ", " cfg.noproxy}"}
+
+ ${concatMapStrings (port: ''
+ Listen ${toString port}
+ '') cfg.port}
+
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+
+ options.services.cntlm = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable the cntlm, which start a local proxy.
+ '';
+ };
+
+ username = mkOption {
+ description = ''
+ Proxy account name, without the possibility to include domain name ('at' sign is interpreted literally).
+ '';
+ };
+
+ domain = mkOption {
+ description = ''Proxy account domain/workgroup name.'';
+ };
+
+ password = mkOption {
+ default = "/etc/cntlm.password";
+ type = types.str;
+ description = ''Proxy account password. Note: use chmod 0600 on /etc/cntlm.password for security.'';
+ };
+
+ netbios_hostname = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ The hostname of your machine.
+ '';
+ };
+
+ proxy = mkOption {
+ description = ''
+ A list of NTLM/NTLMv2 authenticating HTTP proxies.
+
+ Parent proxy, which requires authentication. The same as proxy on the command-line, can be used more than once to specify unlimited
+ number of proxies. Should one proxy fail, cntlm automatically moves on to the next one. The connect request fails only if the whole
+ list of proxies is scanned and (for each request) and found to be invalid. Command-line takes precedence over the configuration file.
+ '';
+ example = [ "proxy.example.com:81" ];
+ };
+
+ noproxy = mkOption {
+ description = ''
+ A list of domains where the proxy is skipped.
+ '';
+ default = [];
+ example = [ "*.example.com" "example.com" ];
+ };
+
+ port = mkOption {
+ default = [3128];
+ description = "Specifies on which ports the cntlm daemon listens.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional config appended to the end of the generated <filename>cntlm.conf</filename>.";
+ };
+
+ configText = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Verbatim contents of <filename>cntlm.conf</filename>.";
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.cntlm = {
+ description = "CNTLM is an NTLM / NTLM Session Response / NTLMv2 authenticating HTTP proxy";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "cntlm";
+ ExecStart = ''
+ ${pkgs.cntlm}/bin/cntlm -U cntlm -c ${configFile} -v -f
+ '';
+ };
+ };
+
+ users.users.cntlm = {
+ name = "cntlm";
+ description = "cntlm system-wide daemon";
+ isSystemUser = true;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/connman.nix b/nixpkgs/nixos/modules/services/networking/connman.nix
new file mode 100644
index 00000000000..31127f79049
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/connman.nix
@@ -0,0 +1,128 @@
+{ config, lib, pkgs, ... }:
+
+with pkgs;
+with lib;
+
+let
+ cfg = config.networking.connman;
+ configFile = pkgs.writeText "connman.conf" ''
+ [General]
+ NetworkInterfaceBlacklist=${concatStringsSep "," cfg.networkInterfaceBlacklist}
+
+ ${cfg.extraConfig}
+ '';
+in {
+
+ ###### interface
+
+ options = {
+
+ networking.connman = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use ConnMan for managing your network connections.
+ '';
+ };
+
+ enableVPN = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable ConnMan VPN service.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = ''
+ '';
+ description = ''
+ Configuration lines appended to the generated connman configuration file.
+ '';
+ };
+
+ networkInterfaceBlacklist = mkOption {
+ type = with types; listOf str;
+ default = [ "vmnet" "vboxnet" "virbr" "ifb" "ve" ];
+ description = ''
+ Default blacklisted interfaces, this includes NixOS containers interfaces (ve).
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = with types; listOf str;
+ default = [ ];
+ example = [ "--nodnsproxy" ];
+ description = ''
+ Extra flags to pass to connmand
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [{
+ assertion = !config.networking.useDHCP;
+ message = "You can not use services.networking.connman with services.networking.useDHCP";
+ }{
+ assertion = config.networking.wireless.enable;
+ message = "You must use services.networking.connman with services.networking.wireless";
+ }{
+ assertion = !config.networking.networkmanager.enable;
+ message = "You can not use services.networking.connman with services.networking.networkmanager";
+ }];
+
+ environment.systemPackages = [ connman ];
+
+ systemd.services.connman = {
+ description = "Connection service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "syslog.target" ];
+ serviceConfig = {
+ Type = "dbus";
+ BusName = "net.connman";
+ Restart = "on-failure";
+ ExecStart = "${pkgs.connman}/sbin/connmand --config=${configFile} --nodaemon ${toString cfg.extraFlags}";
+ StandardOutput = "null";
+ };
+ };
+
+ systemd.services.connman-vpn = mkIf cfg.enableVPN {
+ description = "ConnMan VPN service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "syslog.target" ];
+ before = [ "connman" ];
+ serviceConfig = {
+ Type = "dbus";
+ BusName = "net.connman.vpn";
+ ExecStart = "${pkgs.connman}/sbin/connman-vpnd -n";
+ StandardOutput = "null";
+ };
+ };
+
+ systemd.services.net-connman-vpn = mkIf cfg.enableVPN {
+ description = "D-BUS Service";
+ serviceConfig = {
+ Name = "net.connman.vpn";
+ before = [ "connman" ];
+ ExecStart = "${pkgs.connman}/sbin/connman-vpnd -n";
+ User = "root";
+ SystemdService = "connman-vpn.service";
+ };
+ };
+
+ networking = {
+ useDHCP = false;
+ wireless.enable = true;
+ networkmanager.enable = false;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/consul.nix b/nixpkgs/nixos/modules/services/networking/consul.nix
new file mode 100644
index 00000000000..689cbc8a986
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/consul.nix
@@ -0,0 +1,254 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+let
+
+ dataDir = "/var/lib/consul";
+ cfg = config.services.consul;
+
+ configOptions = {
+ data_dir = dataDir;
+ ui = cfg.webUi;
+ } // cfg.extraConfig;
+
+ configFiles = [ "/etc/consul.json" "/etc/consul-addrs.json" ]
+ ++ cfg.extraConfigFiles;
+
+ devices = attrValues (filterAttrs (_: i: i != null) cfg.interface);
+ systemdDevices = forEach devices
+ (i: "sys-subsystem-net-devices-${utils.escapeSystemdPath i}.device");
+in
+{
+ options = {
+
+ services.consul = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables the consul daemon.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.consul;
+ defaultText = "pkgs.consul";
+ description = ''
+ The package used for the Consul agent and CLI.
+ '';
+ };
+
+
+ webUi = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables the web interface on the consul http port.
+ '';
+ };
+
+ leaveOnStop = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, causes a leave action to be sent when closing consul.
+ This allows a clean termination of the node, but permanently removes
+ it from the cluster. You probably don't want this option unless you
+ are running a node which going offline in a permanent / semi-permanent
+ fashion.
+ '';
+ };
+
+ interface = {
+
+ advertise = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The name of the interface to pull the advertise_addr from.
+ '';
+ };
+
+ bind = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The name of the interface to pull the bind_addr from.
+ '';
+ };
+
+ };
+
+ forceIpv4 = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether we should force the interfaces to only pull ipv4 addresses.
+ '';
+ };
+
+ dropPrivileges = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether the consul agent should be run as a non-root consul user.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = { };
+ description = ''
+ Extra configuration options which are serialized to json and added
+ to the config.json file.
+ '';
+ };
+
+ extraConfigFiles = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ Additional configuration files to pass to consul
+ NOTE: These will not trigger the service to be restarted when altered.
+ '';
+ };
+
+ alerts = {
+ enable = mkEnableOption "consul-alerts";
+
+ package = mkOption {
+ description = "Package to use for consul-alerts.";
+ default = pkgs.consul-alerts;
+ defaultText = "pkgs.consul-alerts";
+ type = types.package;
+ };
+
+ listenAddr = mkOption {
+ description = "Api listening address.";
+ default = "localhost:9000";
+ type = types.str;
+ };
+
+ consulAddr = mkOption {
+ description = "Consul api listening adddress";
+ default = "localhost:8500";
+ type = types.str;
+ };
+
+ watchChecks = mkOption {
+ description = "Whether to enable check watcher.";
+ default = true;
+ type = types.bool;
+ };
+
+ watchEvents = mkOption {
+ description = "Whether to enable event watcher.";
+ default = true;
+ type = types.bool;
+ };
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable (
+ mkMerge [{
+
+ users.users.consul = {
+ description = "Consul agent daemon user";
+ uid = config.ids.uids.consul;
+ # The shell is needed for health checks
+ shell = "/run/current-system/sw/bin/bash";
+ };
+
+ environment = {
+ etc."consul.json".text = builtins.toJSON configOptions;
+ # We need consul.d to exist for consul to start
+ etc."consul.d/dummy.json".text = "{ }";
+ systemPackages = [ cfg.package ];
+ };
+
+ systemd.services.consul = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ] ++ systemdDevices;
+ bindsTo = systemdDevices;
+ restartTriggers = [ config.environment.etc."consul.json".source ]
+ ++ mapAttrsToList (_: d: d.source)
+ (filterAttrs (n: _: hasPrefix "consul.d/" n) config.environment.etc);
+
+ serviceConfig = {
+ ExecStart = "@${cfg.package.bin}/bin/consul consul agent -config-dir /etc/consul.d"
+ + concatMapStrings (n: " -config-file ${n}") configFiles;
+ ExecReload = "${cfg.package.bin}/bin/consul reload";
+ PermissionsStartOnly = true;
+ User = if cfg.dropPrivileges then "consul" else null;
+ Restart = "on-failure";
+ TimeoutStartSec = "infinity";
+ } // (optionalAttrs (cfg.leaveOnStop) {
+ ExecStop = "${cfg.package.bin}/bin/consul leave";
+ });
+
+ path = with pkgs; [ iproute gnugrep gawk consul ];
+ preStart = ''
+ mkdir -m 0700 -p ${dataDir}
+ chown -R consul ${dataDir}
+
+ # Determine interface addresses
+ getAddrOnce () {
+ ip addr show dev "$1" \
+ | grep 'inet${optionalString (cfg.forceIpv4) " "}.*scope global' \
+ | awk -F '[ /\t]*' '{print $3}' | head -n 1
+ }
+ getAddr () {
+ ADDR="$(getAddrOnce $1)"
+ LEFT=60 # Die after 1 minute
+ while [ -z "$ADDR" ]; do
+ sleep 1
+ LEFT=$(expr $LEFT - 1)
+ if [ "$LEFT" -eq "0" ]; then
+ echo "Address lookup timed out"
+ exit 1
+ fi
+ ADDR="$(getAddrOnce $1)"
+ done
+ echo "$ADDR"
+ }
+ echo "{" > /etc/consul-addrs.json
+ delim=" "
+ ''
+ + concatStrings (flip mapAttrsToList cfg.interface (name: i:
+ optionalString (i != null) ''
+ echo "$delim \"${name}_addr\": \"$(getAddr "${i}")\"" >> /etc/consul-addrs.json
+ delim=","
+ ''))
+ + ''
+ echo "}" >> /etc/consul-addrs.json
+ '';
+ };
+ }
+
+ (mkIf (cfg.alerts.enable) {
+ systemd.services.consul-alerts = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "consul.service" ];
+
+ path = [ cfg.package ];
+
+ serviceConfig = {
+ ExecStart = ''
+ ${cfg.alerts.package.bin}/bin/consul-alerts start \
+ --alert-addr=${cfg.alerts.listenAddr} \
+ --consul-addr=${cfg.alerts.consulAddr} \
+ ${optionalString cfg.alerts.watchChecks "--watch-checks"} \
+ ${optionalString cfg.alerts.watchEvents "--watch-events"}
+ '';
+ User = if cfg.dropPrivileges then "consul" else null;
+ Restart = "on-failure";
+ };
+ };
+ })
+
+ ]);
+}
diff --git a/nixpkgs/nixos/modules/services/networking/coredns.nix b/nixpkgs/nixos/modules/services/networking/coredns.nix
new file mode 100644
index 00000000000..afb2b547a46
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/coredns.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.coredns;
+ configFile = pkgs.writeText "Corefile" cfg.config;
+in {
+ options.services.coredns = {
+ enable = mkEnableOption "Coredns dns server";
+
+ config = mkOption {
+ default = "";
+ example = ''
+ . {
+ whoami
+ }
+ '';
+ type = types.lines;
+ description = "Verbatim Corefile to use. See <link xlink:href=\"https://coredns.io/manual/toc/#configuration\"/> for details.";
+ };
+
+ package = mkOption {
+ default = pkgs.coredns;
+ defaultText = "pkgs.coredns";
+ type = types.package;
+ description = "Coredns package to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.coredns = {
+ description = "Coredns dns server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ PermissionsStartOnly = true;
+ LimitNPROC = 512;
+ LimitNOFILE = 1048576;
+ CapabilityBoundingSet = "cap_net_bind_service";
+ AmbientCapabilities = "cap_net_bind_service";
+ NoNewPrivileges = true;
+ DynamicUser = true;
+ ExecStart = "${getBin cfg.package}/bin/coredns -conf=${configFile}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR1 $MAINPID";
+ Restart = "on-failure";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/coturn.nix b/nixpkgs/nixos/modules/services/networking/coturn.nix
new file mode 100644
index 00000000000..c430ce5af92
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/coturn.nix
@@ -0,0 +1,336 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.coturn;
+ pidfile = "/run/turnserver/turnserver.pid";
+ configFile = pkgs.writeText "turnserver.conf" ''
+listening-port=${toString cfg.listening-port}
+tls-listening-port=${toString cfg.tls-listening-port}
+alt-listening-port=${toString cfg.alt-listening-port}
+alt-tls-listening-port=${toString cfg.alt-tls-listening-port}
+${concatStringsSep "\n" (map (x: "listening-ip=${x}") cfg.listening-ips)}
+${concatStringsSep "\n" (map (x: "relay-ip=${x}") cfg.relay-ips)}
+min-port=${toString cfg.min-port}
+max-port=${toString cfg.max-port}
+${lib.optionalString cfg.lt-cred-mech "lt-cred-mech"}
+${lib.optionalString cfg.no-auth "no-auth"}
+${lib.optionalString cfg.use-auth-secret "use-auth-secret"}
+${lib.optionalString (cfg.static-auth-secret != null) ("static-auth-secret=${cfg.static-auth-secret}")}
+realm=${cfg.realm}
+${lib.optionalString cfg.no-udp "no-udp"}
+${lib.optionalString cfg.no-tcp "no-tcp"}
+${lib.optionalString cfg.no-tls "no-tls"}
+${lib.optionalString cfg.no-dtls "no-dtls"}
+${lib.optionalString cfg.no-udp-relay "no-udp-relay"}
+${lib.optionalString cfg.no-tcp-relay "no-tcp-relay"}
+${lib.optionalString (cfg.cert != null) "cert=${cfg.cert}"}
+${lib.optionalString (cfg.pkey != null) "pkey=${cfg.pkey}"}
+${lib.optionalString (cfg.dh-file != null) ("dh-file=${cfg.dh-file}")}
+no-stdout-log
+syslog
+pidfile=${pidfile}
+${lib.optionalString cfg.secure-stun "secure-stun"}
+${lib.optionalString cfg.no-cli "no-cli"}
+cli-ip=${cfg.cli-ip}
+cli-port=${toString cfg.cli-port}
+${lib.optionalString (cfg.cli-password != null) ("cli-password=${cfg.cli-password}")}
+${cfg.extraConfig}
+'';
+in {
+ options = {
+ services.coturn = {
+ enable = mkEnableOption "coturn TURN server";
+ listening-port = mkOption {
+ type = types.int;
+ default = 3478;
+ description = ''
+ TURN listener port for UDP and TCP.
+ Note: actually, TLS and DTLS sessions can connect to the
+ "plain" TCP and UDP port(s), too - if allowed by configuration.
+ '';
+ };
+ tls-listening-port = mkOption {
+ type = types.int;
+ default = 5349;
+ description = ''
+ TURN listener port for TLS.
+ Note: actually, "plain" TCP and UDP sessions can connect to the TLS and
+ DTLS port(s), too - if allowed by configuration. The TURN server
+ "automatically" recognizes the type of traffic. Actually, two listening
+ endpoints (the "plain" one and the "tls" one) are equivalent in terms of
+ functionality; but we keep both endpoints to satisfy the RFC 5766 specs.
+ For secure TCP connections, we currently support SSL version 3 and
+ TLS version 1.0, 1.1 and 1.2.
+ For secure UDP connections, we support DTLS version 1.
+ '';
+ };
+ alt-listening-port = mkOption {
+ type = types.int;
+ default = cfg.listening-port + 1;
+ defaultText = "listening-port + 1";
+ description = ''
+ Alternative listening port for UDP and TCP listeners;
+ default (or zero) value means "listening port plus one".
+ This is needed for RFC 5780 support
+ (STUN extension specs, NAT behavior discovery). The TURN Server
+ supports RFC 5780 only if it is started with more than one
+ listening IP address of the same family (IPv4 or IPv6).
+ RFC 5780 is supported only by UDP protocol, other protocols
+ are listening to that endpoint only for "symmetry".
+ '';
+ };
+ alt-tls-listening-port = mkOption {
+ type = types.int;
+ default = cfg.tls-listening-port + 1;
+ defaultText = "tls-listening-port + 1";
+ description = ''
+ Alternative listening port for TLS and DTLS protocols.
+ '';
+ };
+ listening-ips = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "203.0.113.42" "2001:DB8::42" ];
+ description = ''
+ Listener IP addresses of relay server.
+ If no IP(s) specified in the config file or in the command line options,
+ then all IPv4 and IPv6 system IPs will be used for listening.
+ '';
+ };
+ relay-ips = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "203.0.113.42" "2001:DB8::42" ];
+ description = ''
+ Relay address (the local IP address that will be used to relay the
+ packets to the peer).
+ Multiple relay addresses may be used.
+ The same IP(s) can be used as both listening IP(s) and relay IP(s).
+
+ If no relay IP(s) specified, then the turnserver will apply the default
+ policy: it will decide itself which relay addresses to be used, and it
+ will always be using the client socket IP address as the relay IP address
+ of the TURN session (if the requested relay address family is the same
+ as the family of the client socket).
+ '';
+ };
+ min-port = mkOption {
+ type = types.int;
+ default = 49152;
+ description = ''
+ Lower bound of UDP relay endpoints
+ '';
+ };
+ max-port = mkOption {
+ type = types.int;
+ default = 65535;
+ description = ''
+ Upper bound of UDP relay endpoints
+ '';
+ };
+ lt-cred-mech = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Use long-term credential mechanism.
+ '';
+ };
+ no-auth = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This option is opposite to lt-cred-mech.
+ (TURN Server with no-auth option allows anonymous access).
+ If neither option is defined, and no users are defined,
+ then no-auth is default. If at least one user is defined,
+ in this file or in command line or in usersdb file, then
+ lt-cred-mech is default.
+ '';
+ };
+ use-auth-secret = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ TURN REST API flag.
+ Flag that sets a special authorization option that is based upon authentication secret.
+ This feature can be used with the long-term authentication mechanism, only.
+ This feature purpose is to support "TURN Server REST API", see
+ "TURN REST API" link in the project's page
+ https://github.com/coturn/coturn/
+
+ This option is used with timestamp:
+
+ usercombo -> "timestamp:userid"
+ turn user -> usercombo
+ turn password -> base64(hmac(secret key, usercombo))
+
+ This allows TURN credentials to be accounted for a specific user id.
+ If you don't have a suitable id, the timestamp alone can be used.
+ This option is just turning on secret-based authentication.
+ The actual value of the secret is defined either by option static-auth-secret,
+ or can be found in the turn_secret table in the database.
+ '';
+ };
+ static-auth-secret = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ 'Static' authentication secret value (a string) for TURN REST API only.
+ If not set, then the turn server
+ will try to use the 'dynamic' value in turn_secret table
+ in user database (if present). The database-stored value can be changed on-the-fly
+ by a separate program, so this is why that other mode is 'dynamic'.
+ '';
+ };
+ realm = mkOption {
+ type = types.str;
+ default = config.networking.hostName;
+ example = "example.com";
+ description = ''
+ The default realm to be used for the users when no explicit
+ origin/realm relationship was found in the database, or if the TURN
+ server is not using any database (just the commands-line settings
+ and the userdb file). Must be used with long-term credentials
+ mechanism or with TURN REST API.
+ '';
+ };
+ cert = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/var/lib/acme/example.com/fullchain.pem";
+ description = ''
+ Certificate file in PEM format.
+ '';
+ };
+ pkey = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/var/lib/acme/example.com/key.pem";
+ description = ''
+ Private key file in PEM format.
+ '';
+ };
+ dh-file = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Use custom DH TLS key, stored in PEM format in the file.
+ '';
+ };
+ secure-stun = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Require authentication of the STUN Binding request.
+ By default, the clients are allowed anonymous access to the STUN Binding functionality.
+ '';
+ };
+ no-cli = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Turn OFF the CLI support.
+ '';
+ };
+ cli-ip = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ Local system IP address to be used for CLI server endpoint.
+ '';
+ };
+ cli-port = mkOption {
+ type = types.int;
+ default = 5766;
+ description = ''
+ CLI server port.
+ '';
+ };
+ cli-password = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ CLI access password.
+ For the security reasons, it is recommended to use the encrypted
+ for of the password (see the -P command in the turnadmin utility).
+ '';
+ };
+ no-udp = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Disable UDP client listener";
+ };
+ no-tcp = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Disable TCP client listener";
+ };
+ no-tls = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Disable TLS client listener";
+ };
+ no-dtls = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Disable DTLS client listener";
+ };
+ no-udp-relay = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Disable UDP relay endpoints";
+ };
+ no-tcp-relay = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Disable TCP relay endpoints";
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional configuration options";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users = [
+ { name = "turnserver";
+ uid = config.ids.uids.turnserver;
+ description = "coturn TURN server user";
+ } ];
+ users.groups = [
+ { name = "turnserver";
+ gid = config.ids.gids.turnserver;
+ members = [ "turnserver" ];
+ } ];
+
+ systemd.services.coturn = {
+ description = "coturn TURN server";
+ after = [ "network-online.target" ];
+ wants = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ unitConfig = {
+ Documentation = "man:coturn(1) man:turnadmin(1) man:turnserver(1)";
+ };
+
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${pkgs.coturn}/bin/turnserver -c ${configFile}";
+ RuntimeDirectory = "turnserver";
+ User = "turnserver";
+ Group = "turnserver";
+ AmbientCapabilities =
+ mkIf (
+ cfg.listening-port < 1024 ||
+ cfg.alt-listening-port < 1024 ||
+ cfg.tls-listening-port < 1024 ||
+ cfg.alt-tls-listening-port < 1024 ||
+ cfg.min-port < 1024
+ ) "cap_net_bind_service";
+ Restart = "on-abort";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dante.nix b/nixpkgs/nixos/modules/services/networking/dante.nix
new file mode 100644
index 00000000000..20d4faa1cdb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dante.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+ cfg = config.services.dante;
+ confFile = pkgs.writeText "dante-sockd.conf" ''
+ user.privileged: root
+ user.unprivileged: dante
+ logoutput: syslog
+
+ ${cfg.config}
+ '';
+in
+
+{
+ meta = {
+ maintainers = with maintainers; [ arobyn ];
+ };
+
+ options = {
+ services.dante = {
+ enable = mkEnableOption "Dante SOCKS proxy";
+
+ config = mkOption {
+ type = types.lines;
+ description = ''
+ Contents of Dante's configuration file.
+ NOTE: user.privileged, user.unprivileged and logoutput are set by the service.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ { assertion = cfg.config != "";
+ message = "please provide Dante configuration file contents";
+ }
+ ];
+
+ users.users.dante = {
+ description = "Dante SOCKS proxy daemon user";
+ isSystemUser = true;
+ group = "dante";
+ };
+ users.groups.dante = {};
+
+ systemd.services.dante = {
+ description = "Dante SOCKS v4 and v5 compatible proxy server";
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${pkgs.dante}/bin/sockd -f ${confFile}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ # Can crash sometimes; see https://github.com/NixOS/nixpkgs/pull/39005#issuecomment-381828708
+ Restart = "on-failure";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ddclient.nix b/nixpkgs/nixos/modules/services/networking/ddclient.nix
new file mode 100644
index 00000000000..04ce5ca3a87
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ddclient.nix
@@ -0,0 +1,201 @@
+{ config, pkgs, lib, ... }:
+
+let
+ cfg = config.services.ddclient;
+ boolToStr = bool: if bool then "yes" else "no";
+ dataDir = "/var/lib/ddclient";
+
+ configText = ''
+ # This file can be used as a template for configFile or is automatically generated by Nix options.
+ cache=${dataDir}/ddclient.cache
+ foreground=YES
+ use=${cfg.use}
+ login=${cfg.username}
+ password=${cfg.password}
+ protocol=${cfg.protocol}
+ ${lib.optionalString (cfg.script != "") "script=${cfg.script}"}
+ ${lib.optionalString (cfg.server != "") "server=${cfg.server}"}
+ ${lib.optionalString (cfg.zone != "") "zone=${cfg.zone}"}
+ ssl=${boolToStr cfg.ssl}
+ wildcard=YES
+ quiet=${boolToStr cfg.quiet}
+ verbose=${boolToStr cfg.verbose}
+ ${cfg.extraConfig}
+ ${lib.concatStringsSep "," cfg.domains}
+ '';
+
+in
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.ddclient = with lib.types; {
+
+ enable = mkOption {
+ default = false;
+ type = bool;
+ description = ''
+ Whether to synchronise your machine's IP address with a dynamic DNS provider (e.g. dyndns.org).
+ '';
+ };
+
+ domains = mkOption {
+ default = [ "" ];
+ type = listOf str;
+ description = ''
+ Domain name(s) to synchronize.
+ '';
+ };
+
+ username = mkOption {
+ default = "";
+ type = str;
+ description = ''
+ User name.
+ '';
+ };
+
+ password = mkOption {
+ default = "";
+ type = str;
+ description = ''
+ Password. WARNING: The password becomes world readable in the Nix store.
+ '';
+ };
+
+ interval = mkOption {
+ default = "10min";
+ type = str;
+ description = ''
+ The interval at which to run the check and update.
+ See <command>man 7 systemd.time</command> for the format.
+ '';
+ };
+
+ configFile = mkOption {
+ default = "/etc/ddclient.conf";
+ type = path;
+ description = ''
+ Path to configuration file.
+ When set to the default '/etc/ddclient.conf' it will be populated with the various other options in this module. When it is changed (for example: '/root/nixos/secrets/ddclient.conf') the file read directly to configure ddclient. This is a source of impurity.
+ The purpose of this is to avoid placing secrets into the store.
+ '';
+ example = "/root/nixos/secrets/ddclient.conf";
+ };
+
+ protocol = mkOption {
+ default = "dyndns2";
+ type = str;
+ description = ''
+ Protocol to use with dynamic DNS provider (see https://sourceforge.net/p/ddclient/wiki/protocols).
+ '';
+ };
+
+ server = mkOption {
+ default = "";
+ type = str;
+ description = ''
+ Server address.
+ '';
+ };
+
+ ssl = mkOption {
+ default = true;
+ type = bool;
+ description = ''
+ Whether to use to use SSL/TLS to connect to dynamic DNS provider.
+ '';
+ };
+
+
+ quiet = mkOption {
+ default = false;
+ type = bool;
+ description = ''
+ Print no messages for unnecessary updates.
+ '';
+ };
+
+ script = mkOption {
+ default = "";
+ type = str;
+ description = ''
+ script as required by some providers.
+ '';
+ };
+
+ use = mkOption {
+ default = "web, web=checkip.dyndns.com/, web-skip='Current IP Address: '";
+ type = str;
+ description = ''
+ Method to determine the IP address to send to the dynamic DNS provider.
+ '';
+ };
+
+ verbose = mkOption {
+ default = true;
+ type = bool;
+ description = ''
+ Print verbose information.
+ '';
+ };
+
+ zone = mkOption {
+ default = "";
+ type = str;
+ description = ''
+ zone as required by some providers.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = lines;
+ description = ''
+ Extra configuration. Contents will be added verbatim to the configuration file.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.ddclient.enable {
+ environment.etc."ddclient.conf" = {
+ enable = cfg.configFile == "/etc/ddclient.conf";
+ mode = "0600";
+ text = configText;
+ };
+
+ systemd.services.ddclient = {
+ description = "Dynamic DNS Client";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ restartTriggers = [ config.environment.etc."ddclient.conf".source ];
+
+ serviceConfig = rec {
+ DynamicUser = true;
+ RuntimeDirectory = StateDirectory;
+ StateDirectory = builtins.baseNameOf dataDir;
+ Type = "oneshot";
+ ExecStartPre = "!${lib.getBin pkgs.coreutils}/bin/install -m666 ${cfg.configFile} /run/${RuntimeDirectory}/ddclient.conf";
+ ExecStart = "${lib.getBin pkgs.ddclient}/bin/ddclient -file /run/${RuntimeDirectory}/ddclient.conf";
+ };
+ };
+
+ systemd.timers.ddclient = {
+ description = "Run ddclient";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnBootSec = cfg.interval;
+ OnUnitInactiveSec = cfg.interval;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dhcpcd.nix b/nixpkgs/nixos/modules/services/networking/dhcpcd.nix
new file mode 100644
index 00000000000..7b278603455
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dhcpcd.nix
@@ -0,0 +1,202 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dhcpcd = if !config.boot.isContainer then pkgs.dhcpcd else pkgs.dhcpcd.override { udev = null; };
+
+ cfg = config.networking.dhcpcd;
+
+ interfaces = attrValues config.networking.interfaces;
+
+ enableDHCP = config.networking.dhcpcd.enable &&
+ (config.networking.useDHCP || any (i: i.useDHCP == true) interfaces);
+
+ # Don't start dhcpcd on explicitly configured interfaces or on
+ # interfaces that are part of a bridge, bond or sit device.
+ ignoredInterfaces =
+ map (i: i.name) (filter (i: if i.useDHCP != null then !i.useDHCP else i.ipv4.addresses != [ ]) interfaces)
+ ++ mapAttrsToList (i: _: i) config.networking.sits
+ ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bridges))
+ ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.vswitches))
+ ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bonds))
+ ++ config.networking.dhcpcd.denyInterfaces;
+
+ arrayAppendOrNull = a1: a2: if a1 == null && a2 == null then null
+ else if a1 == null then a2 else if a2 == null then a1
+ else a1 ++ a2;
+
+ # If dhcp is disabled but explicit interfaces are enabled,
+ # we need to provide dhcp just for those interfaces.
+ allowInterfaces = arrayAppendOrNull cfg.allowInterfaces
+ (if !config.networking.useDHCP && enableDHCP then
+ map (i: i.name) (filter (i: i.useDHCP == true) interfaces) else null);
+
+ # Config file adapted from the one that ships with dhcpcd.
+ dhcpcdConf = pkgs.writeText "dhcpcd.conf"
+ ''
+ # Inform the DHCP server of our hostname for DDNS.
+ hostname
+
+ # A list of options to request from the DHCP server.
+ option domain_name_servers, domain_name, domain_search, host_name
+ option classless_static_routes, ntp_servers, interface_mtu
+
+ # A ServerID is required by RFC2131.
+ # Commented out because of many non-compliant DHCP servers in the wild :(
+ #require dhcp_server_identifier
+
+ # A hook script is provided to lookup the hostname if not set by
+ # the DHCP server, but it should not be run by default.
+ nohook lookup-hostname
+
+ # Ignore peth* devices; on Xen, they're renamed physical
+ # Ethernet cards used for bridging. Likewise for vif* and tap*
+ # (Xen) and virbr* and vnet* (libvirt).
+ denyinterfaces ${toString ignoredInterfaces} lo peth* vif* tap* tun* virbr* vnet* vboxnet* sit*
+
+ # Use the list of allowed interfaces if specified
+ ${optionalString (allowInterfaces != null) "allowinterfaces ${toString allowInterfaces}"}
+
+ ${cfg.extraConfig}
+ '';
+
+ exitHook = pkgs.writeText "dhcpcd.exit-hook"
+ ''
+ if [ "$reason" = BOUND -o "$reason" = REBOOT ]; then
+ # Restart ntpd. We need to restart it to make sure that it
+ # will actually do something: if ntpd cannot resolve the
+ # server hostnames in its config file, then it will never do
+ # anything ever again ("couldn't resolve ..., giving up on
+ # it"), so we silently lose time synchronisation. This also
+ # applies to openntpd.
+ ${config.systemd.package}/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service || true
+ fi
+
+ ${cfg.runHook}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ networking.dhcpcd.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable dhcpcd for device configuration. This is mainly to
+ explicitly disable dhcpcd (for example when using networkd).
+ '';
+ };
+
+ networking.dhcpcd.persistent = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whenever to leave interfaces configured on dhcpcd daemon
+ shutdown. Set to true if you have your root or store mounted
+ over the network or this machine accepts SSH connections
+ through DHCP interfaces and clients should be notified when
+ it shuts down.
+ '';
+ };
+
+ networking.dhcpcd.denyInterfaces = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Disable the DHCP client for any interface whose name matches
+ any of the shell glob patterns in this list. The purpose of
+ this option is to blacklist virtual interfaces such as those
+ created by Xen, libvirt, LXC, etc.
+ '';
+ };
+
+ networking.dhcpcd.allowInterfaces = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = ''
+ Enable the DHCP client for any interface whose name matches
+ any of the shell glob patterns in this list. Any interface not
+ explicitly matched by this pattern will be denied. This pattern only
+ applies when non-null.
+ '';
+ };
+
+ networking.dhcpcd.extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Literal string to append to the config file generated for dhcpcd.
+ '';
+ };
+
+ networking.dhcpcd.runHook = mkOption {
+ type = types.lines;
+ default = "";
+ example = "if [[ $reason =~ BOUND ]]; then echo $interface: Routers are $new_routers - were $old_routers; fi";
+ description = ''
+ Shell code that will be run after all other hooks. See
+ `man dhcpcd-run-hooks` for details on what is possible.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf enableDHCP {
+
+ systemd.services.dhcpcd = let
+ cfgN = config.networking;
+ hasDefaultGatewaySet = (cfgN.defaultGateway != null && cfgN.defaultGateway.address != "")
+ && (!cfgN.enableIPv6 || (cfgN.defaultGateway6 != null && cfgN.defaultGateway6.address != ""));
+ in
+ { description = "DHCP Client";
+
+ wantedBy = [ "multi-user.target" ] ++ optional (!hasDefaultGatewaySet) "network-online.target";
+ wants = [ "network.target" "systemd-udev-settle.service" ];
+ before = [ "network-online.target" ];
+ after = [ "systemd-udev-settle.service" ];
+
+ # Stopping dhcpcd during a reconfiguration is undesirable
+ # because it brings down the network interfaces configured by
+ # dhcpcd. So do a "systemctl restart" instead.
+ stopIfChanged = false;
+
+ path = [ dhcpcd pkgs.nettools pkgs.openresolv ];
+
+ unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+
+ serviceConfig =
+ { Type = "forking";
+ PIDFile = "/run/dhcpcd.pid";
+ ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd -w --quiet ${optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}";
+ ExecReload = "${dhcpcd}/sbin/dhcpcd --rebind";
+ Restart = "always";
+ };
+ };
+
+ environment.systemPackages = [ dhcpcd ];
+
+ environment.etc =
+ [ { source = exitHook;
+ target = "dhcpcd.exit-hook";
+ }
+ ];
+
+ powerManagement.resumeCommands = mkIf config.systemd.services.dhcpcd.enable
+ ''
+ # Tell dhcpcd to rebind its interfaces if it's running.
+ ${config.systemd.package}/bin/systemctl reload dhcpcd.service
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dhcpd.nix b/nixpkgs/nixos/modules/services/networking/dhcpd.nix
new file mode 100644
index 00000000000..0b2063bc424
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dhcpd.nix
@@ -0,0 +1,210 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg4 = config.services.dhcpd4;
+ cfg6 = config.services.dhcpd6;
+
+ writeConfig = cfg: pkgs.writeText "dhcpd.conf"
+ ''
+ default-lease-time 600;
+ max-lease-time 7200;
+ authoritative;
+ ddns-update-style interim;
+ log-facility local1; # see dhcpd.nix
+
+ ${cfg.extraConfig}
+
+ ${lib.concatMapStrings
+ (machine: ''
+ host ${machine.hostName} {
+ hardware ethernet ${machine.ethernetAddress};
+ fixed-address ${machine.ipAddress};
+ }
+ '')
+ cfg.machines
+ }
+ '';
+
+ dhcpdService = postfix: cfg: optionalAttrs cfg.enable {
+ "dhcpd${postfix}" = {
+ description = "DHCPv${postfix} server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ preStart = ''
+ mkdir -m 755 -p ${cfg.stateDir}
+ chown dhcpd:nogroup ${cfg.stateDir}
+ touch ${cfg.stateDir}/dhcpd.leases
+ '';
+
+ serviceConfig =
+ let
+ configFile = if cfg.configFile != null then cfg.configFile else writeConfig cfg;
+ args = [ "@${pkgs.dhcp}/sbin/dhcpd" "dhcpd${postfix}" "-${postfix}"
+ "-pf" "/run/dhcpd${postfix}/dhcpd.pid"
+ "-cf" "${configFile}"
+ "-lf" "${cfg.stateDir}/dhcpd.leases"
+ "-user" "dhcpd" "-group" "nogroup"
+ ] ++ cfg.extraFlags
+ ++ cfg.interfaces;
+
+ in {
+ ExecStart = concatMapStringsSep " " escapeShellArg args;
+ Type = "forking";
+ Restart = "always";
+ RuntimeDirectory = [ "dhcpd${postfix}" ];
+ PIDFile = "/run/dhcpd${postfix}/dhcpd.pid";
+ };
+ };
+ };
+
+ machineOpts = { ... }: {
+
+ options = {
+
+ hostName = mkOption {
+ type = types.str;
+ example = "foo";
+ description = ''
+ Hostname which is assigned statically to the machine.
+ '';
+ };
+
+ ethernetAddress = mkOption {
+ type = types.str;
+ example = "00:16:76:9a:32:1d";
+ description = ''
+ MAC address of the machine.
+ '';
+ };
+
+ ipAddress = mkOption {
+ type = types.str;
+ example = "192.168.1.10";
+ description = ''
+ IP address of the machine.
+ '';
+ };
+
+ };
+ };
+
+ dhcpConfig = postfix: {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the DHCPv${postfix} server.
+ '';
+ };
+
+ stateDir = mkOption {
+ type = types.path;
+ # We use /var/lib/dhcp for DHCPv4 to save backwards compatibility.
+ default = "/var/lib/dhcp${if postfix == "4" then "" else postfix}";
+ description = ''
+ State directory for the DHCP server.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ option subnet-mask 255.255.255.0;
+ option broadcast-address 192.168.1.255;
+ option routers 192.168.1.5;
+ option domain-name-servers 130.161.158.4, 130.161.33.17, 130.161.180.1;
+ option domain-name "example.org";
+ subnet 192.168.1.0 netmask 255.255.255.0 {
+ range 192.168.1.100 192.168.1.200;
+ }
+ '';
+ description = ''
+ Extra text to be appended to the DHCP server configuration
+ file. Currently, you almost certainly need to specify something
+ there, such as the options specifying the subnet mask, DNS servers,
+ etc.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Additional command line flags to be passed to the dhcpd daemon.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The path of the DHCP server configuration file. If no file
+ is specified, a file is generated using the other options.
+ '';
+ };
+
+ interfaces = mkOption {
+ type = types.listOf types.str;
+ default = ["eth0"];
+ description = ''
+ The interfaces on which the DHCP server should listen.
+ '';
+ };
+
+ machines = mkOption {
+ type = with types; listOf (submodule machineOpts);
+ default = [];
+ example = [
+ { hostName = "foo";
+ ethernetAddress = "00:16:76:9a:32:1d";
+ ipAddress = "192.168.1.10";
+ }
+ { hostName = "bar";
+ ethernetAddress = "00:19:d1:1d:c4:9a";
+ ipAddress = "192.168.1.11";
+ }
+ ];
+ description = ''
+ A list mapping Ethernet addresses to IPv${postfix} addresses for the
+ DHCP server.
+ '';
+ };
+
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.dhcpd4 = dhcpConfig "4";
+ services.dhcpd6 = dhcpConfig "6";
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf (cfg4.enable || cfg6.enable) {
+
+ users = {
+ users.dhcpd = {
+ uid = config.ids.uids.dhcpd;
+ description = "DHCP daemon user";
+ };
+ };
+
+ systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnscache.nix b/nixpkgs/nixos/modules/services/networking/dnscache.nix
new file mode 100644
index 00000000000..5051fc916d9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnscache.nix
@@ -0,0 +1,106 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dnscache;
+
+ dnscache-root = pkgs.runCommand "dnscache-root" { preferLocalBuild = true; } ''
+ mkdir -p $out/{servers,ip}
+
+ ${concatMapStrings (ip: ''
+ touch "$out/ip/"${lib.escapeShellArg ip}
+ '') cfg.clientIps}
+
+ ${concatStrings (mapAttrsToList (host: ips: ''
+ ${concatMapStrings (ip: ''
+ echo ${lib.escapeShellArg ip} >> "$out/servers/"${lib.escapeShellArg host}
+ '') ips}
+ '') cfg.domainServers)}
+
+ # if a list of root servers was not provided in config, copy it
+ # over. (this is also done by dnscache-conf, but we 'rm -rf
+ # /var/lib/dnscache/root' below & replace it wholesale with this,
+ # so we have to ensure servers/@ exists ourselves.)
+ if [ ! -e $out/servers/@ ]; then
+ # symlink does not work here, due chroot
+ cp ${pkgs.djbdns}/etc/dnsroots.global $out/servers/@;
+ fi
+ '';
+
+in {
+
+ ###### interface
+
+ options = {
+ services.dnscache = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to run the dnscache caching dns server.";
+ };
+
+ ip = mkOption {
+ default = "0.0.0.0";
+ type = types.str;
+ description = "IP address on which to listen for connections.";
+ };
+
+ clientIps = mkOption {
+ default = [ "127.0.0.1" ];
+ type = types.listOf types.str;
+ description = "Client IP addresses (or prefixes) from which to accept connections.";
+ example = ["192.168" "172.23.75.82"];
+ };
+
+ domainServers = mkOption {
+ default = { };
+ type = types.attrsOf (types.listOf types.str);
+ description = ''
+ Table of {hostname: server} pairs to use as authoritative servers for hosts (and subhosts).
+ If entry for @ is not specified predefined list of root servers is used.
+ '';
+ example = {
+ "@" = ["8.8.8.8" "8.8.4.4"];
+ "example.com" = ["192.168.100.100"];
+ };
+ };
+
+ forwardOnly = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to treat root servers (for @) as caching
+ servers, requesting addresses the same way a client does. This is
+ needed if you want to use e.g. Google DNS as your upstream DNS.
+ '';
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.dnscache.enable {
+ environment.systemPackages = [ pkgs.djbdns ];
+ users.users.dnscache = {};
+
+ systemd.services.dnscache = {
+ description = "djbdns dnscache server";
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [ bash daemontools djbdns ];
+ preStart = ''
+ rm -rf /var/lib/dnscache
+ dnscache-conf dnscache dnscache /var/lib/dnscache ${config.services.dnscache.ip}
+ rm -rf /var/lib/dnscache/root
+ ln -sf ${dnscache-root} /var/lib/dnscache/root
+ '';
+ script = ''
+ cd /var/lib/dnscache/
+ ${optionalString cfg.forwardOnly "export FORWARDONLY=1"}
+ exec ./run
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnschain.nix b/nixpkgs/nixos/modules/services/networking/dnschain.nix
new file mode 100644
index 00000000000..5b58ea9b0c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnschain.nix
@@ -0,0 +1,183 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfgs = config.services;
+ cfg = cfgs.dnschain;
+
+ dataDir = "/var/lib/dnschain";
+ username = "dnschain";
+
+ configFile = pkgs.writeText "dnschain.conf" ''
+ [log]
+ level = info
+
+ [dns]
+ host = ${cfg.dns.address}
+ port = ${toString cfg.dns.port}
+ oldDNSMethod = NO_OLD_DNS
+ externalIP = ${cfg.dns.externalAddress}
+
+ [http]
+ host = ${cfg.api.hostname}
+ port = ${toString cfg.api.port}
+ tlsPort = ${toString cfg.api.tlsPort}
+
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.dnschain = {
+
+ enable = mkEnableOption ''
+ DNSChain, a blockchain based DNS + HTTP server.
+ To resolve .bit domains set <literal>services.namecoind.enable = true;</literal>
+ and an RPC username/password.
+ '';
+
+ dns.address = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ The IP address the DNSChain resolver will bind to.
+ Leave this unchanged if you do not wish to directly expose the resolver.
+ '';
+ };
+
+ dns.externalAddress = mkOption {
+ type = types.str;
+ default = cfg.dns.address;
+ description = ''
+ The IP address used by clients to reach the resolver and the value of
+ the <literal>namecoin.dns</literal> record. Set this in case the bind address
+ is not the actual IP address (e.g. the machine is behind a NAT).
+ '';
+ };
+
+ dns.port = mkOption {
+ type = types.int;
+ default = 5333;
+ description = ''
+ The port the DNSChain resolver will bind to.
+ '';
+ };
+
+ api.hostname = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ The hostname (or IP address) the DNSChain API server will bind to.
+ '';
+ };
+
+ api.port = mkOption {
+ type = types.int;
+ default = 8080;
+ description = ''
+ The port the DNSChain API server (HTTP) will bind to.
+ '';
+ };
+
+ api.tlsPort = mkOption {
+ type = types.int;
+ default = 4433;
+ description = ''
+ The port the DNSChain API server (HTTPS) will bind to.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ [log]
+ level = debug
+ '';
+ description = ''
+ Additional options that will be appended to the configuration file.
+ '';
+ };
+
+ };
+
+ services.dnsmasq.resolveDNSChainQueries = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Resolve <literal>.bit</literal> top-level domains using DNSChain and namecoin.
+ '';
+ };
+
+ services.pdns-recursor.resolveDNSChainQueries = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Resolve <literal>.bit</literal> top-level domains using DNSChain and namecoin.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.dnsmasq.servers = optionals cfgs.dnsmasq.resolveDNSChainQueries
+ [ "/.bit/127.0.0.1#${toString cfg.dns.port}"
+ "/.dns/127.0.0.1#${toString cfg.dns.port}"
+ ];
+
+ services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveDNSChainQueries {
+ forwardZones =
+ { bit = "127.0.0.1:${toString cfg.dns.port}";
+ dns = "127.0.0.1:${toString cfg.dns.port}";
+ };
+ luaConfig =''
+ addNTA("bit", "namecoin doesn't support DNSSEC")
+ addNTA("dns", "namecoin doesn't support DNSSEC")
+ '';
+ };
+
+ users.users = singleton {
+ name = username;
+ description = "DNSChain daemon user";
+ home = dataDir;
+ createHome = true;
+ uid = config.ids.uids.dnschain;
+ extraGroups = optional cfgs.namecoind.enable "namecoin";
+ };
+
+ systemd.services.dnschain = {
+ description = "DNSChain daemon";
+ after = optional cfgs.namecoind.enable "namecoind.target";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = "dnschain";
+ Restart = "on-failure";
+ ExecStart = "${pkgs.nodePackages.dnschain}/bin/dnschain";
+ };
+
+ preStart = ''
+ # Link configuration file into dnschain home directory
+ configPath=${dataDir}/.dnschain/dnschain.conf
+ mkdir -p ${dataDir}/.dnschain
+ if [ "$(realpath $configPath)" != "${configFile}" ]; then
+ rm -f $configPath
+ ln -s ${configFile} $configPath
+ fi
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.nix b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.nix
new file mode 100644
index 00000000000..8edcf925dbf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.nix
@@ -0,0 +1,328 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+ cfg = config.services.dnscrypt-proxy;
+
+ stateDirectory = "/var/lib/dnscrypt-proxy";
+
+ # The minisign public key used to sign the upstream resolver list.
+ # This is somewhat more flexible than preloading the key as an
+ # embedded string.
+ upstreamResolverListPubKey = pkgs.fetchurl {
+ url = https://raw.githubusercontent.com/dyne/dnscrypt-proxy/master/minisign.pub;
+ sha256 = "18lnp8qr6ghfc2sd46nn1rhcpr324fqlvgsp4zaigw396cd7vnnh";
+ };
+
+ # Internal flag indicating whether the upstream resolver list is used.
+ useUpstreamResolverList = cfg.customResolver == null;
+
+ # The final local address.
+ localAddress = "${cfg.localAddress}:${toString cfg.localPort}";
+
+ # The final resolvers list path.
+ resolverList = "${stateDirectory}/dnscrypt-resolvers.csv";
+
+ # Build daemon command line
+
+ resolverArgs =
+ if (cfg.customResolver == null)
+ then
+ [ "-L ${resolverList}"
+ "-R ${cfg.resolverName}"
+ ]
+ else with cfg.customResolver;
+ [ "-N ${name}"
+ "-k ${key}"
+ "-r ${address}:${toString port}"
+ ];
+
+ daemonArgs =
+ [ "-a ${localAddress}" ]
+ ++ resolverArgs
+ ++ cfg.extraArgs;
+in
+
+{
+ meta = {
+ maintainers = with maintainers; [ joachifm ];
+ doc = ./dnscrypt-proxy.xml;
+ };
+
+ options = {
+ # Before adding another option, consider whether it could
+ # equally well be passed via extraArgs.
+
+ services.dnscrypt-proxy = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to enable the DNSCrypt client proxy";
+ };
+
+ localAddress = mkOption {
+ default = "127.0.0.1";
+ type = types.str;
+ description = ''
+ Listen for DNS queries to relay on this address. The only reason to
+ change this from its default value is to proxy queries on behalf
+ of other machines (typically on the local network).
+ '';
+ };
+
+ localPort = mkOption {
+ default = 53;
+ type = types.int;
+ description = ''
+ Listen for DNS queries to relay on this port. The default value
+ assumes that the DNSCrypt proxy should relay DNS queries directly.
+ When running as a forwarder for another DNS client, set this option
+ to a different value; otherwise leave the default.
+ '';
+ };
+
+ resolverName = mkOption {
+ default = "random";
+ example = "dnscrypt.eu-nl";
+ type = types.nullOr types.str;
+ description = ''
+ The name of the DNSCrypt resolver to use, taken from
+ <filename>${resolverList}</filename>. The default is to
+ pick a random non-logging resolver that supports DNSSEC.
+ '';
+ };
+
+ customResolver = mkOption {
+ default = null;
+ description = ''
+ Use an unlisted resolver (e.g., a private DNSCrypt provider). For
+ advanced users only. If specified, this option takes precedence.
+ '';
+ type = types.nullOr (types.submodule ({ ... }: { options = {
+ address = mkOption {
+ type = types.str;
+ description = "IP address";
+ example = "208.67.220.220";
+ };
+
+ port = mkOption {
+ type = types.int;
+ description = "Port";
+ default = 443;
+ };
+
+ name = mkOption {
+ type = types.str;
+ description = "Fully qualified domain name";
+ example = "2.dnscrypt-cert.example.com";
+ };
+
+ key = mkOption {
+ type = types.str;
+ description = "Public key";
+ example = "B735:1140:206F:225D:3E2B:D822:D7FD:691E:A1C3:3CC8:D666:8D0C:BE04:BFAB:CA43:FB79";
+ };
+ }; }));
+ };
+
+ extraArgs = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ Additional command-line arguments passed verbatim to the daemon.
+ See <citerefentry><refentrytitle>dnscrypt-proxy</refentrytitle>
+ <manvolnum>8</manvolnum></citerefentry> for details.
+ '';
+ example = [ "-X libdcplugin_example_cache.so,--min-ttl=60" ];
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [{
+ assertions = [
+ { assertion = (cfg.customResolver != null) || (cfg.resolverName != null);
+ message = "please configure upstream DNSCrypt resolver";
+ }
+ ];
+
+ # make man 8 dnscrypt-proxy work
+ environment.systemPackages = [ pkgs.dnscrypt-proxy ];
+
+ users.users.dnscrypt-proxy = {
+ description = "dnscrypt-proxy daemon user";
+ isSystemUser = true;
+ group = "dnscrypt-proxy";
+ };
+ users.groups.dnscrypt-proxy = {};
+
+ systemd.sockets.dnscrypt-proxy = {
+ description = "dnscrypt-proxy listening socket";
+ documentation = [ "man:dnscrypt-proxy(8)" ];
+
+ wantedBy = [ "sockets.target" ];
+
+ socketConfig = {
+ ListenStream = localAddress;
+ ListenDatagram = localAddress;
+ };
+ };
+
+ systemd.services.dnscrypt-proxy = {
+ description = "dnscrypt-proxy daemon";
+ documentation = [ "man:dnscrypt-proxy(8)" ];
+
+ before = [ "nss-lookup.target" ];
+ after = [ "network.target" ];
+ requires = [ "dnscrypt-proxy.socket "];
+
+ serviceConfig = {
+ NonBlocking = "true";
+ ExecStart = "${pkgs.dnscrypt-proxy}/bin/dnscrypt-proxy ${toString daemonArgs}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+
+ User = "dnscrypt-proxy";
+
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectHome = true;
+ };
+ };
+ }
+
+ (mkIf config.security.apparmor.enable {
+ systemd.services.dnscrypt-proxy.after = [ "apparmor.service" ];
+
+ security.apparmor.profiles = singleton (pkgs.writeText "apparmor-dnscrypt-proxy" ''
+ ${pkgs.dnscrypt-proxy}/bin/dnscrypt-proxy {
+ /dev/null rw,
+ /dev/random r,
+ /dev/urandom r,
+
+ /etc/passwd r,
+ /etc/group r,
+ ${config.environment.etc."nsswitch.conf".source} r,
+
+ ${getLib pkgs.glibc}/lib/*.so mr,
+ ${pkgs.tzdata}/share/zoneinfo/** r,
+
+ network inet stream,
+ network inet6 stream,
+ network inet dgram,
+ network inet6 dgram,
+
+ ${getLib pkgs.dnscrypt-proxy}/lib/dnscrypt-proxy/libdcplugin*.so mr,
+
+ ${getLib pkgs.gcc.cc}/lib/libssp.so.* mr,
+ ${getLib pkgs.libsodium}/lib/libsodium.so.* mr,
+ ${getLib pkgs.systemd}/lib/libsystemd.so.* mr,
+ ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr,
+ ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr,
+ ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr,
+ ${getLib pkgs.xz}/lib/liblzma.so.* mr,
+ ${getLib pkgs.libgcrypt}/lib/libgcrypt.so.* mr,
+ ${getLib pkgs.libgpgerror}/lib/libgpg-error.so.* mr,
+ ${getLib pkgs.libcap}/lib/libcap.so.* mr,
+ ${getLib pkgs.lz4}/lib/liblz4.so.* mr,
+ ${getLib pkgs.attr}/lib/libattr.so.* mr, # */
+
+ ${resolverList} r,
+
+ /run/systemd/notify rw,
+ }
+ '');
+ })
+
+ (mkIf useUpstreamResolverList {
+ systemd.services.init-dnscrypt-proxy-statedir = {
+ description = "Initialize dnscrypt-proxy state directory";
+
+ wantedBy = [ "dnscrypt-proxy.service" ];
+ before = [ "dnscrypt-proxy.service" ];
+
+ script = ''
+ mkdir -pv ${stateDirectory}
+ chown -c dnscrypt-proxy:dnscrypt-proxy ${stateDirectory}
+ cp -uv \
+ ${pkgs.dnscrypt-proxy}/share/dnscrypt-proxy/dnscrypt-resolvers.csv \
+ ${stateDirectory}
+ '';
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ };
+
+ systemd.services.update-dnscrypt-resolvers = {
+ description = "Update list of DNSCrypt resolvers";
+
+ requires = [ "init-dnscrypt-proxy-statedir.service" ];
+ after = [ "init-dnscrypt-proxy-statedir.service" ];
+
+ path = with pkgs; [ curl diffutils dnscrypt-proxy minisign ];
+ script = ''
+ cd ${stateDirectory}
+ domain=raw.githubusercontent.com
+ get="curl -fSs --resolve $domain:443:$(hostip -r 8.8.8.8 $domain | head -1)"
+ $get -o dnscrypt-resolvers.csv.tmp \
+ https://$domain/dyne/dnscrypt-proxy/master/dnscrypt-resolvers.csv
+ $get -o dnscrypt-resolvers.csv.minisig.tmp \
+ https://$domain/dyne/dnscrypt-proxy/master/dnscrypt-resolvers.csv.minisig
+ mv dnscrypt-resolvers.csv.minisig{.tmp,}
+ if ! minisign -q -V -p ${upstreamResolverListPubKey} \
+ -m dnscrypt-resolvers.csv.tmp -x dnscrypt-resolvers.csv.minisig ; then
+ echo "failed to verify resolver list!" >&2
+ exit 1
+ fi
+ [[ -f dnscrypt-resolvers.csv ]] && mv dnscrypt-resolvers.csv{,.old}
+ mv dnscrypt-resolvers.csv{.tmp,}
+ if cmp dnscrypt-resolvers.csv{,.old} ; then
+ echo "no change"
+ else
+ echo "resolver list updated"
+ fi
+ '';
+
+ serviceConfig = {
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectHome = true;
+ ProtectSystem = "strict";
+ ReadWritePaths = "${dirOf stateDirectory} ${stateDirectory}";
+ SystemCallFilter = "~@mount";
+ };
+ };
+
+ systemd.timers.update-dnscrypt-resolvers = {
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnBootSec = "5min";
+ OnUnitActiveSec = "6h";
+ };
+ };
+ })
+ ]);
+
+ imports = [
+ (mkRenamedOptionModule [ "services" "dnscrypt-proxy" "port" ] [ "services" "dnscrypt-proxy" "localPort" ])
+
+ (mkChangedOptionModule
+ [ "services" "dnscrypt-proxy" "tcpOnly" ]
+ [ "services" "dnscrypt-proxy" "extraArgs" ]
+ (config:
+ let val = getAttrFromPath [ "services" "dnscrypt-proxy" "tcpOnly" ] config; in
+ optional val "-T"))
+
+ (mkChangedOptionModule
+ [ "services" "dnscrypt-proxy" "ephemeralKeys" ]
+ [ "services" "dnscrypt-proxy" "extraArgs" ]
+ (config:
+ let val = getAttrFromPath [ "services" "dnscrypt-proxy" "ephemeralKeys" ] config; in
+ optional val "-E"))
+
+ (mkRemovedOptionModule [ "services" "dnscrypt-proxy" "resolverList" ] ''
+ The current resolver listing from upstream is always used
+ unless a custom resolver is specified.
+ '')
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.xml b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.xml
new file mode 100644
index 00000000000..afc7880392a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.xml
@@ -0,0 +1,66 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="sec-dnscrypt-proxy">
+ <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>
+ 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>.
+ </para>
+ </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>
+<xref linkend="opt-services.dnscrypt-proxy.localPort"/> = 43;
+</programlisting>
+ </para>
+
+ <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>
+ </sect2>
+
+ <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>
+ </sect2>
+ </sect1>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix b/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix
new file mode 100644
index 00000000000..bf13d5c6f5f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix
@@ -0,0 +1,199 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+ cfg = config.services.dnscrypt-wrapper;
+ dataDir = "/var/lib/dnscrypt-wrapper";
+
+ daemonArgs = with cfg; [
+ "--listen-address=${address}:${toString port}"
+ "--resolver-address=${upstream.address}:${toString upstream.port}"
+ "--provider-name=${providerName}"
+ "--provider-publickey-file=public.key"
+ "--provider-secretkey-file=secret.key"
+ "--provider-cert-file=${providerName}.crt"
+ "--crypt-secretkey-file=${providerName}.key"
+ ];
+
+ genKeys = ''
+ # generates time-limited keypairs
+ keyGen() {
+ dnscrypt-wrapper --gen-crypt-keypair \
+ --crypt-secretkey-file=${cfg.providerName}.key
+
+ dnscrypt-wrapper --gen-cert-file \
+ --crypt-secretkey-file=${cfg.providerName}.key \
+ --provider-cert-file=${cfg.providerName}.crt \
+ --provider-publickey-file=public.key \
+ --provider-secretkey-file=secret.key \
+ --cert-file-expire-days=${toString cfg.keys.expiration}
+ }
+
+ cd ${dataDir}
+
+ # generate provider keypair (first run only)
+ if [ ! -f public.key ] || [ ! -f secret.key ]; then
+ dnscrypt-wrapper --gen-provider-keypair
+ fi
+
+ # generate new keys for rotation
+ if [ ! -f ${cfg.providerName}.key ] || [ ! -f ${cfg.providerName}.crt ]; then
+ keyGen
+ fi
+ '';
+
+ rotateKeys = ''
+ # check if keys are not expired
+ keyValid() {
+ fingerprint=$(dnscrypt-wrapper --show-provider-publickey | awk '{print $(NF)}')
+ dnscrypt-proxy --test=${toString (cfg.keys.checkInterval + 1)} \
+ --resolver-address=127.0.0.1:${toString cfg.port} \
+ --provider-name=${cfg.providerName} \
+ --provider-key=$fingerprint
+ }
+
+ cd ${dataDir}
+
+ # archive old keys and restart the service
+ if ! keyValid; then
+ echo "certificate soon to become invalid; backing up old cert"
+ mkdir -p oldkeys
+ mv -v ${cfg.providerName}.key oldkeys/${cfg.providerName}-$(date +%F-%T).key
+ mv -v ${cfg.providerName}.crt oldkeys/${cfg.providerName}-$(date +%F-%T).crt
+ systemctl restart dnscrypt-wrapper
+ fi
+ '';
+
+in {
+
+
+ ###### interface
+
+ options.services.dnscrypt-wrapper = {
+ enable = mkEnableOption "DNSCrypt wrapper";
+
+ address = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ The DNSCrypt wrapper will bind to this IP address.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 5353;
+ description = ''
+ The DNSCrypt wrapper will listen for DNS queries on this port.
+ '';
+ };
+
+ providerName = mkOption {
+ type = types.str;
+ default = "2.dnscrypt-cert.${config.networking.hostName}";
+ example = "2.dnscrypt-cert.myresolver";
+ description = ''
+ The name that will be given to this DNSCrypt resolver.
+ Note: the resolver name must start with <literal>2.dnscrypt-cert.</literal>.
+ '';
+ };
+
+ upstream.address = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ The IP address of the upstream DNS server DNSCrypt will "wrap".
+ '';
+ };
+
+ upstream.port = mkOption {
+ type = types.int;
+ default = 53;
+ description = ''
+ The port of the upstream DNS server DNSCrypt will "wrap".
+ '';
+ };
+
+ keys.expiration = mkOption {
+ type = types.int;
+ default = 30;
+ description = ''
+ The duration (in days) of the time-limited secret key.
+ This will be automatically rotated before expiration.
+ '';
+ };
+
+ keys.checkInterval = mkOption {
+ type = types.int;
+ default = 1440;
+ description = ''
+ The time interval (in minutes) between key expiration checks.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users.dnscrypt-wrapper = {
+ description = "dnscrypt-wrapper daemon user";
+ home = "${dataDir}";
+ createHome = true;
+ };
+ users.groups.dnscrypt-wrapper = { };
+
+ security.polkit.extraConfig = ''
+ // Allow dnscrypt-wrapper user to restart dnscrypt-wrapper.service
+ polkit.addRule(function(action, subject) {
+ if (action.id == "org.freedesktop.systemd1.manage-units" &&
+ action.lookup("unit") == "dnscrypt-wrapper.service" &&
+ subject.user == "dnscrypt-wrapper") {
+ return polkit.Result.YES;
+ }
+ });
+ '';
+
+ systemd.services.dnscrypt-wrapper = {
+ description = "dnscrypt-wrapper daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.dnscrypt-wrapper ];
+
+ serviceConfig = {
+ User = "dnscrypt-wrapper";
+ WorkingDirectory = dataDir;
+ Restart = "on-failure";
+ ExecStart = "${pkgs.dnscrypt-wrapper}/bin/dnscrypt-wrapper ${toString daemonArgs}";
+ };
+
+ preStart = genKeys;
+ };
+
+
+ systemd.services.dnscrypt-wrapper-rotate = {
+ after = [ "network.target" ];
+ requires = [ "dnscrypt-wrapper.service" ];
+ description = "Rotates DNSCrypt wrapper keys if soon to expire";
+
+ path = with pkgs; [ dnscrypt-wrapper dnscrypt-proxy gawk ];
+ script = rotateKeys;
+ serviceConfig.User = "dnscrypt-wrapper";
+ };
+
+
+ systemd.timers.dnscrypt-wrapper-rotate = {
+ description = "Periodically check DNSCrypt wrapper keys for expiration";
+ wantedBy = [ "multi-user.target" ];
+
+ timerConfig = {
+ Unit = "dnscrypt-wrapper-rotate.service";
+ OnBootSec = "1min";
+ OnUnitActiveSec = cfg.keys.checkInterval * 60;
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnsdist.nix b/nixpkgs/nixos/modules/services/networking/dnsdist.nix
new file mode 100644
index 00000000000..12eee136e63
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnsdist.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dnsdist;
+ configFile = pkgs.writeText "dndist.conf" ''
+ setLocal('${cfg.listenAddress}:${toString cfg.listenPort}')
+ ${cfg.extraConfig}
+ '';
+in {
+ options = {
+ services.dnsdist = {
+ enable = mkEnableOption "dnsdist domain name server";
+
+ listenAddress = mkOption {
+ type = types.str;
+ description = "Listen IP Address";
+ default = "0.0.0.0";
+ };
+ listenPort = mkOption {
+ type = types.int;
+ description = "Listen port";
+ default = 53;
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = ''
+ '';
+ description = ''
+ Extra lines to be added verbatim to dnsdist.conf.
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.dnsdist.enable {
+ systemd.services.dnsdist = {
+ description = "dnsdist load balancer";
+ wantedBy = [ "multi-user.target" ];
+ after = ["network.target"];
+
+ serviceConfig = {
+ Restart="on-failure";
+ RestartSec="1";
+ DynamicUser = true;
+ StartLimitInterval="0";
+ PrivateTmp=true;
+ PrivateDevices=true;
+ CapabilityBoundingSet="CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID";
+ ExecStart = "${pkgs.dnsdist}/bin/dnsdist --supervised --disable-syslog --config ${configFile}";
+ ProtectSystem="full";
+ ProtectHome=true;
+ RestrictAddressFamilies="AF_UNIX AF_INET AF_INET6";
+ LimitNOFILE="16384";
+ TasksMax="8192";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnsmasq.nix b/nixpkgs/nixos/modules/services/networking/dnsmasq.nix
new file mode 100644
index 00000000000..714a5903bff
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnsmasq.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dnsmasq;
+ dnsmasq = pkgs.dnsmasq;
+ stateDir = "/var/lib/dnsmasq";
+
+ dnsmasqConf = pkgs.writeText "dnsmasq.conf" ''
+ dhcp-leasefile=${stateDir}/dnsmasq.leases
+ ${optionalString cfg.resolveLocalQueries ''
+ conf-file=/etc/dnsmasq-conf.conf
+ resolv-file=/etc/dnsmasq-resolv.conf
+ ''}
+ ${flip concatMapStrings cfg.servers (server: ''
+ server=${server}
+ '')}
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.dnsmasq = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run dnsmasq.
+ '';
+ };
+
+ resolveLocalQueries = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether dnsmasq should resolve local queries (i.e. add 127.0.0.1 to
+ /etc/resolv.conf).
+ '';
+ };
+
+ servers = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "8.8.8.8" "8.8.4.4" ];
+ description = ''
+ The DNS servers which dnsmasq should query.
+ '';
+ };
+
+ alwaysKeepRunning = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, systemd will always respawn dnsmasq even if shut down manually. The default, disabled, will only restart it on error.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration directives that should be added to
+ <literal>dnsmasq.conf</literal>.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ networking.nameservers =
+ optional cfg.resolveLocalQueries "127.0.0.1";
+
+ services.dbus.packages = [ dnsmasq ];
+
+ users.users = singleton {
+ name = "dnsmasq";
+ uid = config.ids.uids.dnsmasq;
+ description = "Dnsmasq daemon user";
+ };
+
+ networking.resolvconf = mkIf cfg.resolveLocalQueries {
+ useLocalResolver = mkDefault true;
+
+ extraConfig = ''
+ dnsmasq_conf=/etc/dnsmasq-conf.conf
+ dnsmasq_resolv=/etc/dnsmasq-resolv.conf
+ '';
+ };
+
+ systemd.services.dnsmasq = {
+ description = "Dnsmasq Daemon";
+ after = [ "network.target" "systemd-resolved.service" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ dnsmasq ];
+ preStart = ''
+ mkdir -m 755 -p ${stateDir}
+ touch ${stateDir}/dnsmasq.leases
+ chown -R dnsmasq ${stateDir}
+ touch /etc/dnsmasq-{conf,resolv}.conf
+ dnsmasq --test
+ '';
+ serviceConfig = {
+ Type = "dbus";
+ BusName = "uk.org.thekelleys.dnsmasq";
+ ExecStart = "${dnsmasq}/bin/dnsmasq -k --enable-dbus --user=dnsmasq -C ${dnsmasqConf}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ PrivateTmp = true;
+ ProtectSystem = true;
+ ProtectHome = true;
+ Restart = if cfg.alwaysKeepRunning then "always" else "on-failure";
+ };
+ restartTriggers = [ config.environment.etc.hosts.source ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ejabberd.nix b/nixpkgs/nixos/modules/services/networking/ejabberd.nix
new file mode 100644
index 00000000000..6a38f85c48a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ejabberd.nix
@@ -0,0 +1,157 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.ejabberd;
+
+ ctlcfg = pkgs.writeText "ejabberdctl.cfg" ''
+ ERL_EPMD_ADDRESS=127.0.0.1
+ ${cfg.ctlConfig}
+ '';
+
+ ectl = ''${cfg.package}/bin/ejabberdctl ${optionalString (cfg.configFile != null) "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"'';
+
+ dumps = lib.escapeShellArgs cfg.loadDumps;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.ejabberd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable ejabberd server";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.ejabberd;
+ defaultText = "pkgs.ejabberd";
+ description = "ejabberd server package to use";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "ejabberd";
+ description = "User under which ejabberd is ran";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "ejabberd";
+ description = "Group under which ejabberd is ran";
+ };
+
+ spoolDir = mkOption {
+ type = types.path;
+ default = "/var/lib/ejabberd";
+ description = "Location of the spooldir of ejabberd";
+ };
+
+ logsDir = mkOption {
+ type = types.path;
+ default = "/var/log/ejabberd";
+ description = "Location of the logfile directory of ejabberd";
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ description = "Configuration file for ejabberd in YAML format";
+ default = null;
+ };
+
+ ctlConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Configuration of ejabberdctl";
+ };
+
+ loadDumps = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = "Configuration dumps that should be loaded on the first startup";
+ example = literalExample "[ ./myejabberd.dump ]";
+ };
+
+ imagemagick = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Add ImageMagick to server's path; allows for image thumbnailing";
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ cfg.package ];
+
+ users.users = optionalAttrs (cfg.user == "ejabberd") (singleton
+ { name = "ejabberd";
+ group = cfg.group;
+ home = cfg.spoolDir;
+ createHome = true;
+ uid = config.ids.uids.ejabberd;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "ejabberd") (singleton
+ { name = "ejabberd";
+ gid = config.ids.gids.ejabberd;
+ });
+
+ systemd.services.ejabberd = {
+ description = "ejabberd server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = [ pkgs.findutils pkgs.coreutils ] ++ lib.optional cfg.imagemagick pkgs.imagemagick;
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${ectl} foreground";
+ ExecStop = "${ectl} stop";
+ ExecReload = "${ectl} reload_config";
+ };
+
+ preStart = ''
+ if [ -z "$(ls -A '${cfg.spoolDir}')" ]; then
+ touch "${cfg.spoolDir}/.firstRun"
+ fi
+ '';
+
+ postStart = ''
+ while ! ${ectl} status >/dev/null 2>&1; do
+ if ! kill -0 "$MAINPID"; then exit 1; fi
+ sleep 0.1
+ done
+
+ if [ -e "${cfg.spoolDir}/.firstRun" ]; then
+ rm "${cfg.spoolDir}/.firstRun"
+ for src in ${dumps}; do
+ find "$src" -type f | while read dump; do
+ echo "Loading configuration dump at $dump"
+ ${ectl} load "$dump"
+ done
+ done
+ fi
+ '';
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.logsDir}' 0750 ${cfg.user} ${cfg.group} -"
+ "d '${cfg.spoolDir}' 0700 ${cfg.user} ${cfg.group} -"
+ ];
+
+ security.pam.services.ejabberd = {};
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/epmd.nix b/nixpkgs/nixos/modules/services/networking/epmd.nix
new file mode 100644
index 00000000000..692b75e4f08
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/epmd.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.epmd;
+
+in
+
+{
+ ###### interface
+ options.services.epmd = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable socket activation for Erlang Port Mapper Daemon (epmd),
+ which acts as a name server on all hosts involved in distributed
+ Erlang computations.
+ '';
+ };
+ package = mkOption {
+ type = types.package;
+ default = pkgs.erlang;
+ description = ''
+ The Erlang package to use to get epmd binary. That way you can re-use
+ an Erlang runtime that is already installed for other purposes.
+ '';
+ };
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ systemd.sockets.epmd = rec {
+ description = "Erlang Port Mapper Daemon Activation Socket";
+ wantedBy = [ "sockets.target" ];
+ before = wantedBy;
+ socketConfig = {
+ ListenStream = "4369";
+ Accept = "false";
+ };
+ };
+
+ systemd.services.epmd = {
+ description = "Erlang Port Mapper Daemon";
+ after = [ "network.target" ];
+ requires = [ "epmd.socket" ];
+
+ serviceConfig = {
+ DynamicUser = true;
+ ExecStart = "${cfg.package}/bin/epmd -systemd";
+ Type = "notify";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix b/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix
new file mode 100644
index 00000000000..be7337ece7e
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/networking/fakeroute.nix b/nixpkgs/nixos/modules/services/networking/fakeroute.nix
new file mode 100644
index 00000000000..82a9fb729d8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/fakeroute.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.fakeroute;
+ routeConf = pkgs.writeText "route.conf" (concatStringsSep "\n" cfg.route);
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.fakeroute = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the fakeroute service.
+ '';
+ };
+
+ route = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [
+ "216.102.187.130"
+ "4.0.1.122"
+ "198.116.142.34"
+ "63.199.8.242"
+ ];
+ description = ''
+ Fake route that will appear after the real
+ one to any host running a traceroute.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.fakeroute = {
+ description = "Fakeroute Daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "forking";
+ User = "root";
+ ExecStart = "${pkgs.fakeroute}/bin/fakeroute -f ${routeConf}";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ferm.nix b/nixpkgs/nixos/modules/services/networking/ferm.nix
new file mode 100644
index 00000000000..07338ccf4d9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ferm.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.ferm;
+
+ configFile = pkgs.stdenv.mkDerivation {
+ name = "ferm.conf";
+ text = cfg.config;
+ preferLocalBuild = true;
+ buildCommand = ''
+ echo -n "$text" > $out
+ ${cfg.package}/bin/ferm --noexec $out
+ '';
+ };
+in {
+ options = {
+ services.ferm = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable Ferm Firewall.
+ *Warning*: Enabling this service WILL disable the existing NixOS
+ firewall! Default firewall rules provided by packages are not
+ considered at the moment.
+ '';
+ };
+ config = mkOption {
+ description = "Verbatim ferm.conf configuration.";
+ default = "";
+ defaultText = "empty firewall, allows any traffic";
+ type = types.lines;
+ };
+ package = mkOption {
+ description = "The ferm package.";
+ type = types.package;
+ default = pkgs.ferm;
+ defaultText = "pkgs.ferm";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.firewall.enable = false;
+ systemd.services.ferm = {
+ description = "Ferm Firewall";
+ after = [ "ipset.target" ];
+ before = [ "network-pre.target" ];
+ wants = [ "network-pre.target" ];
+ wantedBy = [ "multi-user.target" ];
+ reloadIfChanged = true;
+ serviceConfig = {
+ Type="oneshot";
+ RemainAfterExit = "yes";
+ ExecStart = "${cfg.package}/bin/ferm ${configFile}";
+ ExecReload = "${cfg.package}/bin/ferm ${configFile}";
+ ExecStop = "${cfg.package}/bin/ferm -F ${configFile}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix b/nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix
new file mode 100644
index 00000000000..6842aa73561
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix
@@ -0,0 +1,183 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.firefox.syncserver;
+
+ defaultDbLocation = "/var/db/firefox-sync-server/firefox-sync-server.db";
+ defaultSqlUri = "sqlite:///${defaultDbLocation}";
+
+ syncServerIni = pkgs.writeText "syncserver.ini" ''
+ [DEFAULT]
+ overrides = ${cfg.privateConfig}
+
+ [server:main]
+ use = egg:gunicorn
+ host = ${cfg.listen.address}
+ port = ${toString cfg.listen.port}
+
+ [app:main]
+ use = egg:syncserver
+
+ [syncserver]
+ public_url = ${cfg.publicUrl}
+ ${optionalString (cfg.sqlUri != "") "sqluri = ${cfg.sqlUri}"}
+ allow_new_users = ${boolToString cfg.allowNewUsers}
+
+ [browserid]
+ backend = tokenserver.verifiers.LocalVerifier
+ audiences = ${removeSuffix "/" cfg.publicUrl}
+ '';
+
+ user = "syncserver";
+ group = "syncserver";
+in
+
+{
+ meta.maintainers = with lib.maintainers; [ nadrieril ];
+
+ options = {
+ services.firefox.syncserver = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable a Firefox Sync Server, this give the opportunity to
+ Firefox users to store all synchronized data on their own server. To use this
+ server, Firefox users should visit the <option>about:config</option>, and
+ replicate the following change
+
+ <screen>
+ services.sync.tokenServerURI: http://localhost:5000/token/1.0/sync/1.5
+ </screen>
+
+ where <option>http://localhost:5000/</option> corresponds to the
+ public url of the server.
+ '';
+ };
+
+ listen.address = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ example = "0.0.0.0";
+ description = ''
+ Address on which the sync server listen to.
+ '';
+ };
+
+ listen.port = mkOption {
+ type = types.int;
+ default = 5000;
+ description = ''
+ Port on which the sync server listen to.
+ '';
+ };
+
+ publicUrl = mkOption {
+ type = types.str;
+ default = "http://localhost:5000/";
+ example = "http://sync.example.com/";
+ description = ''
+ Public URL with which firefox users can use to access the sync server.
+ '';
+ };
+
+ allowNewUsers = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to allow new-user signups on the server. Only request by
+ existing accounts will be honored.
+ '';
+ };
+
+ sqlUri = mkOption {
+ type = types.str;
+ default = defaultSqlUri;
+ example = "postgresql://scott:tiger@localhost/test";
+ description = ''
+ The location of the database. This URL is composed of
+ <option>dialect[+driver]://user:password@host/dbname[?key=value..]</option>,
+ where <option>dialect</option> is a database name such as
+ <option>mysql</option>, <option>oracle</option>, <option>postgresql</option>,
+ etc., and <option>driver</option> the name of a DBAPI, such as
+ <option>psycopg2</option>, <option>pyodbc</option>, <option>cx_oracle</option>,
+ etc. The <link
+ xlink:href="http://docs.sqlalchemy.org/en/rel_0_9/core/engines.html#database-urls">
+ SQLAlchemy documentation</link> provides more examples and describe the syntax of
+ the expected URL.
+ '';
+ };
+
+ privateConfig = mkOption {
+ type = types.str;
+ default = "/etc/firefox/syncserver-secret.ini";
+ description = ''
+ The private config file is used to extend the generated config with confidential
+ information, such as the <option>syncserver.sqlUri</option> setting if it contains a
+ password, and the <option>syncserver.secret</option> setting is used by the server to
+ generate cryptographically-signed authentication tokens.
+
+ If this file does not exists, then it is created with a generated
+ <option>syncserver.secret</option> settings.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.syncserver = {
+ after = [ "network.target" ];
+ description = "Firefox Sync Server";
+ wantedBy = [ "multi-user.target" ];
+ path = [
+ pkgs.coreutils
+ (pkgs.python.withPackages (ps: [ pkgs.syncserver ps.gunicorn ]))
+ ];
+
+ serviceConfig = {
+ User = user;
+ Group = group;
+ PermissionsStartOnly = true;
+ };
+
+ preStart = ''
+ if ! test -e ${cfg.privateConfig}; then
+ mkdir -p $(dirname ${cfg.privateConfig})
+ echo > ${cfg.privateConfig} '[syncserver]'
+ chmod 600 ${cfg.privateConfig}
+ echo >> ${cfg.privateConfig} "secret = $(head -c 20 /dev/urandom | sha1sum | tr -d ' -')"
+ fi
+ chmod 600 ${cfg.privateConfig}
+ chmod 755 $(dirname ${cfg.privateConfig})
+ chown ${user}:${group} ${cfg.privateConfig}
+
+ '' + optionalString (cfg.sqlUri == defaultSqlUri) ''
+ if ! test -e $(dirname ${defaultDbLocation}); then
+ mkdir -m 700 -p $(dirname ${defaultDbLocation})
+ chown ${user}:${group} $(dirname ${defaultDbLocation})
+ fi
+
+ # Move previous database file if it exists
+ oldDb="/var/db/firefox-sync-server.db"
+ if test -f $oldDb; then
+ mv $oldDb ${defaultDbLocation}
+ chown ${user}:${group} ${defaultDbLocation}
+ fi
+ '';
+
+ script = ''
+ gunicorn --paste ${syncServerIni}
+ '';
+ };
+
+ users.users.${user} = {
+ inherit group;
+ isSystemUser = true;
+ };
+
+ users.groups.${group} = {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/fireqos.nix b/nixpkgs/nixos/modules/services/networking/fireqos.nix
new file mode 100644
index 00000000000..0b34f0b6b8b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/fireqos.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.fireqos;
+ fireqosConfig = pkgs.writeText "fireqos.conf" "${cfg.config}";
+in {
+ options.services.fireqos = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, FireQOS will be launched with the specified
+ configuration given in `config`.
+ '';
+ };
+
+ config = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ interface wlp3s0 world-in input rate 10mbit ethernet
+ class web commit 50kbit
+ match tcp ports 80,443
+
+ interface wlp3s0 world-out input rate 10mbit ethernet
+ class web commit 50kbit
+ match tcp ports 80,443
+ '';
+ description = ''
+ The FireQOS configuration goes here.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.fireqos = {
+ description = "FireQOS";
+ after = [ "network.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${pkgs.firehol}/bin/fireqos start ${fireqosConfig}";
+ ExecStop = [
+ "${pkgs.firehol}/bin/fireqos stop"
+ "${pkgs.firehol}/bin/fireqos clear_all_qos"
+ ];
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/firewall.nix b/nixpkgs/nixos/modules/services/networking/firewall.nix
new file mode 100644
index 00000000000..5b3aa19af3b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/firewall.nix
@@ -0,0 +1,579 @@
+/* This module enables a simple firewall.
+
+ The firewall can be customised in arbitrary ways by setting
+ ‘networking.firewall.extraCommands’. For modularity, the firewall
+ uses several chains:
+
+ - ‘nixos-fw’ is the main chain for input packet processing.
+
+ - ‘nixos-fw-accept’ is called for accepted packets. If you want
+ additional logging, or want to reject certain packets anyway, you
+ can insert rules at the start of this chain.
+
+ - ‘nixos-fw-log-refuse’ and ‘nixos-fw-refuse’ are called for
+ refused packets. (The former jumps to the latter after logging
+ the packet.) If you want additional logging, or want to accept
+ certain packets anyway, you can insert rules at the start of
+ this chain.
+
+ - ‘nixos-fw-rpfilter’ is used as the main chain in the raw table,
+ called from the built-in ‘PREROUTING’ chain. If the kernel
+ supports it and `cfg.checkReversePath` is set this chain will
+ perform a reverse path filter test.
+
+ - ‘nixos-drop’ is used while reloading the firewall in order to drop
+ all traffic. Since reloading isn't implemented in an atomic way
+ this'll prevent any traffic from leaking through while reloading
+ the firewall. However, if the reloading fails, the ‘firewall-stop’
+ script will be called which in return will effectively disable the
+ complete firewall (in the default configuration).
+
+*/
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.networking.firewall;
+
+ inherit (config.boot.kernelPackages) kernel;
+
+ kernelHasRPFilter = ((kernel.config.isEnabled or (x: false)) "IP_NF_MATCH_RPFILTER") || (kernel.features.netfilterRPFilter or false);
+
+ helpers =
+ ''
+ # Helper command to manipulate both the IPv4 and IPv6 tables.
+ ip46tables() {
+ iptables -w "$@"
+ ${optionalString config.networking.enableIPv6 ''
+ ip6tables -w "$@"
+ ''}
+ }
+ '';
+
+ writeShScript = name: text: let dir = pkgs.writeScriptBin name ''
+ #! ${pkgs.runtimeShell} -e
+ ${text}
+ ''; in "${dir}/bin/${name}";
+
+ defaultInterface = { default = mapAttrs (name: value: cfg.${name}) commonOptions; };
+ allInterfaces = defaultInterface // cfg.interfaces;
+
+ startScript = writeShScript "firewall-start" ''
+ ${helpers}
+
+ # Flush the old firewall rules. !!! Ideally, updating the
+ # firewall would be atomic. Apparently that's possible
+ # with iptables-restore.
+ ip46tables -D INPUT -j nixos-fw 2> /dev/null || true
+ for chain in nixos-fw nixos-fw-accept nixos-fw-log-refuse nixos-fw-refuse; do
+ ip46tables -F "$chain" 2> /dev/null || true
+ ip46tables -X "$chain" 2> /dev/null || true
+ done
+
+
+ # The "nixos-fw-accept" chain just accepts packets.
+ ip46tables -N nixos-fw-accept
+ ip46tables -A nixos-fw-accept -j ACCEPT
+
+
+ # The "nixos-fw-refuse" chain rejects or drops packets.
+ ip46tables -N nixos-fw-refuse
+
+ ${if cfg.rejectPackets then ''
+ # Send a reset for existing TCP connections that we've
+ # somehow forgotten about. Send ICMP "port unreachable"
+ # for everything else.
+ ip46tables -A nixos-fw-refuse -p tcp ! --syn -j REJECT --reject-with tcp-reset
+ ip46tables -A nixos-fw-refuse -j REJECT
+ '' else ''
+ ip46tables -A nixos-fw-refuse -j DROP
+ ''}
+
+
+ # The "nixos-fw-log-refuse" chain performs logging, then
+ # jumps to the "nixos-fw-refuse" chain.
+ ip46tables -N nixos-fw-log-refuse
+
+ ${optionalString cfg.logRefusedConnections ''
+ ip46tables -A nixos-fw-log-refuse -p tcp --syn -j LOG --log-level info --log-prefix "refused connection: "
+ ''}
+ ${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
+ ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type broadcast \
+ -j LOG --log-level info --log-prefix "refused broadcast: "
+ ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type multicast \
+ -j LOG --log-level info --log-prefix "refused multicast: "
+ ''}
+ ip46tables -A nixos-fw-log-refuse -m pkttype ! --pkt-type unicast -j nixos-fw-refuse
+ ${optionalString cfg.logRefusedPackets ''
+ ip46tables -A nixos-fw-log-refuse \
+ -j LOG --log-level info --log-prefix "refused packet: "
+ ''}
+ ip46tables -A nixos-fw-log-refuse -j nixos-fw-refuse
+
+
+ # The "nixos-fw" chain does the actual work.
+ ip46tables -N nixos-fw
+
+ # Clean up rpfilter rules
+ ip46tables -t raw -D PREROUTING -j nixos-fw-rpfilter 2> /dev/null || true
+ ip46tables -t raw -F nixos-fw-rpfilter 2> /dev/null || true
+ ip46tables -t raw -X nixos-fw-rpfilter 2> /dev/null || true
+
+ ${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
+ # Perform a reverse-path test to refuse spoofers
+ # For now, we just drop, as the raw table doesn't have a log-refuse yet
+ ip46tables -t raw -N nixos-fw-rpfilter 2> /dev/null || true
+ ip46tables -t raw -A nixos-fw-rpfilter -m rpfilter --validmark ${optionalString (cfg.checkReversePath == "loose") "--loose"} -j RETURN
+
+ # Allows this host to act as a DHCP4 client without first having to use APIPA
+ iptables -t raw -A nixos-fw-rpfilter -p udp --sport 67 --dport 68 -j RETURN
+
+ # Allows this host to act as a DHCPv4 server
+ iptables -t raw -A nixos-fw-rpfilter -s 0.0.0.0 -d 255.255.255.255 -p udp --sport 68 --dport 67 -j RETURN
+
+ ${optionalString cfg.logReversePathDrops ''
+ ip46tables -t raw -A nixos-fw-rpfilter -j LOG --log-level info --log-prefix "rpfilter drop: "
+ ''}
+ ip46tables -t raw -A nixos-fw-rpfilter -j DROP
+
+ ip46tables -t raw -A PREROUTING -j nixos-fw-rpfilter
+ ''}
+
+ # Accept all traffic on the trusted interfaces.
+ ${flip concatMapStrings cfg.trustedInterfaces (iface: ''
+ ip46tables -A nixos-fw -i ${iface} -j nixos-fw-accept
+ '')}
+
+ # Accept packets from established or related connections.
+ ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept
+
+ # Accept connections to the allowed TCP ports.
+ ${concatStrings (mapAttrsToList (iface: cfg:
+ concatMapStrings (port:
+ ''
+ ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+ ''
+ ) cfg.allowedTCPPorts
+ ) allInterfaces)}
+
+ # Accept connections to the allowed TCP port ranges.
+ ${concatStrings (mapAttrsToList (iface: cfg:
+ concatMapStrings (rangeAttr:
+ let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
+ ''
+ ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+ ''
+ ) cfg.allowedTCPPortRanges
+ ) allInterfaces)}
+
+ # Accept packets on the allowed UDP ports.
+ ${concatStrings (mapAttrsToList (iface: cfg:
+ concatMapStrings (port:
+ ''
+ ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+ ''
+ ) cfg.allowedUDPPorts
+ ) allInterfaces)}
+
+ # Accept packets on the allowed UDP port ranges.
+ ${concatStrings (mapAttrsToList (iface: cfg:
+ concatMapStrings (rangeAttr:
+ let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
+ ''
+ ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+ ''
+ ) cfg.allowedUDPPortRanges
+ ) allInterfaces)}
+
+ # Accept IPv4 multicast. Not a big security risk since
+ # probably nobody is listening anyway.
+ #iptables -A nixos-fw -d 224.0.0.0/4 -j nixos-fw-accept
+
+ # Optionally respond to ICMPv4 pings.
+ ${optionalString cfg.allowPing ''
+ iptables -w -A nixos-fw -p icmp --icmp-type echo-request ${optionalString (cfg.pingLimit != null)
+ "-m limit ${cfg.pingLimit} "
+ }-j nixos-fw-accept
+ ''}
+
+ ${optionalString config.networking.enableIPv6 ''
+ # Accept all ICMPv6 messages except redirects and node
+ # information queries (type 139). See RFC 4890, section
+ # 4.4.
+ ip6tables -A nixos-fw -p icmpv6 --icmpv6-type redirect -j DROP
+ ip6tables -A nixos-fw -p icmpv6 --icmpv6-type 139 -j DROP
+ ip6tables -A nixos-fw -p icmpv6 -j nixos-fw-accept
+
+ # Allow this host to act as a DHCPv6 client
+ ip6tables -A nixos-fw -d fe80::/64 -p udp --dport 546 -j nixos-fw-accept
+ ''}
+
+ ${cfg.extraCommands}
+
+ # Reject/drop everything else.
+ ip46tables -A nixos-fw -j nixos-fw-log-refuse
+
+
+ # Enable the firewall.
+ ip46tables -A INPUT -j nixos-fw
+ '';
+
+ stopScript = writeShScript "firewall-stop" ''
+ ${helpers}
+
+ # Clean up in case reload fails
+ ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
+
+ # Clean up after added ruleset
+ ip46tables -D INPUT -j nixos-fw 2>/dev/null || true
+
+ ${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
+ ip46tables -t raw -D PREROUTING -j nixos-fw-rpfilter 2>/dev/null || true
+ ''}
+
+ ${cfg.extraStopCommands}
+ '';
+
+ reloadScript = writeShScript "firewall-reload" ''
+ ${helpers}
+
+ # Create a unique drop rule
+ ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
+ ip46tables -F nixos-drop 2>/dev/null || true
+ ip46tables -X nixos-drop 2>/dev/null || true
+ ip46tables -N nixos-drop
+ ip46tables -A nixos-drop -j DROP
+
+ # Don't allow traffic to leak out until the script has completed
+ ip46tables -A INPUT -j nixos-drop
+
+ ${cfg.extraStopCommands}
+
+ if ${startScript}; then
+ ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
+ else
+ echo "Failed to reload firewall... Stopping"
+ ${stopScript}
+ exit 1
+ fi
+ '';
+
+ canonicalizePortList =
+ ports: lib.unique (builtins.sort builtins.lessThan ports);
+
+ commonOptions = {
+ allowedTCPPorts = mkOption {
+ type = types.listOf types.port;
+ default = [ ];
+ apply = canonicalizePortList;
+ example = [ 22 80 ];
+ description =
+ ''
+ List of TCP ports on which incoming connections are
+ accepted.
+ '';
+ };
+
+ allowedTCPPortRanges = mkOption {
+ type = types.listOf (types.attrsOf types.port);
+ default = [ ];
+ example = [ { from = 8999; to = 9003; } ];
+ description =
+ ''
+ A range of TCP ports on which incoming connections are
+ accepted.
+ '';
+ };
+
+ allowedUDPPorts = mkOption {
+ type = types.listOf types.port;
+ default = [ ];
+ apply = canonicalizePortList;
+ example = [ 53 ];
+ description =
+ ''
+ List of open UDP ports.
+ '';
+ };
+
+ allowedUDPPortRanges = mkOption {
+ type = types.listOf (types.attrsOf types.port);
+ default = [ ];
+ example = [ { from = 60000; to = 61000; } ];
+ description =
+ ''
+ Range of open UDP ports.
+ '';
+ };
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ networking.firewall = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Whether to enable the firewall. This is a simple stateful
+ firewall that blocks connection attempts to unauthorised TCP
+ or UDP ports on this machine. It does not affect packet
+ forwarding.
+ '';
+ };
+
+ logRefusedConnections = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Whether to log rejected or dropped incoming connections.
+ '';
+ };
+
+ logRefusedPackets = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Whether to log all rejected or dropped incoming packets.
+ This tends to give a lot of log messages, so it's mostly
+ useful for debugging.
+ '';
+ };
+
+ logRefusedUnicastsOnly = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ If <option>networking.firewall.logRefusedPackets</option>
+ and this option are enabled, then only log packets
+ specifically directed at this machine, i.e., not broadcasts
+ or multicasts.
+ '';
+ };
+
+ rejectPackets = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ If set, refused packets are rejected rather than dropped
+ (ignored). This means that an ICMP "port unreachable" error
+ message is sent back to the client (or a TCP RST packet in
+ case of an existing connection). Rejecting packets makes
+ port scanning somewhat easier.
+ '';
+ };
+
+ trustedInterfaces = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "enp0s2" ];
+ description =
+ ''
+ Traffic coming in from these interfaces will be accepted
+ unconditionally. Traffic from the loopback (lo) interface
+ will always be accepted.
+ '';
+ };
+
+ allowPing = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Whether to respond to incoming ICMPv4 echo requests
+ ("pings"). ICMPv6 pings are always allowed because the
+ larger address space of IPv6 makes network scanning much
+ less effective.
+ '';
+ };
+
+ pingLimit = mkOption {
+ type = types.nullOr (types.separatedString " ");
+ default = null;
+ example = "--limit 1/minute --limit-burst 5";
+ description =
+ ''
+ If pings are allowed, this allows setting rate limits
+ on them. If non-null, this option should be in the form of
+ flags like "--limit 1/minute --limit-burst 5"
+ '';
+ };
+
+ checkReversePath = mkOption {
+ type = types.either types.bool (types.enum ["strict" "loose"]);
+ default = kernelHasRPFilter;
+ example = "loose";
+ description =
+ ''
+ Performs a reverse path filter test on a packet. If a reply
+ to the packet would not be sent via the same interface that
+ the packet arrived on, it is refused.
+
+ If using asymmetric routing or other complicated routing, set
+ this option to loose mode or disable it and setup your own
+ counter-measures.
+
+ This option can be either true (or "strict"), "loose" (only
+ drop the packet if the source address is not reachable via any
+ interface) or false. Defaults to the value of
+ kernelHasRPFilter.
+
+ (needs kernel 3.3+)
+ '';
+ };
+
+ logReversePathDrops = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Logs dropped packets failing the reverse path filter test if
+ the option networking.firewall.checkReversePath is enabled.
+ '';
+ };
+
+ connectionTrackingModules = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "ftp" "irc" "sane" "sip" "tftp" "amanda" "h323" "netbios_sn" "pptp" "snmp" ];
+ description =
+ ''
+ List of connection-tracking helpers that are auto-loaded.
+ The complete list of possible values is given in the example.
+
+ As helpers can pose as a security risk, it is advised to
+ set this to an empty list and disable the setting
+ networking.firewall.autoLoadConntrackHelpers unless you
+ know what you are doing. Connection tracking is disabled
+ by default.
+
+ Loading of helpers is recommended to be done through the
+ CT target. More info:
+ https://home.regit.org/netfilter-en/secure-use-of-helpers/
+ '';
+ };
+
+ autoLoadConntrackHelpers = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Whether to auto-load connection-tracking helpers.
+ See the description at networking.firewall.connectionTrackingModules
+
+ (needs kernel 3.5+)
+ '';
+ };
+
+ extraCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example = "iptables -A INPUT -p icmp -j ACCEPT";
+ description =
+ ''
+ Additional shell commands executed as part of the firewall
+ initialisation script. These are executed just before the
+ final "reject" firewall rule is added, so they can be used
+ to allow packets that would otherwise be refused.
+ '';
+ };
+
+ extraPackages = mkOption {
+ type = types.listOf types.package;
+ default = [ ];
+ example = literalExample "[ pkgs.ipset ]";
+ description =
+ ''
+ Additional packages to be included in the environment of the system
+ as well as the path of networking.firewall.extraCommands.
+ '';
+ };
+
+ extraStopCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example = "iptables -P INPUT ACCEPT";
+ description =
+ ''
+ Additional shell commands executed as part of the firewall
+ shutdown script. These are executed just after the removal
+ of the NixOS input rule, or if the service enters a failed
+ state.
+ '';
+ };
+
+ interfaces = mkOption {
+ default = { };
+ type = with types; attrsOf (submodule [ { options = commonOptions; } ]);
+ description =
+ ''
+ Interface-specific open ports.
+ '';
+ };
+ } // commonOptions;
+
+ };
+
+
+ ###### implementation
+
+ # FIXME: Maybe if `enable' is false, the firewall should still be
+ # built but not started by default?
+ config = mkIf cfg.enable {
+
+ networking.firewall.trustedInterfaces = [ "lo" ];
+
+ environment.systemPackages = [ pkgs.iptables ] ++ cfg.extraPackages;
+
+ boot.kernelModules = (optional cfg.autoLoadConntrackHelpers "nf_conntrack")
+ ++ map (x: "nf_conntrack_${x}") cfg.connectionTrackingModules;
+ boot.extraModprobeConfig = optionalString cfg.autoLoadConntrackHelpers ''
+ options nf_conntrack nf_conntrack_helper=1
+ '';
+
+ assertions = [ { assertion = (cfg.checkReversePath != false) || kernelHasRPFilter;
+ message = "This kernel does not support rpfilter"; }
+ ];
+
+ systemd.services.firewall = {
+ description = "Firewall";
+ wantedBy = [ "sysinit.target" ];
+ wants = [ "network-pre.target" ];
+ before = [ "network-pre.target" ];
+ after = [ "systemd-modules-load.service" ];
+
+ path = [ pkgs.iptables ] ++ cfg.extraPackages;
+
+ # FIXME: this module may also try to load kernel modules, but
+ # containers don't have CAP_SYS_MODULE. So the host system had
+ # better have all necessary modules already loaded.
+ unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+ unitConfig.DefaultDependencies = false;
+
+ reloadIfChanged = true;
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "@${startScript} firewall-start";
+ ExecReload = "@${reloadScript} firewall-reload";
+ ExecStop = "@${stopScript} firewall-stop";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/flannel.nix b/nixpkgs/nixos/modules/services/networking/flannel.nix
new file mode 100644
index 00000000000..dd2f6454e95
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/flannel.nix
@@ -0,0 +1,191 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.flannel;
+
+ networkConfig = filterAttrs (n: v: v != null) {
+ Network = cfg.network;
+ SubnetLen = cfg.subnetLen;
+ SubnetMin = cfg.subnetMin;
+ SubnetMax = cfg.subnetMax;
+ Backend = cfg.backend;
+ };
+in {
+ options.services.flannel = {
+ enable = mkEnableOption "flannel";
+
+ package = mkOption {
+ description = "Package to use for flannel";
+ type = types.package;
+ default = pkgs.flannel.bin;
+ defaultText = "pkgs.flannel.bin";
+ };
+
+ publicIp = mkOption {
+ description = ''
+ IP accessible by other nodes for inter-host communication.
+ Defaults to the IP of the interface being used for communication.
+ '';
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ iface = mkOption {
+ description = ''
+ Interface to use (IP or name) for inter-host communication.
+ Defaults to the interface for the default route on the machine.
+ '';
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ etcd = {
+ endpoints = mkOption {
+ description = "Etcd endpoints";
+ type = types.listOf types.str;
+ default = ["http://127.0.0.1:2379"];
+ };
+
+ prefix = mkOption {
+ description = "Etcd key prefix";
+ type = types.str;
+ default = "/coreos.com/network";
+ };
+
+ caFile = mkOption {
+ description = "Etcd certificate authority file";
+ type = types.nullOr types.path;
+ default = null;
+ };
+
+ certFile = mkOption {
+ description = "Etcd cert file";
+ type = types.nullOr types.path;
+ default = null;
+ };
+
+ keyFile = mkOption {
+ description = "Etcd key file";
+ type = types.nullOr types.path;
+ default = null;
+ };
+ };
+
+ kubeconfig = mkOption {
+ description = ''
+ Path to kubeconfig to use for storing flannel config using the
+ Kubernetes API
+ '';
+ type = types.nullOr types.path;
+ default = null;
+ };
+
+ network = mkOption {
+ description = " IPv4 network in CIDR format to use for the entire flannel network.";
+ type = types.str;
+ };
+
+ nodeName = mkOption {
+ description = ''
+ Needed when running with Kubernetes as backend as this cannot be auto-detected";
+ '';
+ type = types.nullOr types.str;
+ default = with config.networking; (hostName + optionalString (domain != null) ".${domain}");
+ example = "node1.example.com";
+ };
+
+ storageBackend = mkOption {
+ description = "Determines where flannel stores its configuration at runtime";
+ type = types.enum ["etcd" "kubernetes"];
+ default = "etcd";
+ };
+
+ subnetLen = mkOption {
+ description = ''
+ The size of the subnet allocated to each host. Defaults to 24 (i.e. /24)
+ unless the Network was configured to be smaller than a /24 in which case
+ it is one less than the network.
+ '';
+ type = types.int;
+ default = 24;
+ };
+
+ subnetMin = mkOption {
+ description = ''
+ The beginning of IP range which the subnet allocation should start with.
+ Defaults to the first subnet of Network.
+ '';
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ subnetMax = mkOption {
+ description = ''
+ The end of IP range which the subnet allocation should start with.
+ Defaults to the last subnet of Network.
+ '';
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ backend = mkOption {
+ description = "Type of backend to use and specific configurations for that backend.";
+ type = types.attrs;
+ default = {
+ Type = "vxlan";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.flannel = {
+ description = "Flannel Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment = {
+ FLANNELD_PUBLIC_IP = cfg.publicIp;
+ FLANNELD_IFACE = cfg.iface;
+ } // optionalAttrs (cfg.storageBackend == "etcd") {
+ FLANNELD_ETCD_ENDPOINTS = concatStringsSep "," cfg.etcd.endpoints;
+ FLANNELD_ETCD_KEYFILE = cfg.etcd.keyFile;
+ FLANNELD_ETCD_CERTFILE = cfg.etcd.certFile;
+ FLANNELD_ETCD_CAFILE = cfg.etcd.caFile;
+ ETCDCTL_CERT_FILE = cfg.etcd.certFile;
+ ETCDCTL_KEY_FILE = cfg.etcd.keyFile;
+ ETCDCTL_CA_FILE = cfg.etcd.caFile;
+ ETCDCTL_PEERS = concatStringsSep "," cfg.etcd.endpoints;
+ } // optionalAttrs (cfg.storageBackend == "kubernetes") {
+ FLANNELD_KUBE_SUBNET_MGR = "true";
+ FLANNELD_KUBECONFIG_FILE = cfg.kubeconfig;
+ NODE_NAME = cfg.nodeName;
+ };
+ path = [ pkgs.iptables ];
+ preStart = ''
+ mkdir -p /run/flannel
+ touch /run/flannel/docker
+ '' + optionalString (cfg.storageBackend == "etcd") ''
+ echo "setting network configuration"
+ until ${pkgs.etcdctl.bin}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}'
+ do
+ echo "setting network configuration, retry"
+ sleep 1
+ done
+ '';
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/flannel";
+ Restart = "always";
+ RestartSec = "10s";
+ };
+ };
+
+ services.etcd.enable = mkDefault (cfg.storageBackend == "etcd" && cfg.etcd.endpoints == ["http://127.0.0.1:2379"]);
+
+ # for some reason, flannel doesn't let you configure this path
+ # see: https://github.com/coreos/flannel/blob/master/Documentation/configuration.md#configuration
+ environment.etc."kube-flannel/net-conf.json" = mkIf (cfg.storageBackend == "kubernetes") {
+ source = pkgs.writeText "net-conf.json" (builtins.toJSON networkConfig);
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/flashpolicyd.nix b/nixpkgs/nixos/modules/services/networking/flashpolicyd.nix
new file mode 100644
index 00000000000..9c51b88ef67
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/flashpolicyd.nix
@@ -0,0 +1,84 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.flashpolicyd;
+
+ flashpolicyd = pkgs.stdenv.mkDerivation {
+ name = "flashpolicyd-0.6";
+
+ src = pkgs.fetchurl {
+ name = "flashpolicyd_v0.6.zip";
+ url = "https://download.adobe.com/pub/adobe/devnet/flashplayer/articles/socket_policy_files/flashpolicyd_v0.6.zip";
+ sha256 = "16zk237233npwfq1m4ksy4g5lzy1z9fp95w7pz0cdlpmv0fv9sm3";
+ };
+
+ buildInputs = [ pkgs.unzip pkgs.perl ];
+
+ installPhase = "mkdir $out; cp -pr * $out/; chmod +x $out/*/*.pl";
+ };
+
+ flashpolicydWrapper = pkgs.writeScriptBin "flashpolicyd"
+ ''
+ #! ${pkgs.runtimeShell}
+ exec ${flashpolicyd}/Perl_xinetd/in.flashpolicyd.pl \
+ --file=${pkgs.writeText "flashpolixy.xml" cfg.policy} \
+ 2> /dev/null
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.flashpolicyd = {
+
+ enable = mkOption {
+ default = false;
+ description =
+ ''
+ Whether to enable the Flash Policy server. This is
+ necessary if you want Flash applications to make
+ connections to your server.
+ '';
+ };
+
+ policy = mkOption {
+ default =
+ ''
+ <?xml version="1.0"?>
+ <!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">
+ <cross-domain-policy>
+ <site-control permitted-cross-domain-policies="master-only"/>
+ <allow-access-from domain="*" to-ports="*" />
+ </cross-domain-policy>
+ '';
+ description = "The policy to be served. The default is to allow connections from any domain to any port.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.xinetd.enable = true;
+
+ services.xinetd.services = singleton
+ { name = "flashpolicy";
+ port = 843;
+ unlisted = true;
+ server = "${flashpolicydWrapper}/bin/flashpolicyd";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/freenet.nix b/nixpkgs/nixos/modules/services/networking/freenet.nix
new file mode 100644
index 00000000000..3da3ab0c7df
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/freenet.nix
@@ -0,0 +1,64 @@
+# NixOS module for Freenet daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.freenet;
+ varDir = "/var/lib/freenet";
+
+in
+
+{
+
+ ### configuration
+
+ options = {
+
+ services.freenet = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the Freenet daemon";
+ };
+
+ nice = mkOption {
+ type = types.int;
+ default = 10;
+ description = "Set the nice level for the Freenet daemon";
+ };
+
+ };
+
+ };
+
+ ### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.freenet = {
+ description = "Freenet daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${pkgs.freenet}/bin/freenet";
+ serviceConfig.User = "freenet";
+ serviceConfig.UMask = "0007";
+ serviceConfig.WorkingDirectory = varDir;
+ serviceConfig.Nice = cfg.nice;
+ };
+
+ users.users.freenet = {
+ group = "freenet";
+ description = "Freenet daemon user";
+ home = varDir;
+ createHome = true;
+ uid = config.ids.uids.freenet;
+ };
+
+ users.groups.freenet.gid = config.ids.gids.freenet;
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/freeradius.nix b/nixpkgs/nixos/modules/services/networking/freeradius.nix
new file mode 100644
index 00000000000..e192b70c129
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/freeradius.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.freeradius;
+
+ freeradiusService = cfg:
+ {
+ description = "FreeRadius server";
+ wantedBy = ["multi-user.target"];
+ after = ["network-online.target"];
+ wants = ["network-online.target"];
+ preStart = ''
+ ${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkgs.freeradius}/bin/radiusd -f -d ${cfg.configDir} -l stdout -xx";
+ ExecReload = [
+ "${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout"
+ "${pkgs.coreutils}/bin/kill -HUP $MAINPID"
+ ];
+ User = "radius";
+ ProtectSystem = "full";
+ ProtectHome = "on";
+ Restart = "on-failure";
+ RestartSec = 2;
+ };
+ };
+
+ freeradiusConfig = {
+ enable = mkEnableOption "the freeradius server";
+
+ configDir = mkOption {
+ type = types.path;
+ default = "/etc/raddb";
+ description = ''
+ The path of the freeradius server configuration directory.
+ '';
+ };
+
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.freeradius = freeradiusConfig;
+ };
+
+
+ ###### implementation
+
+ config = mkIf (cfg.enable) {
+
+ users = {
+ users.radius = {
+ /*uid = config.ids.uids.radius;*/
+ description = "Radius daemon user";
+ };
+ };
+
+ systemd.services.freeradius = freeradiusService cfg;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/gale.nix b/nixpkgs/nixos/modules/services/networking/gale.nix
new file mode 100644
index 00000000000..7083d87c407
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gale.nix
@@ -0,0 +1,182 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gale;
+ # we convert the path to a string to avoid it being copied to the nix store,
+ # otherwise users could read the private key as all files in the store are
+ # world-readable
+ keyPath = toString cfg.keyPath;
+ # ...but we refer to the pubkey file using a path so that we can ensure the
+ # config gets rebuilt if the public key changes (we can assume the private key
+ # will never change without the public key having changed)
+ gpubFile = cfg.keyPath + "/${cfg.domain}.gpub";
+ home = "/var/lib/gale";
+ keysPrepared = cfg.keyPath != null && lib.pathExists cfg.keyPath;
+in
+{
+ options = {
+ services.gale = {
+ enable = mkEnableOption "the Gale messaging daemon";
+
+ user = mkOption {
+ default = "gale";
+ type = types.str;
+ description = "Username for the Gale daemon.";
+ };
+
+ group = mkOption {
+ default = "gale";
+ type = types.str;
+ description = "Group name for the Gale daemon.";
+ };
+
+ setuidWrapper = mkOption {
+ default = null;
+ description = "Configuration for the Gale gksign setuid wrapper.";
+ };
+
+ domain = mkOption {
+ default = "";
+ type = types.str;
+ description = "Domain name for the Gale system.";
+ };
+
+ keyPath = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = ''
+ Directory containing the key pair for this Gale domain. The expected
+ filename will be taken from the domain option with ".gpri" and ".gpub"
+ appended.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional text to be added to <filename>/etc/gale/conf</filename>.
+ '';
+ };
+ };
+ };
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ assertions = [{
+ assertion = cfg.domain != "";
+ message = "A domain must be set for Gale.";
+ }];
+
+ warnings = mkIf (!keysPrepared) [
+ "You must run gale-install in order to generate a domain key."
+ ];
+
+ system.activationScripts.gale = mkIf cfg.enable (
+ stringAfter [ "users" "groups" ] ''
+ chmod 755 ${home}
+ mkdir -m 0777 -p ${home}/auth/cache
+ mkdir -m 1777 -p ${home}/auth/local # GALE_DOMAIN.gpub
+ mkdir -m 0700 -p ${home}/auth/private # ROOT.gpub
+ mkdir -m 0755 -p ${home}/auth/trusted # ROOT
+ mkdir -m 0700 -p ${home}/.gale
+ mkdir -m 0700 -p ${home}/.gale/auth
+ mkdir -m 0700 -p ${home}/.gale/auth/private # GALE_DOMAIN.gpri
+
+ ln -sf ${pkgs.gale}/etc/gale/auth/trusted/ROOT "${home}/auth/trusted/ROOT"
+ chown ${cfg.user}:${cfg.group} ${home} ${home}/auth ${home}/auth/*
+ chown ${cfg.user}:${cfg.group} ${home}/.gale ${home}/.gale/auth ${home}/.gale/auth/private
+ ''
+ );
+
+ environment = {
+ etc = {
+ "gale/auth".source = home + "/auth"; # symlink /var/lib/gale/auth
+ "gale/conf".text = ''
+ GALE_USER ${cfg.user}
+ GALE_DOMAIN ${cfg.domain}
+ ${cfg.extraConfig}
+ '';
+ };
+
+ systemPackages = [ pkgs.gale ];
+ };
+
+ users.users = [{
+ name = cfg.user;
+ description = "Gale daemon";
+ uid = config.ids.uids.gale;
+ group = cfg.group;
+ home = home;
+ createHome = true;
+ }];
+
+ users.groups = [{
+ name = cfg.group;
+ gid = config.ids.gids.gale;
+ }];
+ })
+ (mkIf (cfg.enable && keysPrepared) {
+ assertions = [
+ {
+ assertion = cfg.keyPath != null
+ && lib.pathExists (cfg.keyPath + "/${cfg.domain}.gpub");
+ message = "Couldn't find a Gale public key for ${cfg.domain}.";
+ }
+ {
+ assertion = cfg.keyPath != null
+ && lib.pathExists (cfg.keyPath + "/${cfg.domain}.gpri");
+ message = "Couldn't find a Gale private key for ${cfg.domain}.";
+ }
+ ];
+
+ services.gale.setuidWrapper = {
+ program = "gksign";
+ source = "${pkgs.gale}/bin/gksign";
+ owner = cfg.user;
+ group = cfg.group;
+ setuid = true;
+ setgid = false;
+ };
+
+ security.wrappers.gksign = cfg.setuidWrapper;
+
+ systemd.services.gale-galed = {
+ description = "Gale messaging daemon";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "gale-gdomain.service" ];
+ after = [ "network.target" ];
+
+ preStart = ''
+ install -m 0640 -o ${cfg.user} -g ${cfg.group} ${keyPath}/${cfg.domain}.gpri "${home}/.gale/auth/private/"
+ install -m 0644 -o ${cfg.user} -g ${cfg.group} ${gpubFile} "${home}/.gale/auth/private/${cfg.domain}.gpub"
+ install -m 0644 -o ${cfg.user} -g ${cfg.group} ${gpubFile} "${home}/auth/local/${cfg.domain}.gpub"
+ '';
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "@${pkgs.gale}/bin/galed galed";
+ User = cfg.user;
+ Group = cfg.group;
+ PermissionsStartOnly = true;
+ };
+ };
+
+ systemd.services.gale-gdomain = {
+ description = "Gale AKD daemon";
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "gale-galed.service" ];
+ after = [ "gale-galed.service" ];
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "@${pkgs.gale}/bin/gdomain gdomain";
+ User = cfg.user;
+ Group = cfg.group;
+ };
+ };
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/gateone.nix b/nixpkgs/nixos/modules/services/networking/gateone.nix
new file mode 100644
index 00000000000..4456a95402e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gateone.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ...}:
+with lib;
+let
+ cfg = config.services.gateone;
+in
+{
+options = {
+ services.gateone = {
+ enable = mkEnableOption "GateOne server";
+ pidDir = mkOption {
+ default = "/run/gateone";
+ type = types.path;
+ description = ''Path of pid files for GateOne.'';
+ };
+ settingsDir = mkOption {
+ default = "/var/lib/gateone";
+ type = types.path;
+ description = ''Path of configuration files for GateOne.'';
+ };
+ };
+};
+config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs.pythonPackages; [
+ gateone pkgs.openssh pkgs.procps pkgs.coreutils pkgs.cacert];
+
+ users.users.gateone = {
+ description = "GateOne privilege separation user";
+ uid = config.ids.uids.gateone;
+ home = cfg.settingsDir;
+ };
+ users.groups.gateone.gid = config.ids.gids.gateone;
+
+ systemd.services.gateone = with pkgs; {
+ description = "GateOne web-based terminal";
+ path = [ pythonPackages.gateone nix openssh procps coreutils ];
+ preStart = ''
+ if [ ! -d ${cfg.settingsDir} ] ; then
+ mkdir -m 0750 -p ${cfg.settingsDir}
+ chown -R gateone.gateone ${cfg.settingsDir}
+ fi
+ if [ ! -d ${cfg.pidDir} ] ; then
+ mkdir -m 0750 -p ${cfg.pidDir}
+ chown -R gateone.gateone ${cfg.pidDir}
+ fi
+ '';
+ #unitConfig.RequiresMountsFor = "${cfg.settingsDir}";
+ serviceConfig = {
+ ExecStart = ''${pythonPackages.gateone}/bin/gateone --settings_dir=${cfg.settingsDir} --pid_file=${cfg.pidDir}/gateone.pid --gid=${toString config.ids.gids.gateone} --uid=${toString config.ids.uids.gateone}'';
+ User = "gateone";
+ Group = "gateone";
+ WorkingDirectory = cfg.settingsDir;
+ };
+
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "network.target" ];
+ };
+};
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/gdomap.nix b/nixpkgs/nixos/modules/services/networking/gdomap.nix
new file mode 100644
index 00000000000..3d829cb6913
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gdomap.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ #
+ # interface
+ #
+ options = {
+ services.gdomap = {
+ enable = mkEnableOption "GNUstep Distributed Objects name server";
+ };
+ };
+
+ #
+ # implementation
+ #
+ config = mkIf config.services.gdomap.enable {
+ # NOTE: gdomap runs as root
+ # TODO: extra user for gdomap?
+ systemd.services.gdomap = {
+ description = "gdomap server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = [ pkgs.gnustep.base ];
+ serviceConfig.ExecStart = "${pkgs.gnustep.base}/bin/gdomap -f";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/git-daemon.nix b/nixpkgs/nixos/modules/services/networking/git-daemon.nix
new file mode 100644
index 00000000000..a638a3083fb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/git-daemon.nix
@@ -0,0 +1,130 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+
+ cfg = config.services.gitDaemon;
+
+in
+{
+
+ ###### interface
+
+ options = {
+ services.gitDaemon = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable Git daemon, which allows public hosting of git repositories
+ without any access controls. This is mostly intended for read-only access.
+
+ You can allow write access by setting daemon.receivepack configuration
+ item of the repository to true. This is solely meant for a closed LAN setting
+ where everybody is friendly.
+
+ If you need any access controls, use something else.
+ '';
+ };
+
+ basePath = mkOption {
+ type = types.str;
+ default = "";
+ example = "/srv/git/";
+ description = ''
+ Remap all the path requests as relative to the given path. For example,
+ if you set base-path to /srv/git, then if you later try to pull
+ git://example.com/hello.git, Git daemon will interpret the path as /srv/git/hello.git.
+ '';
+ };
+
+ exportAll = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Publish all directories that look like Git repositories (have the objects
+ and refs subdirectories), even if they do not have the git-daemon-export-ok file.
+
+ If disabled, you need to touch .git/git-daemon-export-ok in each repository
+ you want the daemon to publish.
+
+ Warning: enabling this without a repository whitelist or basePath
+ publishes every git repository you have.
+ '';
+ };
+
+ repositories = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "/srv/git" "/home/user/git/repo2" ];
+ description = ''
+ A whitelist of paths of git repositories, or directories containing repositories
+ all of which would be published. Paths must not end in "/".
+
+ Warning: leaving this empty and enabling exportAll publishes all
+ repositories in your filesystem or basePath if specified.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "";
+ example = "example.com";
+ description = "Listen on a specific IP address or hostname.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 9418;
+ description = "Port to listen on.";
+ };
+
+ options = mkOption {
+ type = types.str;
+ default = "";
+ description = "Extra configuration options to be passed to Git daemon.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "git";
+ description = "User under which Git daemon would be running.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "git";
+ description = "Group under which Git daemon would be running.";
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = if cfg.user != "git" then {} else singleton
+ { name = "git";
+ uid = config.ids.uids.git;
+ description = "Git daemon user";
+ };
+
+ users.groups = if cfg.group != "git" then {} else singleton
+ { name = "git";
+ gid = config.ids.gids.git;
+ };
+
+ systemd.services.git-daemon = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ script = "${pkgs.git}/bin/git daemon --reuseaddr "
+ + (optionalString (cfg.basePath != "") "--base-path=${cfg.basePath} ")
+ + (optionalString (cfg.listenAddress != "") "--listen=${cfg.listenAddress} ")
+ + "--port=${toString cfg.port} --user=${cfg.user} --group=${cfg.group} ${cfg.options} "
+ + "--verbose " + (optionalString cfg.exportAll "--export-all ") + concatStringsSep " " cfg.repositories;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/gnunet.nix b/nixpkgs/nixos/modules/services/networking/gnunet.nix
new file mode 100644
index 00000000000..178a832c166
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gnunet.nix
@@ -0,0 +1,158 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.gnunet;
+
+ homeDir = "/var/lib/gnunet";
+
+ configFile = with cfg; pkgs.writeText "gnunetd.conf"
+ ''
+ [PATHS]
+ SERVICEHOME = ${homeDir}
+
+ [ats]
+ WAN_QUOTA_IN = ${toString load.maxNetDownBandwidth} b
+ WAN_QUOTA_OUT = ${toString load.maxNetUpBandwidth} b
+
+ [datastore]
+ QUOTA = ${toString fileSharing.quota} MB
+
+ [transport-udp]
+ PORT = ${toString udp.port}
+ ADVERTISED_PORT = ${toString udp.port}
+
+ [transport-tcp]
+ PORT = ${toString tcp.port}
+ ADVERTISED_PORT = ${toString tcp.port}
+
+ ${extraOptions}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gnunet = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to run the GNUnet daemon. GNUnet is GNU's anonymous
+ peer-to-peer communication and file sharing framework.
+ '';
+ };
+
+ fileSharing = {
+ quota = mkOption {
+ default = 1024;
+ description = ''
+ Maximum file system usage (in MiB) for file sharing.
+ '';
+ };
+ };
+
+ udp = {
+ port = mkOption {
+ default = 2086; # assigned by IANA
+ description = ''
+ The UDP port for use by GNUnet.
+ '';
+ };
+ };
+
+ tcp = {
+ port = mkOption {
+ default = 2086; # assigned by IANA
+ description = ''
+ The TCP port for use by GNUnet.
+ '';
+ };
+ };
+
+ load = {
+ maxNetDownBandwidth = mkOption {
+ default = 50000;
+ description = ''
+ Maximum bandwidth usage (in bits per second) for GNUnet
+ when downloading data.
+ '';
+ };
+
+ maxNetUpBandwidth = mkOption {
+ default = 50000;
+ description = ''
+ Maximum bandwidth usage (in bits per second) for GNUnet
+ when downloading data.
+ '';
+ };
+
+ hardNetUpBandwidth = mkOption {
+ default = 0;
+ description = ''
+ Hard bandwidth limit (in bits per second) when uploading
+ data.
+ '';
+ };
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.gnunet;
+ defaultText = "pkgs.gnunet";
+ description = "Overridable attribute of the gnunet package to use.";
+ example = literalExample "pkgs.gnunet_git";
+ };
+
+ extraOptions = mkOption {
+ default = "";
+ description = ''
+ Additional options that will be copied verbatim in `gnunet.conf'.
+ See `gnunet.conf(5)' for details.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.gnunet.enable {
+
+ users.users.gnunet = {
+ group = "gnunet";
+ description = "GNUnet User";
+ home = homeDir;
+ createHome = true;
+ uid = config.ids.uids.gnunet;
+ };
+
+ users.groups.gnunet.gid = config.ids.gids.gnunet;
+
+ # The user tools that talk to `gnunetd' should come from the same source,
+ # so install them globally.
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services.gnunet = {
+ description = "GNUnet";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ cfg.package pkgs.miniupnpc ];
+ environment.TMPDIR = "/tmp";
+ serviceConfig.PrivateTmp = true;
+ serviceConfig.ExecStart = "${cfg.package}/lib/gnunet/libexec/gnunet-service-arm -c ${configFile}";
+ serviceConfig.User = "gnunet";
+ serviceConfig.UMask = "0007";
+ serviceConfig.WorkingDirectory = homeDir;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/gogoclient.nix b/nixpkgs/nixos/modules/services/networking/gogoclient.nix
new file mode 100644
index 00000000000..c9b03bca711
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gogoclient.nix
@@ -0,0 +1,84 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.gogoclient;
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.gogoclient = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable the gogoCLIENT IPv6 tunnel.
+ '';
+ };
+ autorun = mkOption {
+ default = true;
+ description = ''
+ Whether to automatically start the tunnel.
+ '';
+ };
+
+ username = mkOption {
+ default = "";
+ description = ''
+ Your Gateway6 login name, if any.
+ '';
+ };
+
+ password = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Path to a file (as a string), containing your gogoNET password, if any.
+ '';
+ };
+
+ server = mkOption {
+ default = "anonymous.freenet6.net";
+ example = "broker.freenet6.net";
+ description = "The Gateway6 server to be used.";
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ boot.kernelModules = [ "tun" ];
+
+ networking.enableIPv6 = true;
+
+ systemd.services.gogoclient = {
+ description = "ipv6 tunnel";
+
+ after = [ "network.target" ];
+ requires = [ "network.target" ];
+
+ unitConfig.RequiresMountsFor = "/var/lib/gogoc";
+
+ script = let authMethod = if cfg.password == "" then "anonymous" else "any"; in ''
+ mkdir -p -m 700 /var/lib/gogoc
+ cat ${pkgs.gogoclient}/share/${pkgs.gogoclient.name}/gogoc.conf.sample | \
+ ${pkgs.gnused}/bin/sed \
+ -e "s|^userid=|&${cfg.username}|" \
+ -e "s|^passwd=|&${optionalString (cfg.password != "") "$(cat ${cfg.password})"}|" \
+ -e "s|^server=.*|server=${cfg.server}|" \
+ -e "s|^auth_method=.*|auth_method=${authMethod}|" \
+ -e "s|^#log_file=|log_file=1|" > /var/lib/gogoc/gogoc.conf
+ cd /var/lib/gogoc
+ exec ${pkgs.gogoclient}/bin/gogoc -y -f /var/lib/gogoc/gogoc.conf
+ '';
+ } // optionalAttrs cfg.autorun {
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/gvpe.nix b/nixpkgs/nixos/modules/services/networking/gvpe.nix
new file mode 100644
index 00000000000..3ef3548e0a0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gvpe.nix
@@ -0,0 +1,128 @@
+# GNU Virtual Private Ethernet
+
+{config, pkgs, lib, ...}:
+
+let
+ inherit (lib) mkOption mkIf;
+
+ cfg = config.services.gvpe;
+
+ finalConfig = if cfg.configFile != null then
+ cfg.configFile
+ else if cfg.configText != null then
+ pkgs.writeTextFile {
+ name = "gvpe.conf";
+ text = cfg.configText;
+ }
+ else
+ throw "You must either specify contents of the config file or the config file itself for GVPE";
+
+ ifupScript = if cfg.ipAddress == null || cfg.subnet == null then
+ throw "Specify IP address and subnet (with mask) for GVPE"
+ else if cfg.nodename == null then
+ throw "You must set node name for GVPE"
+ else
+ (pkgs.writeTextFile {
+ name = "gvpe-if-up";
+ text = ''
+ #! /bin/sh
+
+ export PATH=$PATH:${pkgs.iproute}/sbin
+
+ ip link set $IFNAME up
+ ip address add ${cfg.ipAddress} dev $IFNAME
+ ip route add ${cfg.subnet} dev $IFNAME
+
+ ${cfg.customIFSetup}
+ '';
+ executable = true;
+ });
+in
+
+{
+ options = {
+ services.gvpe = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to run gvpe
+ '';
+ };
+ nodename = mkOption {
+ default = null;
+ description =''
+ GVPE node name
+ '';
+ };
+ configText = mkOption {
+ default = null;
+ example = ''
+ tcp-port = 655
+ udp-port = 655
+ mtu = 1480
+ ifname = vpn0
+
+ node = alpha
+ hostname = alpha.example.org
+ connect = always
+ enable-udp = true
+ enable-tcp = true
+ on alpha if-up = if-up-0
+ on alpha pid-file = /var/gvpe/gvpe.pid
+ '';
+ description = ''
+ GVPE config contents
+ '';
+ };
+ configFile = mkOption {
+ default = null;
+ example = "/root/my-gvpe-conf";
+ description = ''
+ GVPE config file, if already present
+ '';
+ };
+ ipAddress = mkOption {
+ default = null;
+ description = ''
+ IP address to assign to GVPE interface
+ '';
+ };
+ subnet = mkOption {
+ default = null;
+ example = "10.0.0.0/8";
+ description = ''
+ IP subnet assigned to GVPE network
+ '';
+ };
+ customIFSetup = mkOption {
+ default = "";
+ description = ''
+ Additional commands to apply in ifup script
+ '';
+ };
+ };
+ };
+ config = mkIf cfg.enable {
+ systemd.services.gvpe = {
+ description = "GNU Virtual Private Ethernet node";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ mkdir -p /var/gvpe
+ mkdir -p /var/gvpe/pubkey
+ chown root /var/gvpe
+ chmod 700 /var/gvpe
+ cp ${finalConfig} /var/gvpe/gvpe.conf
+ cp ${ifupScript} /var/gvpe/if-up
+ '';
+
+ script = "${pkgs.gvpe}/sbin/gvpe -c /var/gvpe -D ${cfg.nodename} "
+ + " ${cfg.nodename}.pid-file=/var/gvpe/gvpe.pid"
+ + " ${cfg.nodename}.if-up=if-up"
+ + " &> /var/log/gvpe";
+
+ serviceConfig.Restart = "always";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hans.nix b/nixpkgs/nixos/modules/services/networking/hans.nix
new file mode 100644
index 00000000000..20e57e4626e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hans.nix
@@ -0,0 +1,145 @@
+# NixOS module for hans, ip over icmp daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.hans;
+
+ hansUser = "hans";
+
+in
+{
+
+ ### configuration
+
+ options = {
+
+ services.hans = {
+ clients = mkOption {
+ default = {};
+ description = ''
+ Each attribute of this option defines a systemd service that
+ runs hans. Many or none may be defined.
+ The name of each service is
+ <literal>hans-<replaceable>name</replaceable></literal>
+ where <replaceable>name</replaceable> is the name of the
+ corresponding attribute name.
+ '';
+ example = literalExample ''
+ {
+ foo = {
+ server = "192.0.2.1";
+ extraConfig = "-v";
+ }
+ }
+ '';
+ type = types.attrsOf (types.submodule (
+ {
+ options = {
+ server = mkOption {
+ type = types.str;
+ default = "";
+ description = "IP address of server running hans";
+ example = "192.0.2.1";
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = "Additional command line parameters";
+ example = "-v";
+ };
+
+ passwordFile = mkOption {
+ type = types.str;
+ default = "";
+ description = "File that containts password";
+ };
+
+ };
+ }));
+ };
+
+ server = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "enable hans server";
+ };
+
+ ip = mkOption {
+ type = types.str;
+ default = "";
+ description = "The assigned ip range";
+ example = "198.51.100.0";
+ };
+
+ respondToSystemPings = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Force hans respond to ordinary pings";
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = "Additional command line parameters";
+ example = "-v";
+ };
+
+ passwordFile = mkOption {
+ type = types.str;
+ default = "";
+ description = "File that containts password";
+ };
+ };
+
+ };
+ };
+
+ ### implementation
+
+ config = mkIf (cfg.server.enable || cfg.clients != {}) {
+ boot.kernel.sysctl = optionalAttrs cfg.server.respondToSystemPings {
+ "net.ipv4.icmp_echo_ignore_all" = 1;
+ };
+
+ boot.kernelModules = [ "tun" ];
+
+ systemd.services =
+ let
+ createHansClientService = name: cfg:
+ {
+ description = "hans client - ${name}";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ script = "${pkgs.hans}/bin/hans -f -u ${hansUser} ${cfg.extraConfig} -c ${cfg.server} ${optionalString (cfg.passwordFile != "") "-p $(cat \"${cfg.passwordFile}\")"}";
+ serviceConfig = {
+ RestartSec = "30s";
+ Restart = "always";
+ };
+ };
+ in
+ listToAttrs (
+ mapAttrsToList
+ (name: value: nameValuePair "hans-${name}" (createHansClientService name value))
+ cfg.clients
+ ) // {
+ hans = mkIf (cfg.server.enable) {
+ description = "hans, ip over icmp server daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ script = "${pkgs.hans}/bin/hans -f -u ${hansUser} ${cfg.server.extraConfig} -s ${cfg.server.ip} ${optionalString cfg.server.respondToSystemPings "-r"} ${optionalString (cfg.server.passwordFile != "") "-p $(cat \"${cfg.server.passwordFile}\")"}";
+ };
+ };
+
+ users.users = singleton {
+ name = hansUser;
+ description = "Hans daemon user";
+ };
+ };
+
+ meta.maintainers = with maintainers; [ gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/haproxy.nix b/nixpkgs/nixos/modules/services/networking/haproxy.nix
new file mode 100644
index 00000000000..0438d0bf8d8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/haproxy.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.services.haproxy;
+ haproxyCfg = pkgs.writeText "haproxy.conf" cfg.config;
+in
+with lib;
+{
+ options = {
+ services.haproxy = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable HAProxy, the reliable, high performance TCP/HTTP
+ load balancer.
+ '';
+ };
+
+ config = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Contents of the HAProxy configuration file,
+ <filename>haproxy.conf</filename>.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [{
+ assertion = cfg.config != null;
+ message = "You must provide services.haproxy.config.";
+ }];
+
+ systemd.services.haproxy = {
+ description = "HAProxy";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "forking";
+ PIDFile = "/run/haproxy.pid";
+ ExecStartPre = "${pkgs.haproxy}/sbin/haproxy -c -q -f ${haproxyCfg}";
+ ExecStart = "${pkgs.haproxy}/sbin/haproxy -D -f ${haproxyCfg} -p /run/haproxy.pid";
+ ExecReload = "-${pkgs.bash}/bin/bash -c \"exec ${pkgs.haproxy}/sbin/haproxy -D -f ${haproxyCfg} -p /run/haproxy.pid -sf $MAINPID\"";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.haproxy ];
+
+ users.users.haproxy = {
+ group = "haproxy";
+ uid = config.ids.uids.haproxy;
+ };
+
+ users.groups.haproxy.gid = config.ids.uids.haproxy;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/heyefi.nix b/nixpkgs/nixos/modules/services/networking/heyefi.nix
new file mode 100644
index 00000000000..fc2b5a84857
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/heyefi.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.heyefi;
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.heyefi = {
+
+ enable = mkEnableOption "heyefi";
+
+ cardMacaddress = mkOption {
+ default = "";
+ description = ''
+ An Eye-Fi card MAC address.
+ '';
+ };
+
+ uploadKey = mkOption {
+ default = "";
+ description = ''
+ An Eye-Fi card's upload key.
+ '';
+ };
+
+ uploadDir = mkOption {
+ example = "/home/username/pictures";
+ description = ''
+ The directory to upload the files to.
+ '';
+ };
+
+ user = mkOption {
+ default = "root";
+ description = ''
+ heyefi will be run under this user (user must exist,
+ this can be your user name).
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.heyefi =
+ {
+ description = "heyefi service";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "${cfg.user}";
+ Restart = "always";
+ ExecStart = "${pkgs.heyefi}/bin/heyefi";
+ };
+
+ };
+
+ environment.etc."heyefi/heyefi.config".text =
+ ''
+ # /etc/heyefi/heyefi.conf: DO NOT EDIT -- this file has been generated automatically.
+ cards = [["${config.services.heyefi.cardMacaddress}","${config.services.heyefi.uploadKey}"]]
+ upload_dir = "${toString config.services.heyefi.uploadDir}"
+ '';
+
+ environment.systemPackages = [ pkgs.heyefi ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hostapd.nix b/nixpkgs/nixos/modules/services/networking/hostapd.nix
new file mode 100644
index 00000000000..2915b54f05b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hostapd.nix
@@ -0,0 +1,182 @@
+{ config, lib, pkgs, utils, ... }:
+
+# TODO:
+#
+# asserts
+# ensure that the nl80211 module is loaded/compiled in the kernel
+# wpa_supplicant and hostapd on the same wireless interface doesn't make any sense
+
+with lib;
+
+let
+
+ cfg = config.services.hostapd;
+
+ escapedInterface = utils.escapeSystemdPath cfg.interface;
+
+ configFile = pkgs.writeText "hostapd.conf" ''
+ interface=${cfg.interface}
+ driver=${cfg.driver}
+ ssid=${cfg.ssid}
+ hw_mode=${cfg.hwMode}
+ channel=${toString cfg.channel}
+
+ # logging (debug level)
+ logger_syslog=-1
+ logger_syslog_level=2
+ logger_stdout=-1
+ logger_stdout_level=2
+
+ ctrl_interface=/run/hostapd
+ ctrl_interface_group=${cfg.group}
+
+ ${optionalString cfg.wpa ''
+ wpa=2
+ wpa_passphrase=${cfg.wpaPassphrase}
+ ''}
+ ${optionalString cfg.noScan "noscan=1"}
+
+ ${cfg.extraConfig}
+ '' ;
+
+in
+
+{
+ ###### interface
+
+ options = {
+
+ services.hostapd = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Enable putting a wireless interface into infrastructure mode,
+ allowing other wireless devices to associate with the wireless
+ interface and do wireless networking. A simple access point will
+ <option>enable hostapd.wpa</option>,
+ <option>hostapd.wpaPassphrase</option>, and
+ <option>hostapd.ssid</option>, as well as DHCP on the wireless
+ interface to provide IP addresses to the associated stations, and
+ NAT (from the wireless interface to an upstream interface).
+ '';
+ };
+
+ interface = mkOption {
+ default = "";
+ example = "wlp2s0";
+ description = ''
+ The interfaces <command>hostapd</command> will use.
+ '';
+ };
+
+ noScan = mkOption {
+ default = false;
+ description = ''
+ Do not scan for overlapping BSSs in HT40+/- mode.
+ Caution: turning this on will violate regulatory requirements!
+ '';
+ };
+
+ driver = mkOption {
+ default = "nl80211";
+ example = "hostapd";
+ type = types.str;
+ description = ''
+ Which driver <command>hostapd</command> will use.
+ Most applications will probably use the default.
+ '';
+ };
+
+ ssid = mkOption {
+ default = "nixos";
+ example = "mySpecialSSID";
+ type = types.str;
+ description = "SSID to be used in IEEE 802.11 management frames.";
+ };
+
+ hwMode = mkOption {
+ default = "g";
+ type = types.enum [ "a" "b" "g" ];
+ description = ''
+ Operation mode.
+ (a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g).
+ '';
+ };
+
+ channel = mkOption {
+ default = 7;
+ example = 11;
+ type = types.int;
+ description = ''
+ Channel number (IEEE 802.11)
+ Please note that some drivers do not use this value from
+ <command>hostapd</command> and the channel will need to be configured
+ separately with <command>iwconfig</command>.
+ '';
+ };
+
+ group = mkOption {
+ default = "wheel";
+ example = "network";
+ type = types.str;
+ description = ''
+ Members of this group can control <command>hostapd</command>.
+ '';
+ };
+
+ wpa = mkOption {
+ default = true;
+ description = ''
+ Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point.
+ '';
+ };
+
+ wpaPassphrase = mkOption {
+ default = "my_sekret";
+ example = "any_64_char_string";
+ type = types.str;
+ description = ''
+ WPA-PSK (pre-shared-key) passphrase. Clients will need this
+ passphrase to associate with this access point.
+ Warning: This passphrase will get put into a world-readable file in
+ the Nix store!
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ example = ''
+ auth_algo=0
+ ieee80211n=1
+ ht_capab=[HT40-][SHORT-GI-40][DSSS_CCK-40]
+ '';
+ type = types.lines;
+ description = "Extra configuration options to put in hostapd.conf.";
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.hostapd ];
+
+ systemd.services.hostapd =
+ { description = "hostapd wireless AP";
+
+ path = [ pkgs.hostapd ];
+ after = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
+ bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
+ requiredBy = [ "network-link-${cfg.interface}.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig =
+ { ExecStart = "${pkgs.hostapd}/bin/hostapd ${configFile}";
+ Restart = "always";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/htpdate.nix b/nixpkgs/nixos/modules/services/networking/htpdate.nix
new file mode 100644
index 00000000000..6954e5b060c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/htpdate.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ inherit (pkgs) htpdate;
+
+ cfg = config.services.htpdate;
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.htpdate = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable htpdate daemon.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Additional command line arguments to pass to htpdate.
+ '';
+ };
+
+ servers = mkOption {
+ type = types.listOf types.str;
+ default = [ "www.google.com" ];
+ description = ''
+ HTTP servers to use for time synchronization.
+ '';
+ };
+
+ proxy = mkOption {
+ type = types.str;
+ default = "";
+ example = "127.0.0.1:8118";
+ description = ''
+ HTTP proxy used for requests.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.htpdate = {
+ description = "htpdate daemon";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "forking";
+ PIDFile = "/run/htpdate.pid";
+ ExecStart = concatStringsSep " " [
+ "${htpdate}/bin/htpdate"
+ "-D -u nobody"
+ "-a -s"
+ "-l"
+ "${optionalString (cfg.proxy != "") "-P ${cfg.proxy}"}"
+ "${cfg.extraOptions}"
+ "${concatStringsSep " " cfg.servers}"
+ ];
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/default.nix b/nixpkgs/nixos/modules/services/networking/hylafax/default.nix
new file mode 100644
index 00000000000..d8ffa3fc04d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/default.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+{
+
+ imports = [
+ ./options.nix
+ ./systemd.nix
+ ];
+
+ config = lib.modules.mkIf config.services.hylafax.enable {
+ environment.systemPackages = [ pkgs.hylafaxplus ];
+ users.users.uucp = {
+ uid = config.ids.uids.uucp;
+ group = "uucp";
+ description = "Unix-to-Unix CoPy system";
+ isSystemUser = true;
+ inherit (config.users.users.nobody) home;
+ };
+ assertions = [{
+ assertion = config.services.hylafax.modems != {};
+ message = ''
+ HylaFAX cannot be used without modems.
+ Please define at least one modem with
+ <option>config.services.hylafax.modems</option>.
+ '';
+ }];
+ };
+
+ meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/faxq-default.nix b/nixpkgs/nixos/modules/services/networking/hylafax/faxq-default.nix
new file mode 100644
index 00000000000..9b634650cf7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/faxq-default.nix
@@ -0,0 +1,12 @@
+{ ... }:
+
+# see man:hylafax-config(5)
+
+{
+
+ ModemGroup = [ ''"any:0:.*"'' ];
+ ServerTracing = "0x78701";
+ SessionTracing = "0x78701";
+ UUCPLockDir = "/var/lock";
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/faxq-wait.sh b/nixpkgs/nixos/modules/services/networking/hylafax/faxq-wait.sh
new file mode 100755
index 00000000000..8c39e9d20c1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/faxq-wait.sh
@@ -0,0 +1,29 @@
+#! @shell@ -e
+
+# skip this if there are no modems at all
+if ! stat -t "@spoolAreaPath@"/etc/config.* >/dev/null 2>&1
+then
+ exit 0
+fi
+
+echo "faxq started, waiting for modem(s) to initialize..."
+
+for i in `seq @timeoutSec@0 -1 0` # gracefully timeout
+do
+ sleep 0.1
+ # done if status files exist, but don't mention initialization
+ if \
+ stat -t "@spoolAreaPath@"/status/* >/dev/null 2>&1 \
+ && \
+ ! grep --silent --ignore-case 'initializing server' \
+ "@spoolAreaPath@"/status/*
+ then
+ echo "modem(s) apparently ready"
+ exit 0
+ fi
+ # if i reached 0, modems probably failed to initialize
+ if test $i -eq 0
+ then
+ echo "warning: modem initialization timed out"
+ fi
+done
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/hfaxd-default.nix b/nixpkgs/nixos/modules/services/networking/hylafax/hfaxd-default.nix
new file mode 100644
index 00000000000..8999dae57f4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/hfaxd-default.nix
@@ -0,0 +1,10 @@
+{ ... }:
+
+# see man:hfaxd(8)
+
+{
+
+ ServerTracing = "0x91";
+ XferLogFile = "/clientlog";
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/modem-default.nix b/nixpkgs/nixos/modules/services/networking/hylafax/modem-default.nix
new file mode 100644
index 00000000000..7529b5b0aaf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/modem-default.nix
@@ -0,0 +1,22 @@
+{ pkgs, ... }:
+
+# see man:hylafax-config(5)
+
+{
+
+ TagLineFont = "etc/LiberationSans-25.pcf";
+ TagLineLocale = ''en_US.UTF-8'';
+
+ AdminGroup = "root"; # groups that can change server config
+ AnswerRotary = "fax"; # don't accept anything else but faxes
+ LogFileMode = "0640";
+ PriorityScheduling = true;
+ RecvFileMode = "0640";
+ ServerTracing = "0x78701";
+ SessionTracing = "0x78701";
+ UUCPLockDir = "/var/lock";
+
+ SendPageCmd = ''${pkgs.coreutils}/bin/false''; # prevent pager transmit
+ SendUUCPCmd = ''${pkgs.coreutils}/bin/false''; # prevent UUCP transmit
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/options.nix b/nixpkgs/nixos/modules/services/networking/hylafax/options.nix
new file mode 100644
index 00000000000..4ac6d3fa843
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/options.nix
@@ -0,0 +1,375 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+ inherit (lib.options) literalExample mkEnableOption mkOption;
+ inherit (lib.types) bool enum int lines loaOf nullOr path str submodule;
+ inherit (lib.modules) mkDefault mkIf mkMerge;
+
+ commonDescr = ''
+ Values can be either strings or integers
+ (which will be added to the config file verbatimly)
+ or lists thereof
+ (which will be translated to multiple
+ lines with the same configuration key).
+ Boolean values are translated to "Yes" or "No".
+ The default contains some reasonable
+ configuration to yield an operational system.
+ '';
+
+ str1 = lib.types.addCheck str (s: s!=""); # non-empty string
+ int1 = lib.types.addCheck int (i: i>0); # positive integer
+
+ configAttrType =
+ # Options in HylaFAX configuration files can be
+ # booleans, strings, integers, or list thereof
+ # representing multiple config directives with the same key.
+ # This type definition resolves all
+ # those types into a list of strings.
+ let
+ inherit (lib.types) attrsOf coercedTo listOf;
+ innerType = coercedTo bool (x: if x then "Yes" else "No")
+ (coercedTo int (toString) str);
+ in
+ attrsOf (coercedTo innerType lib.singleton (listOf innerType));
+
+ cfg = config.services.hylafax;
+
+ modemConfigOptions = { name, config, ... }: {
+ options = {
+ name = mkOption {
+ type = str1;
+ example = "ttyS1";
+ description = ''
+ Name of modem device,
+ will be searched for in <filename>/dev</filename>.
+ '';
+ };
+ type = mkOption {
+ type = str1;
+ example = "cirrus";
+ description = ''
+ Name of modem configuration file,
+ will be searched for in <filename>config</filename>
+ in the spooling area directory.
+ '';
+ };
+ config = mkOption {
+ type = configAttrType;
+ example = {
+ AreaCode = "49";
+ LocalCode = "30";
+ FAXNumber = "123456";
+ LocalIdentifier = "LostInBerlin";
+ };
+ description = ''
+ Attribute set of values for the given modem.
+ ${commonDescr}
+ Options defined here override options in
+ <option>commonModemConfig</option> for this modem.
+ '';
+ };
+ };
+ config.name = mkDefault name;
+ config.config.Include = [ "config/${config.type}" ];
+ };
+
+ defaultConfig =
+ let
+ inherit (config.security) wrapperDir;
+ inherit (config.services.mail.sendmailSetuidWrapper) program;
+ mkIfDefault = cond: value: mkIf cond (mkDefault value);
+ noWrapper = config.services.mail.sendmailSetuidWrapper==null;
+ # If a sendmail setuid wrapper exists,
+ # we add the path to the default configuration file.
+ # Otherwise, we use `false` to provoke
+ # an error if hylafax tries to use it.
+ c.sendmailPath = mkMerge [
+ (mkIfDefault noWrapper ''${pkgs.coreutils}/bin/false'')
+ (mkIfDefault (!noWrapper) ''${wrapperDir}/${program}'')
+ ];
+ importDefaultConfig = file:
+ lib.attrsets.mapAttrs
+ (lib.trivial.const mkDefault)
+ (import file { inherit pkgs; });
+ c.commonModemConfig = importDefaultConfig ./modem-default.nix;
+ c.faxqConfig = importDefaultConfig ./faxq-default.nix;
+ c.hfaxdConfig = importDefaultConfig ./hfaxd-default.nix;
+ in
+ c;
+
+ localConfig =
+ let
+ c.hfaxdConfig.UserAccessFile = cfg.userAccessFile;
+ c.faxqConfig = lib.attrsets.mapAttrs
+ (lib.trivial.const (v: mkIf (v!=null) v))
+ {
+ AreaCode = cfg.areaCode;
+ CountryCode = cfg.countryCode;
+ LongDistancePrefix = cfg.longDistancePrefix;
+ InternationalPrefix = cfg.internationalPrefix;
+ };
+ c.commonModemConfig = c.faxqConfig;
+ in
+ c;
+
+in
+
+
+{
+
+
+ options.services.hylafax = {
+
+ enable = mkEnableOption ''HylaFAX server'';
+
+ autostart = mkOption {
+ type = bool;
+ default = true;
+ example = false;
+ description = ''
+ Autostart the HylaFAX queue manager at system start.
+ If this is <literal>false</literal>, the queue manager
+ will still be started if there are pending
+ jobs or if a user tries to connect to it.
+ '';
+ };
+
+ countryCode = mkOption {
+ type = nullOr str1;
+ default = null;
+ example = "49";
+ description = ''Country code for server and all modems.'';
+ };
+
+ areaCode = mkOption {
+ type = nullOr str1;
+ default = null;
+ example = "30";
+ description = ''Area code for server and all modems.'';
+ };
+
+ longDistancePrefix = mkOption {
+ type = nullOr str;
+ default = null;
+ example = "0";
+ description = ''Long distance prefix for server and all modems.'';
+ };
+
+ internationalPrefix = mkOption {
+ type = nullOr str;
+ default = null;
+ example = "00";
+ description = ''International prefix for server and all modems.'';
+ };
+
+ spoolAreaPath = mkOption {
+ type = path;
+ default = "/var/spool/fax";
+ description = ''
+ The spooling area will be created/maintained
+ at the location given here.
+ '';
+ };
+
+ userAccessFile = mkOption {
+ type = path;
+ default = "/etc/hosts.hfaxd";
+ description = ''
+ The <filename>hosts.hfaxd</filename>
+ file entry in the spooling area
+ will be symlinked to the location given here.
+ This file must exist and be
+ readable only by the <literal>uucp</literal> user.
+ See hosts.hfaxd(5) for details.
+ This configuration permits access for all users:
+ <literal>
+ environment.etc."hosts.hfaxd" = {
+ mode = "0600";
+ user = "uucp";
+ text = ".*";
+ };
+ </literal>
+ Note that host-based access can be controlled with
+ <option>config.systemd.sockets.hylafax-hfaxd.listenStreams</option>;
+ by default, only 127.0.0.1 is permitted to connect.
+ '';
+ };
+
+ sendmailPath = mkOption {
+ type = path;
+ example = literalExample "''${pkgs.postfix}/bin/sendmail";
+ # '' ; # fix vim
+ description = ''
+ Path to <filename>sendmail</filename> program.
+ The default uses the local sendmail wrapper
+ (see <option>config.services.mail.sendmailSetuidWrapper</option>),
+ otherwise the <filename>false</filename>
+ binary to cause an error if used.
+ '';
+ };
+
+ hfaxdConfig = mkOption {
+ type = configAttrType;
+ example.RecvqProtection = "0400";
+ description = ''
+ Attribute set of lines for the global
+ hfaxd config file <filename>etc/hfaxd.conf</filename>.
+ ${commonDescr}
+ '';
+ };
+
+ faxqConfig = mkOption {
+ type = configAttrType;
+ example = {
+ InternationalPrefix = "00";
+ LongDistancePrefix = "0";
+ };
+ description = ''
+ Attribute set of lines for the global
+ faxq config file <filename>etc/config</filename>.
+ ${commonDescr}
+ '';
+ };
+
+ commonModemConfig = mkOption {
+ type = configAttrType;
+ example = {
+ InternationalPrefix = "00";
+ LongDistancePrefix = "0";
+ };
+ description = ''
+ Attribute set of default values for
+ modem config files <filename>etc/config.*</filename>.
+ ${commonDescr}
+ Think twice before changing
+ paths of fax-processing scripts.
+ '';
+ };
+
+ modems = mkOption {
+ type = loaOf (submodule [ modemConfigOptions ]);
+ default = {};
+ example.ttyS1 = {
+ type = "cirrus";
+ config = {
+ FAXNumber = "123456";
+ LocalIdentifier = "Smith";
+ };
+ };
+ description = ''
+ Description of installed modems.
+ At least on modem must be defined
+ to enable the HylaFAX server.
+ '';
+ };
+
+ spoolExtraInit = mkOption {
+ type = lines;
+ default = "";
+ example = ''chmod 0755 . # everyone may read my faxes'';
+ description = ''
+ Additional shell code that is executed within the
+ spooling area directory right after its setup.
+ '';
+ };
+
+ faxcron.enable.spoolInit = mkEnableOption ''
+ Purge old files from the spooling area with
+ <filename>faxcron</filename>
+ each time the spooling area is initialized.
+ '';
+ faxcron.enable.frequency = mkOption {
+ type = nullOr str1;
+ default = null;
+ example = "daily";
+ description = ''
+ Purge old files from the spooling area with
+ <filename>faxcron</filename> with the given frequency
+ (see systemd.time(7)).
+ '';
+ };
+ faxcron.infoDays = mkOption {
+ type = int1;
+ default = 30;
+ description = ''
+ Set the expiration time for data in the
+ remote machine information directory in days.
+ '';
+ };
+ faxcron.logDays = mkOption {
+ type = int1;
+ default = 30;
+ description = ''
+ Set the expiration time for
+ session trace log files in days.
+ '';
+ };
+ faxcron.rcvDays = mkOption {
+ type = int1;
+ default = 7;
+ description = ''
+ Set the expiration time for files in
+ the received facsimile queue in days.
+ '';
+ };
+
+ faxqclean.enable.spoolInit = mkEnableOption ''
+ Purge old files from the spooling area with
+ <filename>faxqclean</filename>
+ each time the spooling area is initialized.
+ '';
+ faxqclean.enable.frequency = mkOption {
+ type = nullOr str1;
+ default = null;
+ example = "daily";
+ description = ''
+ Purge old files from the spooling area with
+ <filename>faxcron</filename> with the given frequency
+ (see systemd.time(7)).
+ '';
+ };
+ faxqclean.archiving = mkOption {
+ type = enum [ "never" "as-flagged" "always" ];
+ default = "as-flagged";
+ example = "always";
+ description = ''
+ Enable or suppress job archiving:
+ <literal>never</literal> disables job archiving,
+ <literal>as-flagged</literal> archives jobs that
+ have been flagged for archiving by sendfax,
+ <literal>always</literal> forces archiving of all jobs.
+ See also sendfax(1) and faxqclean(8).
+ '';
+ };
+ faxqclean.doneqMinutes = mkOption {
+ type = int1;
+ default = 15;
+ example = literalExample ''24*60'';
+ description = ''
+ Set the job
+ age threshold (in minutes) that controls how long
+ jobs may reside in the doneq directory.
+ '';
+ };
+ faxqclean.docqMinutes = mkOption {
+ type = int1;
+ default = 60;
+ example = literalExample ''24*60'';
+ description = ''
+ Set the document
+ age threshold (in minutes) that controls how long
+ unreferenced files may reside in the docq directory.
+ '';
+ };
+
+ };
+
+
+ config.services.hylafax =
+ mkIf
+ (config.services.hylafax.enable)
+ (mkMerge [ defaultConfig localConfig ])
+ ;
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/spool.sh b/nixpkgs/nixos/modules/services/networking/hylafax/spool.sh
new file mode 100755
index 00000000000..31e930e8c59
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/spool.sh
@@ -0,0 +1,111 @@
+#! @shell@ -e
+
+# The following lines create/update the HylaFAX spool directory:
+# Subdirectories/files with persistent data are kept,
+# other directories/files are removed/recreated,
+# mostly from the template spool
+# directory in the HylaFAX package.
+
+# This block explains how the spool area is
+# derived from the spool template in the HylaFAX package:
+#
+# + capital letter: directory; file otherwise
+# + P/p: persistent directory
+# + F/f: directory with symlinks per entry
+# + T/t: temporary data
+# + S/s: single symlink into package
+# |
+# | + u: change ownership to uucp:uucp
+# | + U: ..also change access mode to user-only
+# | |
+# archive P U
+# bin S
+# client T u (client connection info)
+# config S
+# COPYRIGHT s
+# dev T u (maybe some FIFOs)
+# docq P U
+# doneq P U
+# etc F contains customized config files!
+# etc/hosts.hfaxd f
+# etc/xferfaxlog f
+# info P u (database of called devices)
+# log P u (communication logs)
+# pollq P U
+# recvq P u
+# sendq P U
+# status T u (modem status info files)
+# tmp T U
+
+
+shopt -s dotglob # if bash sees "*", it also includes dot files
+lnsym () { ln --symbol "$@" ; }
+lnsymfrc () { ln --symbolic --force "$@" ; }
+cprd () { cp --remove-destination "$@" ; }
+update () { install --owner=@faxuser@ --group=@faxgroup@ "$@" ; }
+
+
+## create/update spooling area
+
+update --mode=0750 -d "@spoolAreaPath@"
+cd "@spoolAreaPath@"
+
+persist=(archive docq doneq info log pollq recvq sendq)
+
+# remove entries that don't belong here
+touch dummy # ensure "*" resolves to something
+for k in *
+do
+ keep=0
+ for j in "${persist[@]}" xferfaxlog clientlog faxcron.lastrun
+ do
+ if test "$k" == "$j"
+ then
+ keep=1
+ break
+ fi
+ done
+ if test "$keep" == "0"
+ then
+ rm --recursive "$k"
+ fi
+done
+
+# create persistent data directories (unless they exist already)
+update --mode=0700 -d "${persist[@]}"
+chmod 0755 info log recvq
+
+# create ``xferfaxlog``, ``faxcron.lastrun``, ``clientlog``
+touch clientlog faxcron.lastrun xferfaxlog
+chown @faxuser@:@faxgroup@ clientlog faxcron.lastrun xferfaxlog
+
+# create symlinks for frozen directories/files
+lnsym --target-directory=. "@hylafax@"/spool/{COPYRIGHT,bin,config}
+
+# create empty temporary directories
+update --mode=0700 -d client dev status
+update -d tmp
+
+
+## create and fill etc
+
+install -d "@spoolAreaPath@/etc"
+cd "@spoolAreaPath@/etc"
+
+# create symlinks to all files in template's etc
+lnsym --target-directory=. "@hylafax@/spool/etc"/*
+
+# set LOCKDIR in setup.cache
+sed --regexp-extended 's|^(UUCP_LOCKDIR=).*$|\1'"'@lockPath@'|g" --in-place setup.cache
+
+# etc/{xferfaxlog,lastrun} are stored in the spool root
+lnsymfrc --target-directory=. ../xferfaxlog
+lnsymfrc --no-target-directory ../faxcron.lastrun lastrun
+
+# etc/hosts.hfaxd is provided by the NixOS configuration
+lnsymfrc --no-target-directory "@userAccessFile@" hosts.hfaxd
+
+# etc/config and etc/config.${DEVID} must be copied:
+# hfaxd reads these file after locking itself up in a chroot
+cprd --no-target-directory "@globalConfigPath@" config
+cprd --target-directory=. "@modemConfigPath@"/*
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix b/nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix
new file mode 100644
index 00000000000..b9b9b9dca4f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix
@@ -0,0 +1,249 @@
+{ config, lib, pkgs, ... }:
+
+
+let
+
+ inherit (lib) mkIf mkMerge;
+ inherit (lib) concatStringsSep optionalString;
+
+ cfg = config.services.hylafax;
+ mapModems = lib.forEach (lib.attrValues cfg.modems);
+
+ mkConfigFile = name: conf:
+ # creates hylafax config file,
+ # makes sure "Include" is listed *first*
+ let
+ mkLines = conf:
+ (lib.concatLists
+ (lib.flip lib.mapAttrsToList conf
+ (k: map (v: ''${k}: ${v}'')
+ )));
+ include = mkLines { Include = conf.Include or []; };
+ other = mkLines ( conf // { Include = []; } );
+ in
+ pkgs.writeText ''hylafax-config${name}''
+ (concatStringsSep "\n" (include ++ other));
+
+ globalConfigPath = mkConfigFile "" cfg.faxqConfig;
+
+ modemConfigPath =
+ let
+ mkModemConfigFile = { config, name, ... }:
+ mkConfigFile ''.${name}''
+ (cfg.commonModemConfig // config);
+ mkLine = { name, type, ... }@modem: ''
+ # check if modem config file exists:
+ test -f "${pkgs.hylafaxplus}/spool/config/${type}"
+ ln \
+ --symbolic \
+ --no-target-directory \
+ "${mkModemConfigFile modem}" \
+ "$out/config.${name}"
+ '';
+ in
+ pkgs.runCommand "hylafax-config-modems" { preferLocalBuild = true; }
+ ''mkdir --parents "$out/" ${concatStringsSep "\n" (mapModems mkLine)}'';
+
+ setupSpoolScript = pkgs.substituteAll {
+ name = "hylafax-setup-spool.sh";
+ src = ./spool.sh;
+ isExecutable = true;
+ inherit (pkgs.stdenv) shell;
+ hylafax = pkgs.hylafaxplus;
+ faxuser = "uucp";
+ faxgroup = "uucp";
+ lockPath = "/var/lock";
+ inherit globalConfigPath modemConfigPath;
+ inherit (cfg) sendmailPath spoolAreaPath userAccessFile;
+ };
+
+ waitFaxqScript = pkgs.substituteAll {
+ # This script checks the modems status files
+ # and waits until all modems report readiness.
+ name = "hylafax-faxq-wait-start.sh";
+ src = ./faxq-wait.sh;
+ isExecutable = true;
+ timeoutSec = toString 10;
+ inherit (pkgs.stdenv) shell;
+ inherit (cfg) spoolAreaPath;
+ };
+
+ sockets.hylafax-hfaxd = {
+ description = "HylaFAX server socket";
+ documentation = [ "man:hfaxd(8)" ];
+ wantedBy = [ "multi-user.target" ];
+ listenStreams = [ "127.0.0.1:4559" ];
+ socketConfig.FreeBind = true;
+ socketConfig.Accept = true;
+ };
+
+ paths.hylafax-faxq = {
+ description = "HylaFAX queue manager sendq watch";
+ documentation = [ "man:faxq(8)" "man:sendq(5)" ];
+ wantedBy = [ "multi-user.target" ];
+ pathConfig.PathExistsGlob = [ ''${cfg.spoolAreaPath}/sendq/q*'' ];
+ };
+
+ timers = mkMerge [
+ (
+ mkIf (cfg.faxcron.enable.frequency!=null)
+ { hylafax-faxcron.timerConfig.Persistent = true; }
+ )
+ (
+ mkIf (cfg.faxqclean.enable.frequency!=null)
+ { hylafax-faxqclean.timerConfig.Persistent = true; }
+ )
+ ];
+
+ hardenService =
+ # Add some common systemd service hardening settings,
+ # but allow each service (here) to override
+ # settings by explicitely setting those to `null`.
+ # More hardening would be nice but makes
+ # customizing hylafax setups very difficult.
+ # If at all, it should only be added along
+ # with some options to customize it.
+ let
+ hardening = {
+ PrivateDevices = true; # breaks /dev/tty...
+ PrivateNetwork = true;
+ PrivateTmp = true;
+ ProtectControlGroups = true;
+ #ProtectHome = true; # breaks custom spool dirs
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ #ProtectSystem = "strict"; # breaks custom spool dirs
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ };
+ filter = key: value: (value != null) || ! (lib.hasAttr key hardening);
+ apply = service: lib.filterAttrs filter (hardening // (service.serviceConfig or {}));
+ in
+ service: service // { serviceConfig = apply service; };
+
+ services.hylafax-spool = {
+ description = "HylaFAX spool area preparation";
+ documentation = [ "man:hylafax-server(4)" ];
+ script = ''
+ ${setupSpoolScript}
+ cd "${cfg.spoolAreaPath}"
+ ${cfg.spoolExtraInit}
+ if ! test -f "${cfg.spoolAreaPath}/etc/hosts.hfaxd"
+ then
+ echo hosts.hfaxd is missing
+ exit 1
+ fi
+ '';
+ serviceConfig.ExecStop = ''${setupSpoolScript}'';
+ serviceConfig.RemainAfterExit = true;
+ serviceConfig.Type = "oneshot";
+ unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ];
+ };
+
+ services.hylafax-faxq = {
+ description = "HylaFAX queue manager";
+ documentation = [ "man:faxq(8)" ];
+ requires = [ "hylafax-spool.service" ];
+ after = [ "hylafax-spool.service" ];
+ wants = mapModems ( { name, ... }: ''hylafax-faxgetty@${name}.service'' );
+ wantedBy = mkIf cfg.autostart [ "multi-user.target" ];
+ serviceConfig.Type = "forking";
+ serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/faxq -q "${cfg.spoolAreaPath}"'';
+ # This delays the "readiness" of this service until
+ # all modems are initialized (or a timeout is reached).
+ # Otherwise, sending a fax with the fax service
+ # stopped will always yield a failed send attempt:
+ # The fax service is started when the job is created with
+ # `sendfax`, but modems need some time to initialize.
+ serviceConfig.ExecStartPost = [ ''${waitFaxqScript}'' ];
+ # faxquit fails if the pipe is already gone
+ # (e.g. the service is already stopping)
+ serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}"'';
+ # disable some systemd hardening settings
+ serviceConfig.PrivateDevices = null;
+ serviceConfig.RestrictRealtime = null;
+ };
+
+ services."hylafax-hfaxd@" = {
+ description = "HylaFAX server";
+ documentation = [ "man:hfaxd(8)" ];
+ after = [ "hylafax-faxq.service" ];
+ requires = [ "hylafax-faxq.service" ];
+ serviceConfig.StandardInput = "socket";
+ serviceConfig.StandardOutput = "socket";
+ serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/hfaxd -q "${cfg.spoolAreaPath}" -d -I'';
+ unitConfig.RequiresMountsFor = [ cfg.userAccessFile ];
+ # disable some systemd hardening settings
+ serviceConfig.PrivateDevices = null;
+ serviceConfig.PrivateNetwork = null;
+ };
+
+ services.hylafax-faxcron = rec {
+ description = "HylaFAX spool area maintenance";
+ documentation = [ "man:faxcron(8)" ];
+ after = [ "hylafax-spool.service" ];
+ requires = [ "hylafax-spool.service" ];
+ wantedBy = mkIf cfg.faxcron.enable.spoolInit requires;
+ startAt = mkIf (cfg.faxcron.enable.frequency!=null) cfg.faxcron.enable.frequency;
+ serviceConfig.ExecStart = concatStringsSep " " [
+ ''${pkgs.hylafaxplus}/spool/bin/faxcron''
+ ''-q "${cfg.spoolAreaPath}"''
+ ''-info ${toString cfg.faxcron.infoDays}''
+ ''-log ${toString cfg.faxcron.logDays}''
+ ''-rcv ${toString cfg.faxcron.rcvDays}''
+ ];
+ };
+
+ services.hylafax-faxqclean = rec {
+ description = "HylaFAX spool area queue cleaner";
+ documentation = [ "man:faxqclean(8)" ];
+ after = [ "hylafax-spool.service" ];
+ requires = [ "hylafax-spool.service" ];
+ wantedBy = mkIf cfg.faxqclean.enable.spoolInit requires;
+ startAt = mkIf (cfg.faxqclean.enable.frequency!=null) cfg.faxqclean.enable.frequency;
+ serviceConfig.ExecStart = concatStringsSep " " [
+ ''${pkgs.hylafaxplus}/spool/bin/faxqclean''
+ ''-q "${cfg.spoolAreaPath}"''
+ ''-v''
+ (optionalString (cfg.faxqclean.archiving!="never") ''-a'')
+ (optionalString (cfg.faxqclean.archiving=="always") ''-A'')
+ ''-j ${toString (cfg.faxqclean.doneqMinutes*60)}''
+ ''-d ${toString (cfg.faxqclean.docqMinutes*60)}''
+ ];
+ };
+
+ mkFaxgettyService = { name, ... }:
+ lib.nameValuePair ''hylafax-faxgetty@${name}'' rec {
+ description = "HylaFAX faxgetty for %I";
+ documentation = [ "man:faxgetty(8)" ];
+ bindsTo = [ "dev-%i.device" ];
+ requires = [ "hylafax-spool.service" ];
+ after = bindsTo ++ requires;
+ before = [ "hylafax-faxq.service" "getty.target" ];
+ unitConfig.StopWhenUnneeded = true;
+ unitConfig.AssertFileNotEmpty = ''${cfg.spoolAreaPath}/etc/config.%I'';
+ serviceConfig.UtmpIdentifier = "%I";
+ serviceConfig.TTYPath = "/dev/%I";
+ serviceConfig.Restart = "always";
+ serviceConfig.KillMode = "process";
+ serviceConfig.IgnoreSIGPIPE = false;
+ serviceConfig.ExecStart = ''-${pkgs.hylafaxplus}/spool/bin/faxgetty -q "${cfg.spoolAreaPath}" /dev/%I'';
+ # faxquit fails if the pipe is already gone
+ # (e.g. the service is already stopping)
+ serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}" %I'';
+ # disable some systemd hardening settings
+ serviceConfig.PrivateDevices = null;
+ serviceConfig.RestrictRealtime = null;
+ };
+
+ modemServices =
+ lib.listToAttrs (mapModems mkFaxgettyService);
+
+in
+
+{
+ config.systemd = mkIf cfg.enable {
+ inherit sockets timers paths;
+ services = lib.mapAttrs (lib.const hardenService) (services // modemServices);
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/i2p.nix b/nixpkgs/nixos/modules/services/networking/i2p.nix
new file mode 100644
index 00000000000..3b6010531f1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/i2p.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.i2p;
+ homeDir = "/var/lib/i2p";
+in {
+ ###### interface
+ options.services.i2p.enable = mkEnableOption "I2P router";
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ users.users.i2p = {
+ group = "i2p";
+ description = "i2p User";
+ home = homeDir;
+ createHome = true;
+ uid = config.ids.uids.i2p;
+ };
+ users.groups.i2p.gid = config.ids.gids.i2p;
+ systemd.services.i2p = {
+ description = "I2P router with administration interface for hidden services";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "i2p";
+ WorkingDirectory = homeDir;
+ Restart = "on-abort";
+ ExecStart = "${pkgs.i2p}/bin/i2prouter-plain";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/i2pd.nix b/nixpkgs/nixos/modules/services/networking/i2pd.nix
new file mode 100644
index 00000000000..f2be417738e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/i2pd.nix
@@ -0,0 +1,680 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.i2pd;
+
+ homeDir = "/var/lib/i2pd";
+
+ strOpt = k: v: k + " = " + v;
+ boolOpt = k: v: k + " = " + boolToString v;
+ intOpt = k: v: k + " = " + toString v;
+ lstOpt = k: xs: k + " = " + concatStringsSep "," xs;
+ optionalNullString = o: s: optional (s != null) (strOpt o s);
+ optionalNullBool = o: b: optional (b != null) (boolOpt o b);
+ optionalNullInt = o: i: optional (i != null) (intOpt o i);
+ optionalEmptyList = o: l: optional ([] != l) (lstOpt o l);
+
+ mkEnableTrueOption = name: mkEnableOption name // { default = true; };
+
+ mkEndpointOpt = name: addr: port: {
+ enable = mkEnableOption name;
+ name = mkOption {
+ type = types.str;
+ default = name;
+ description = "The endpoint name.";
+ };
+ address = mkOption {
+ type = types.str;
+ default = addr;
+ description = "Bind address for ${name} endpoint.";
+ };
+ port = mkOption {
+ type = types.int;
+ default = port;
+ description = "Bind port for ${name} endoint.";
+ };
+ };
+
+ i2cpOpts = name: {
+ length = mkOption {
+ type = types.int;
+ description = "Guaranteed minimum hops for ${name} tunnels.";
+ default = 3;
+ };
+ quantity = mkOption {
+ type = types.int;
+ description = "Number of simultaneous ${name} tunnels.";
+ default = 5;
+ };
+ };
+
+ mkKeyedEndpointOpt = name: addr: port: keyloc:
+ (mkEndpointOpt name addr port) // {
+ keys = mkOption {
+ type = with types; nullOr str;
+ default = keyloc;
+ description = ''
+ File to persist ${lib.toUpper name} keys.
+ '';
+ };
+ inbound = i2cpOpts name;
+ outbound = i2cpOpts name;
+ latency.min = mkOption {
+ type = with types; nullOr int;
+ description = "Min latency for tunnels.";
+ default = null;
+ };
+ latency.max = mkOption {
+ type = with types; nullOr int;
+ description = "Max latency for tunnels.";
+ default = null;
+ };
+ };
+
+ commonTunOpts = name: {
+ outbound = i2cpOpts name;
+ inbound = i2cpOpts name;
+ crypto.tagsToSend = mkOption {
+ type = types.int;
+ description = "Number of ElGamal/AES tags to send.";
+ default = 40;
+ };
+ destination = mkOption {
+ type = types.str;
+ description = "Remote endpoint, I2P hostname or b32.i2p address.";
+ };
+ keys = mkOption {
+ type = types.str;
+ default = name + "-keys.dat";
+ description = "Keyset used for tunnel identity.";
+ };
+ } // mkEndpointOpt name "127.0.0.1" 0;
+
+ sec = name: "\n[" + name + "]";
+ notice = "# DO NOT EDIT -- this file has been generated automatically.";
+ i2pdConf = let
+ opts = [
+ notice
+ (strOpt "loglevel" cfg.logLevel)
+ (boolOpt "logclftime" cfg.logCLFTime)
+ (boolOpt "ipv4" cfg.enableIPv4)
+ (boolOpt "ipv6" cfg.enableIPv6)
+ (boolOpt "notransit" cfg.notransit)
+ (boolOpt "floodfill" cfg.floodfill)
+ (intOpt "netid" cfg.netid)
+ ] ++ (optionalNullInt "bandwidth" cfg.bandwidth)
+ ++ (optionalNullInt "port" cfg.port)
+ ++ (optionalNullString "family" cfg.family)
+ ++ (optionalNullString "datadir" cfg.dataDir)
+ ++ (optionalNullInt "share" cfg.share)
+ ++ (optionalNullBool "ssu" cfg.ssu)
+ ++ (optionalNullBool "ntcp" cfg.ntcp)
+ ++ (optionalNullString "ntcpproxy" cfg.ntcpProxy)
+ ++ (optionalNullString "ifname" cfg.ifname)
+ ++ (optionalNullString "ifname4" cfg.ifname4)
+ ++ (optionalNullString "ifname6" cfg.ifname6)
+ ++ [
+ (sec "limits")
+ (intOpt "transittunnels" cfg.limits.transittunnels)
+ (intOpt "coresize" cfg.limits.coreSize)
+ (intOpt "openfiles" cfg.limits.openFiles)
+ (intOpt "ntcphard" cfg.limits.ntcpHard)
+ (intOpt "ntcpsoft" cfg.limits.ntcpSoft)
+ (intOpt "ntcpthreads" cfg.limits.ntcpThreads)
+ (sec "upnp")
+ (boolOpt "enabled" cfg.upnp.enable)
+ (sec "precomputation")
+ (boolOpt "elgamal" cfg.precomputation.elgamal)
+ (sec "reseed")
+ (boolOpt "verify" cfg.reseed.verify)
+ ] ++ (optionalNullString "file" cfg.reseed.file)
+ ++ (optionalEmptyList "urls" cfg.reseed.urls)
+ ++ (optionalNullString "floodfill" cfg.reseed.floodfill)
+ ++ (optionalNullString "zipfile" cfg.reseed.zipfile)
+ ++ (optionalNullString "proxy" cfg.reseed.proxy)
+ ++ [
+ (sec "trust")
+ (boolOpt "enabled" cfg.trust.enable)
+ (boolOpt "hidden" cfg.trust.hidden)
+ ] ++ (optionalEmptyList "routers" cfg.trust.routers)
+ ++ (optionalNullString "family" cfg.trust.family)
+ ++ [
+ (sec "websockets")
+ (boolOpt "enabled" cfg.websocket.enable)
+ (strOpt "address" cfg.websocket.address)
+ (intOpt "port" cfg.websocket.port)
+ (sec "exploratory")
+ (intOpt "inbound.length" cfg.exploratory.inbound.length)
+ (intOpt "inbound.quantity" cfg.exploratory.inbound.quantity)
+ (intOpt "outbound.length" cfg.exploratory.outbound.length)
+ (intOpt "outbound.quantity" cfg.exploratory.outbound.quantity)
+ (sec "ntcp2")
+ (boolOpt "enabled" cfg.ntcp2.enable)
+ (boolOpt "published" cfg.ntcp2.published)
+ (intOpt "port" cfg.ntcp2.port)
+ (sec "addressbook")
+ (strOpt "defaulturl" cfg.addressbook.defaulturl)
+ ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions)
+ ++ (flip map
+ (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto)
+ (proto: let protoOpts = [
+ (sec proto.name)
+ (boolOpt "enabled" proto.enable)
+ (strOpt "address" proto.address)
+ (intOpt "port" proto.port)
+ ] ++ (if proto ? keys then optionalNullString "keys" proto.keys else [])
+ ++ (if proto ? auth then optionalNullBool "auth" proto.auth else [])
+ ++ (if proto ? user then optionalNullString "user" proto.user else [])
+ ++ (if proto ? pass then optionalNullString "pass" proto.pass else [])
+ ++ (if proto ? strictHeaders then optionalNullBool "strictheaders" proto.strictHeaders else [])
+ ++ (if proto ? hostname then optionalNullString "hostname" proto.hostname else [])
+ ++ (if proto ? outproxy then optionalNullString "outproxy" proto.outproxy else [])
+ ++ (if proto ? outproxyPort then optionalNullInt "outproxyport" proto.outproxyPort else [])
+ ++ (if proto ? outproxyEnable then optionalNullBool "outproxy.enabled" proto.outproxyEnable else []);
+ in (concatStringsSep "\n" protoOpts)
+ ));
+ in
+ pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts);
+
+ tunnelConf = let opts = [
+ notice
+ (flip map
+ (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
+ (tun: let outTunOpts = [
+ (sec tun.name)
+ "type = client"
+ (intOpt "port" tun.port)
+ (strOpt "destination" tun.destination)
+ ] ++ (if tun ? destinationPort then optionalNullInt "destinationport" tun.destinationPort else [])
+ ++ (if tun ? keys then
+ optionalNullString "keys" tun.keys else [])
+ ++ (if tun ? address then
+ optionalNullString "address" tun.address else [])
+ ++ (if tun ? inbound.length then
+ optionalNullInt "inbound.length" tun.inbound.length else [])
+ ++ (if tun ? inbound.quantity then
+ optionalNullInt "inbound.quantity" tun.inbound.quantity else [])
+ ++ (if tun ? outbound.length then
+ optionalNullInt "outbound.length" tun.outbound.length else [])
+ ++ (if tun ? outbound.quantity then
+ optionalNullInt "outbound.quantity" tun.outbound.quantity else [])
+ ++ (if tun ? crypto.tagsToSend then
+ optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend else []);
+ in concatStringsSep "\n" outTunOpts))
+ (flip map
+ (collect (tun: tun ? port && tun ? address) cfg.inTunnels)
+ (tun: let inTunOpts = [
+ (sec tun.name)
+ "type = server"
+ (intOpt "port" tun.port)
+ (strOpt "host" tun.address)
+ ] ++ (if tun ? destination then
+ optionalNullString "destination" tun.destination else [])
+ ++ (if tun ? keys then
+ optionalNullString "keys" tun.keys else [])
+ ++ (if tun ? inPort then
+ optionalNullInt "inport" tun.inPort else [])
+ ++ (if tun ? accessList then
+ optionalEmptyList "accesslist" tun.accessList else []);
+ in concatStringsSep "\n" inTunOpts))];
+ in pkgs.writeText "i2pd-tunnels.conf" opts;
+
+ i2pdSh = pkgs.writeScriptBin "i2pd" ''
+ #!/bin/sh
+ exec ${pkgs.i2pd}/bin/i2pd \
+ ${if cfg.address == null then "" else "--host="+cfg.address} \
+ --service \
+ --conf=${i2pdConf} \
+ --tunconf=${tunnelConf}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.i2pd = {
+
+ enable = mkEnableOption "I2Pd daemon" // {
+ description = ''
+ Enables I2Pd as a running service upon activation.
+ Please read http://i2pd.readthedocs.io/en/latest/ for further
+ configuration help.
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.enum ["debug" "info" "warn" "error"];
+ default = "error";
+ description = ''
+ The log level. <command>i2pd</command> defaults to "info"
+ but that generates copious amounts of log messages.
+
+ We default to "error" which is similar to the default log
+ level of <command>tor</command>.
+ '';
+ };
+
+ logCLFTime = mkEnableOption "Full CLF-formatted date and time to log";
+
+ address = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Your external IP or hostname.
+ '';
+ };
+
+ family = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Specify a family the router belongs to.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Alternative path to storage of i2pd data (RI, keys, peer profiles, ...)
+ '';
+ };
+
+ share = mkOption {
+ type = types.int;
+ default = 100;
+ description = ''
+ Limit of transit traffic from max bandwidth in percents.
+ '';
+ };
+
+ ifname = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Network interface to bind to.
+ '';
+ };
+
+ ifname4 = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ IPv4 interface to bind to.
+ '';
+ };
+
+ ifname6 = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ IPv6 interface to bind to.
+ '';
+ };
+
+ ntcpProxy = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Proxy URL for NTCP transport.
+ '';
+ };
+
+ ntcp = mkEnableTrueOption "ntcp";
+ ssu = mkEnableTrueOption "ssu";
+
+ notransit = mkEnableOption "notransit" // {
+ description = ''
+ Tells the router to not accept transit tunnels during startup.
+ '';
+ };
+
+ floodfill = mkEnableOption "floodfill" // {
+ description = ''
+ If the router is declared to be unreachable and needs introduction nodes.
+ '';
+ };
+
+ netid = mkOption {
+ type = types.int;
+ default = 2;
+ description = ''
+ I2P overlay netid.
+ '';
+ };
+
+ bandwidth = mkOption {
+ type = with types; nullOr int;
+ default = null;
+ description = ''
+ Set a router bandwidth limit integer in KBps.
+ If not set, <command>i2pd</command> defaults to 32KBps.
+ '';
+ };
+
+ port = mkOption {
+ type = with types; nullOr int;
+ default = null;
+ description = ''
+ I2P listen port. If no one is given the router will pick between 9111 and 30777.
+ '';
+ };
+
+ enableIPv4 = mkEnableTrueOption "IPv4 connectivity";
+ enableIPv6 = mkEnableOption "IPv6 connectivity";
+ nat = mkEnableTrueOption "NAT bypass";
+
+ upnp.enable = mkEnableOption "UPnP service discovery";
+ upnp.name = mkOption {
+ type = types.str;
+ default = "I2Pd";
+ description = ''
+ Name i2pd appears in UPnP forwardings list.
+ '';
+ };
+
+ precomputation.elgamal = mkEnableTrueOption "Precomputed ElGamal tables" // {
+ description = ''
+ Whenever to use precomputated tables for ElGamal.
+ <command>i2pd</command> defaults to <literal>false</literal>
+ to save 64M of memory (and looses some performance).
+
+ We default to <literal>true</literal> as that is what most
+ users want anyway.
+ '';
+ };
+
+ reseed.verify = mkEnableOption "SU3 signature verification";
+
+ reseed.file = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Full path to SU3 file to reseed from.
+ '';
+ };
+
+ reseed.urls = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = ''
+ Reseed URLs.
+ '';
+ };
+
+ reseed.floodfill = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Path to router info of floodfill to reseed from.
+ '';
+ };
+
+ reseed.zipfile = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Path to local .zip file to reseed from.
+ '';
+ };
+
+ reseed.proxy = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ URL for reseed proxy, supports http/socks.
+ '';
+ };
+
+ addressbook.defaulturl = mkOption {
+ type = types.str;
+ default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
+ description = ''
+ AddressBook subscription URL for initial setup
+ '';
+ };
+ addressbook.subscriptions = mkOption {
+ type = with types; listOf str;
+ default = [
+ "http://inr.i2p/export/alive-hosts.txt"
+ "http://i2p-projekt.i2p/hosts.txt"
+ "http://stats.i2p/cgi-bin/newhosts.txt"
+ ];
+ description = ''
+ AddressBook subscription URLs
+ '';
+ };
+
+ trust.enable = mkEnableOption "Explicit trust options";
+
+ trust.family = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Router Familiy to trust for first hops.
+ '';
+ };
+
+ trust.routers = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = ''
+ Only connect to the listed routers.
+ '';
+ };
+
+ trust.hidden = mkEnableOption "Router concealment";
+
+ websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
+
+ exploratory.inbound = i2cpOpts "exploratory";
+ exploratory.outbound = i2cpOpts "exploratory";
+
+ ntcp2.enable = mkEnableTrueOption "NTCP2.";
+ ntcp2.published = mkEnableOption "NTCP2 publication";
+ ntcp2.port = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Port to listen for incoming NTCP2 connections (0=auto).
+ '';
+ };
+
+ limits.transittunnels = mkOption {
+ type = types.int;
+ default = 2500;
+ description = ''
+ Maximum number of active transit sessions.
+ '';
+ };
+
+ limits.coreSize = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Maximum size of corefile in Kb (0 - use system limit).
+ '';
+ };
+
+ limits.openFiles = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Maximum number of open files (0 - use system default).
+ '';
+ };
+
+ limits.ntcpHard = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Maximum number of active transit sessions.
+ '';
+ };
+
+ limits.ntcpSoft = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Threshold to start probabalistic backoff with ntcp sessions (default: use system limit).
+ '';
+ };
+
+ limits.ntcpThreads = mkOption {
+ type = types.int;
+ default = 1;
+ description = ''
+ Maximum number of threads used by NTCP DH worker.
+ '';
+ };
+
+ proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
+
+ auth = mkEnableOption "Webconsole authentication";
+
+ user = mkOption {
+ type = types.str;
+ default = "i2pd";
+ description = ''
+ Username for webconsole access
+ '';
+ };
+
+ pass = mkOption {
+ type = types.str;
+ default = "i2pd";
+ description = ''
+ Password for webconsole access.
+ '';
+ };
+
+ strictHeaders = mkOption {
+ type = with types; nullOr bool;
+ default = null;
+ description = ''
+ Enable strict host checking on WebUI.
+ '';
+ };
+
+ hostname = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Expected hostname for WebUI.
+ '';
+ };
+ };
+
+ proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat")
+ // {
+ outproxy = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "Upstream outproxy bind address.";
+ };
+ };
+ proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat")
+ // {
+ outproxyEnable = mkEnableOption "SOCKS outproxy";
+ outproxy = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Upstream outproxy bind address.";
+ };
+ outproxyPort = mkOption {
+ type = types.int;
+ default = 4444;
+ description = "Upstream outproxy bind port.";
+ };
+ };
+
+ proto.sam = mkEndpointOpt "sam" "127.0.0.1" 7656;
+ proto.bob = mkEndpointOpt "bob" "127.0.0.1" 2827;
+ proto.i2cp = mkEndpointOpt "i2cp" "127.0.0.1" 7654;
+ proto.i2pControl = mkEndpointOpt "i2pcontrol" "127.0.0.1" 7650;
+
+ outTunnels = mkOption {
+ default = {};
+ type = with types; loaOf (submodule (
+ { name, ... }: {
+ options = {
+ destinationPort = mkOption {
+ type = with types; nullOr int;
+ default = null;
+ description = "Connect to particular port at destination.";
+ };
+ } // commonTunOpts name;
+ config = {
+ name = mkDefault name;
+ };
+ }
+ ));
+ description = ''
+ Connect to someone as a client and establish a local accept endpoint
+ '';
+ };
+
+ inTunnels = mkOption {
+ default = {};
+ type = with types; loaOf (submodule (
+ { name, ... }: {
+ options = {
+ inPort = mkOption {
+ type = types.int;
+ default = 0;
+ description = "Service port. Default to the tunnel's listen port.";
+ };
+ accessList = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = "I2P nodes that are allowed to connect to this service.";
+ };
+ } // commonTunOpts name;
+ config = {
+ name = mkDefault name;
+ };
+ }
+ ));
+ description = ''
+ Serve something on I2P network at port and delegate requests to address inPort.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users.i2pd = {
+ group = "i2pd";
+ description = "I2Pd User";
+ home = homeDir;
+ createHome = true;
+ uid = config.ids.uids.i2pd;
+ };
+
+ users.groups.i2pd.gid = config.ids.gids.i2pd;
+
+ systemd.services.i2pd = {
+ description = "Minimal I2P router";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig =
+ {
+ User = "i2pd";
+ WorkingDirectory = homeDir;
+ Restart = "on-abort";
+ ExecStart = "${i2pdSh}/bin/i2pd";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/iodine.nix b/nixpkgs/nixos/modules/services/networking/iodine.nix
new file mode 100644
index 00000000000..344f84374bb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/iodine.nix
@@ -0,0 +1,150 @@
+# NixOS module for iodine, ip over dns daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.iodine;
+
+ iodinedUser = "iodined";
+
+in
+{
+
+ ### configuration
+
+ options = {
+
+ services.iodine = {
+ clients = mkOption {
+ default = {};
+ description = ''
+ Each attribute of this option defines a systemd service that
+ runs iodine. Many or none may be defined.
+ The name of each service is
+ <literal>iodine-<replaceable>name</replaceable></literal>
+ where <replaceable>name</replaceable> is the name of the
+ corresponding attribute name.
+ '';
+ example = literalExample ''
+ {
+ foo = {
+ server = "tunnel.mdomain.com";
+ relay = "8.8.8.8";
+ extraConfig = "-v";
+ }
+ }
+ '';
+ type = types.attrsOf (types.submodule (
+ {
+ options = {
+ server = mkOption {
+ type = types.str;
+ default = "";
+ description = "Domain or Subdomain of server running iodined";
+ example = "tunnel.mydomain.com";
+ };
+
+ relay = mkOption {
+ type = types.str;
+ default = "";
+ description = "DNS server to use as a intermediate relay to the iodined server";
+ example = "8.8.8.8";
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = "Additional command line parameters";
+ example = "-l 192.168.1.10 -p 23";
+ };
+
+ passwordFile = mkOption {
+ type = types.str;
+ default = "";
+ description = "File that contains password";
+ };
+ };
+ }));
+ };
+
+ server = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "enable iodined server";
+ };
+
+ ip = mkOption {
+ type = types.str;
+ default = "";
+ description = "The assigned ip address or ip range";
+ example = "172.16.10.1/24";
+ };
+
+ domain = mkOption {
+ type = types.str;
+ default = "";
+ description = "Domain or subdomain of which nameservers point to us";
+ example = "tunnel.mydomain.com";
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = "Additional command line parameters";
+ example = "-l 192.168.1.10 -p 23";
+ };
+
+ passwordFile = mkOption {
+ type = types.str;
+ default = "";
+ description = "File that contains password";
+ };
+ };
+
+ };
+ };
+
+ ### implementation
+
+ config = mkIf (cfg.server.enable || cfg.clients != {}) {
+ environment.systemPackages = [ pkgs.iodine ];
+ boot.kernelModules = [ "tun" ];
+
+ systemd.services =
+ let
+ createIodineClientService = name: cfg:
+ {
+ description = "iodine client - ${name}";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ script = "exec ${pkgs.iodine}/bin/iodine -f -u ${iodinedUser} ${cfg.extraConfig} ${optionalString (cfg.passwordFile != "") "< \"${cfg.passwordFile}\""} ${cfg.relay} ${cfg.server}";
+ serviceConfig = {
+ RestartSec = "30s";
+ Restart = "always";
+ };
+ };
+ in
+ listToAttrs (
+ mapAttrsToList
+ (name: value: nameValuePair "iodine-${name}" (createIodineClientService name value))
+ cfg.clients
+ ) // {
+ iodined = mkIf (cfg.server.enable) {
+ description = "iodine, ip over dns server daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ script = "exec ${pkgs.iodine}/bin/iodined -f -u ${iodinedUser} ${cfg.server.extraConfig} ${optionalString (cfg.server.passwordFile != "") "< \"${cfg.server.passwordFile}\""} ${cfg.server.ip} ${cfg.server.domain}";
+ };
+ };
+
+ users.users = singleton {
+ name = iodinedUser;
+ uid = config.ids.uids.iodined;
+ description = "Iodine daemon user";
+ };
+ users.groups.iodined.gid = config.ids.gids.iodined;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/iperf3.nix b/nixpkgs/nixos/modules/services/networking/iperf3.nix
new file mode 100644
index 00000000000..0fe378b225d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/iperf3.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }: with lib;
+let
+ cfg = config.services.iperf3;
+
+ api = {
+ enable = mkEnableOption "iperf3 network throughput testing server";
+ port = mkOption {
+ type = types.ints.u16;
+ default = 5201;
+ description = "Server port to listen on for iperf3 client requsts.";
+ };
+ affinity = mkOption {
+ type = types.nullOr types.ints.unsigned;
+ default = null;
+ description = "CPU affinity for the process.";
+ };
+ bind = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Bind to the specific interface associated with the given address.";
+ };
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Open ports in the firewall for iperf3.";
+ };
+ verbose = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Give more detailed output.";
+ };
+ forceFlush = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Force flushing output at every interval.";
+ };
+ debug = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Emit debugging output.";
+ };
+ rsaPrivateKey = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "Path to the RSA private key (not password-protected) used to decrypt authentication credentials from the client.";
+ };
+ authorizedUsersFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "Path to the configuration file containing authorized users credentials to run iperf tests.";
+ };
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = "Extra flags to pass to iperf3(1).";
+ };
+ };
+
+ imp = {
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.port ];
+ };
+
+ systemd.services.iperf3 = {
+ description = "iperf3 daemon";
+ unitConfig.Documentation = "man:iperf3(1) https://iperf.fr/iperf-doc.php";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ Restart = "on-failure";
+ RestartSec = 2;
+ DynamicUser = true;
+ PrivateDevices = true;
+ CapabilityBoundingSet = "";
+ NoNewPrivileges = true;
+ ExecStart = ''
+ ${pkgs.iperf3}/bin/iperf \
+ --server \
+ --port ${toString cfg.port} \
+ ${optionalString (cfg.affinity != null) "--affinity ${toString cfg.affinity}"} \
+ ${optionalString (cfg.bind != null) "--bind ${cfg.bind}"} \
+ ${optionalString (cfg.rsaPrivateKey != null) "--rsa-private-key-path ${cfg.rsaPrivateKey}"} \
+ ${optionalString (cfg.authorizedUsersFile != null) "--authorized-users-path ${cfg.authorizedUsersFile}"} \
+ ${optionalString cfg.verbose "--verbose"} \
+ ${optionalString cfg.debug "--debug"} \
+ ${optionalString cfg.forceFlush "--forceflush"} \
+ ${escapeShellArgs cfg.extraFlags}
+ '';
+ };
+ };
+ };
+in {
+ options.services.iperf3 = api;
+ config = mkIf cfg.enable imp;
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh
new file mode 100644
index 00000000000..38312210df2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh
@@ -0,0 +1,31 @@
+source $stdenv/setup
+
+doSub() {
+ local src=$1
+ local dst=$2
+ mkdir -p $(dirname $dst)
+ substituteAll $src $dst
+}
+
+subDir=/
+for i in $scripts; do
+ if test "$(echo $i | cut -c1-2)" = "=>"; then
+ subDir=$(echo $i | cut -c3-)
+ else
+ dst=$out/$subDir/$(stripHash $i | sed 's/\.in//')
+ doSub $i $dst
+ chmod +x $dst # !!!
+ fi
+done
+
+subDir=/
+for i in $substFiles; do
+ if test "$(echo $i | cut -c1-2)" = "=>"; then
+ subDir=$(echo $i | cut -c3-)
+ else
+ dst=$out/$subDir/$(stripHash $i | sed 's/\.in//')
+ doSub $i $dst
+ fi
+done
+
+mkdir -p $out/bin
diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/control.in b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/control.in
new file mode 100644
index 00000000000..312dfaada32
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/control.in
@@ -0,0 +1,26 @@
+#! @shell@ -e
+
+# Make sure that the environment is deterministic.
+export PATH=@coreutils@/bin
+
+if test "$1" = "start"; then
+ if ! @procps@/bin/pgrep ircd; then
+ if @ipv6Enabled@; then
+ while ! @iproute@/sbin/ip addr |
+ @gnugrep@/bin/grep inet6 |
+ @gnugrep@/bin/grep global; do
+ sleep 1;
+ done;
+ fi;
+ rm -rf /home/ircd
+ mkdir -p /home/ircd
+ chown ircd: /home/ircd
+ cd /home/ircd
+ env - HOME=/homeless-shelter $extraEnv \
+ @su@/bin/su ircd --shell=/bin/sh -c ' @ircdHybrid@/bin/ircd -configfile @out@/conf/ircd.conf </dev/null -logfile /home/ircd/ircd.log' 2>&1 >/var/log/ircd-hybrid.out
+ fi;
+fi
+
+if test "$1" = "stop" ; then
+ @procps@/bin/pkill ircd;
+fi;
diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix
new file mode 100644
index 00000000000..f5abe61a1ba
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.ircdHybrid;
+
+ ircdService = pkgs.stdenv.mkDerivation rec {
+ name = "ircd-hybrid-service";
+ scripts = [ "=>/bin" ./control.in ];
+ substFiles = [ "=>/conf" ./ircd.conf ];
+ inherit (pkgs) ircdHybrid coreutils su iproute gnugrep procps;
+
+ ipv6Enabled = boolToString config.networking.enableIPv6;
+
+ inherit (cfg) serverName sid description adminEmail
+ extraPort;
+
+ cryptoSettings =
+ (optionalString (cfg.rsaKey != null) "rsa_private_key_file = \"${cfg.rsaKey}\";\n") +
+ (optionalString (cfg.certificate != null) "ssl_certificate_file = \"${cfg.certificate}\";\n");
+
+ extraListen = map (ip: "host = \""+ip+"\";\nport = 6665 .. 6669, "+extraPort+"; ") cfg.extraIPs;
+
+ builder = ./builder.sh;
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.ircdHybrid = {
+
+ enable = mkOption {
+ default = false;
+ description = "
+ Enable IRCD.
+ ";
+ };
+
+ serverName = mkOption {
+ default = "hades.arpa";
+ description = "
+ IRCD server name.
+ ";
+ };
+
+ sid = mkOption {
+ default = "0NL";
+ description = "
+ IRCD server unique ID in a net of servers.
+ ";
+ };
+
+ description = mkOption {
+ default = "Hybrid-7 IRC server.";
+ description = "
+ IRCD server description.
+ ";
+ };
+
+ rsaKey = mkOption {
+ default = null;
+ example = literalExample "/root/certificates/irc.key";
+ description = "
+ IRCD server RSA key.
+ ";
+ };
+
+ certificate = mkOption {
+ default = null;
+ example = literalExample "/root/certificates/irc.pem";
+ description = "
+ IRCD server SSL certificate. There are some limitations - read manual.
+ ";
+ };
+
+ adminEmail = mkOption {
+ default = "<bit-bucket@example.com>";
+ example = "<name@domain.tld>";
+ description = "
+ IRCD server administrator e-mail.
+ ";
+ };
+
+ extraIPs = mkOption {
+ default = [];
+ example = ["127.0.0.1"];
+ description = "
+ Extra IP's to bind.
+ ";
+ };
+
+ extraPort = mkOption {
+ default = "7117";
+ description = "
+ Extra port to avoid filtering.
+ ";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.ircdHybrid.enable {
+
+ users.users = singleton
+ { name = "ircd";
+ description = "IRCD owner";
+ group = "ircd";
+ uid = config.ids.uids.ircd;
+ };
+
+ users.groups.ircd.gid = config.ids.gids.ircd;
+
+ systemd.services.ircd-hybrid = {
+ description = "IRCD Hybrid server";
+ after = [ "started networking" ];
+ wantedBy = [ "multi-user.target" ];
+ script = "${ircdService}/bin/control start";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/ircd.conf b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/ircd.conf
new file mode 100644
index 00000000000..17ef203840a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/ircd.conf
@@ -0,0 +1,1051 @@
+/* doc/example.conf - ircd-hybrid-7 Example configuration file
+ * Copyright (C) 2000-2006 Hybrid Development Team
+ *
+ * Written by ejb, wcampbel, db, leeh and others
+ * Other example configurations can be found in the source dir under
+ * etc/.
+ *
+ * $Id: example.conf 639 2006-06-01 14:12:21Z michael $
+ */
+
+/* IMPORTANT NOTES:
+ *
+ * auth {} blocks MUST be specified in order of precedence. The first one
+ * that matches a user will be used. So place spoofs first, then specials,
+ * then general access.
+ *
+ * Shell style (#), C++ style (//) and C style comments are supported.
+ *
+ * Files may be included by either:
+ * .include "filename"
+ * .include <filename>
+ *
+ * Times/durations are written as:
+ * 12 hours 30 minutes 1 second
+ *
+ * Valid units of time:
+ * month, week, day, hour, minute, second
+ *
+ * Valid units of size:
+ * megabyte/mbyte/mb, kilobyte/kbyte/kb, byte
+ *
+ * Sizes and times may be singular or plural.
+ */
+
+/* EFNET NOTE:
+ *
+ * This config file is NOT suitable for EFNet. EFNet admins should use
+ * example.efnet.conf
+ */
+
+/*
+ * serverinfo {}: contains information about the server. (OLD M:)
+ */
+serverinfo {
+ /*
+ * name: the name of our server. This cannot be changed at runtime.
+ */
+ name = "@serverName@";
+
+ /*
+ * sid: a server's unique ID. This is three characters long and must
+ * be in the form [0-9][A-Z0-9][A-Z0-9]. The first character must be
+ * a digit, followed by 2 alpha-numerical letters.
+ * NOTE: The letters must be capitalized. This cannot be changed at runtime.
+ */
+ sid = "@sid@";
+
+ /*
+ * description: the description of the server. '[' and ']' may not
+ * be used here for compatibility with older servers.
+ */
+ description = "@description@";
+
+ /*
+ * network info: the name and description of the network this server
+ * is on. Shown in the 005 reply and used with serverhiding.
+ */
+ network_name = "JustIRCNetwork";
+ network_desc = "This is My Network";
+
+ /*
+ * hub: allow this server to act as a hub and have multiple servers
+ * connected to it. This may not be changed if there are active
+ * LazyLink servers.
+ */
+ hub = no;
+
+ /*
+ * vhost: the IP to bind to when we connect outward to ipv4 servers.
+ * This should be an ipv4 IP only, or "* for INADDR_ANY.
+ */
+ #vhost = "192.169.0.1";
+
+ /*
+ * vhost6: the IP to bind to when we connect outward to ipv6 servers.
+ * This should be an ipv6 IP only, or "* for INADDR_ANY.
+ */
+ #vhost6 = "3ffe:80e8:546::2";
+
+ /* max_clients: the maximum number of clients allowed to connect */
+ max_clients = 512;
+
+ /*
+ * rsa key: the path to the file containing our rsa key for cryptlink.
+ *
+ * Example command to store a 2048 bit RSA keypair in
+ * rsa.key, and the public key in rsa.pub:
+ *
+ * openssl genrsa -out rsa.key 2048
+ * openssl rsa -in rsa.key -pubout -out rsa.pub
+ * chown <ircd-user>.<ircd.group> rsa.key rsa.pub
+ * chmod 0600 rsa.key
+ * chmod 0644 rsa.pub
+ */
+ #rsa_private_key_file = "/usr/local/ircd/etc/rsa.key";
+
+ /*
+ * ssl certificate: the path to the file containing our ssl certificate
+ * for encrypted client connection.
+ *
+ * This assumes your private RSA key is stored in rsa.key. You
+ * MUST have an RSA key in order to generate the certificate
+ *
+ * openssl req -new -days 365 -x509 -key rsa.key -out cert.pem
+ *
+ * See http://www.openssl.org/docs/HOWTO/certificates.txt
+ *
+ * Please use the following values when generating the cert
+ *
+ * Organization Name: Network Name
+ * Organization Unit Name: changme.someirc.net
+ * Common Name: irc.someirc.net
+ * E-mail: you@domain.com
+ */
+ #ssl_certificate_file = "/usr/local/ircd/etc/cert.pem";
+
+ @cryptoSettings@
+};
+
+/*
+ * admin {}: contains admin information about the server. (OLD A:)
+ */
+admin {
+ name = "Anonymous Hero";
+ description = "Main Server Administrator";
+ email = "@adminEmail@";
+};
+
+/*
+ * log {}: contains information about logfiles.
+ */
+log {
+ /* Do you want to enable logging to ircd.log? */
+ use_logging = yes;
+
+ /*
+ * logfiles: the logfiles to use for user connects, /oper uses,
+ * and failed /oper. These files must exist for logging to be used.
+ */
+ fname_userlog = "/home/ircd/logs/userlog";
+ fname_operlog = "/home/ircd/logs/operlog";
+ fname_killlog = "/home/ircd/logs/kill";
+ fname_klinelog = "/home/ircd/logs/kline";
+ fname_glinelog = "/home/ircd/logs/gline";
+
+ /*
+ * log_level: the amount of detail to log in ircd.log. The
+ * higher, the more information is logged. May be changed
+ * once the server is running via /quote SET LOG. Either:
+ * L_CRIT, L_ERROR, L_WARN, L_NOTICE, L_TRACE, L_INFO or L_DEBUG
+ */
+ log_level = L_INFO;
+};
+
+/*
+ * class {}: contains information about classes for users (OLD Y:)
+ */
+class {
+ /* name: the name of the class. classes are text now */
+ name = "users";
+
+ /*
+ * ping_time: how often a client must reply to a PING from the
+ * server before they are dropped.
+ */
+ ping_time = 90 seconds;
+
+ /*
+ * number_per_ip: how many local users are allowed to connect
+ * from one IP (optional)
+ */
+ number_per_ip = 10;
+
+ /*
+ * max_local: how many local users are allowed to connect
+ * from one ident@host (optional)
+ */
+ max_local = 50;
+
+ /*
+ * max_global: network-wide limit of users per ident@host (optional)
+ */
+ max_global = 50;
+
+ /*
+ * max_number: the maximum number of users allowed in this class (optional)
+ */
+ max_number = 10000;
+
+ /*
+ * the following lines are optional and allow you to define
+ * how many users can connect from one /NN subnet
+ */
+ /*cidr_bitlen_ipv4 = 24;
+ *cidr_bitlen_ipv6 = 120;
+ *number_per_cidr = 16;*/
+
+ /*
+ * sendq: the amount of data allowed in a clients queue before
+ * they are dropped.
+ */
+ sendq = 100 kbytes;
+};
+
+class {
+ name = "opers";
+ ping_time = 90 seconds;
+ number_per_ip = 10;
+ max_number = 100;
+ sendq = 100kbytes;
+};
+
+class {
+ name = "server";
+ ping_time = 90 seconds;
+
+ /*
+ * ping_warning: how fast a server must reply to a PING before
+ * a warning to opers is generated.
+ */
+ ping_warning = 15 seconds;
+
+ /*
+ * connectfreq: only used in server classes. Specifies the delay
+ * between autoconnecting to servers.
+ */
+ connectfreq = 5 minutes;
+
+ /* max number: the amount of servers to autoconnect to */
+ max_number = 1;
+
+ /* sendq: servers need a higher sendq as they send more data */
+ sendq = 2 megabytes;
+};
+
+/*
+ * listen {}: contains information about the ports ircd listens on (OLD P:)
+ */
+listen {
+ /*
+ * port: the specific port to listen on. If no host is specified
+ * before, it will listen on all available IPs.
+ *
+ * Ports are separated via a comma, a range may be specified using ".."
+ */
+
+ /* port: listen on all available IPs, ports 6665 to 6669 */
+ port = 6665 .. 6669;
+
+ /*
+ * Listen on 192.168.0.1/6697 with ssl enabled and hidden from STATS P
+ * unless you are an administrator.
+ *
+ * NOTE: The "flags" directive has to come before "port". Always!
+ */
+ #flags = hidden, ssl;
+ #host = "192.168.0.1";
+ #port = 6697;
+
+ /*
+ * host: set a specific IP/host the ports after the line will listen
+ * on. This may be ipv4 or ipv6.
+ */
+ #host = "1.2.3.4";
+ #port = 7000, 7001;
+
+ #host = "3ffe:1234:a:b:c::d";
+ #port = 7002;
+
+ @extraListen@
+};
+
+auth {
+ user = "*@*";
+ class = "users";
+ #flags = need_ident;
+};
+
+/*
+ * operator {}: defines ircd operators. (OLD O:)
+ *
+ * ircd-hybrid no longer supports local operators, privileges are
+ * controlled via flags.
+ */
+operator {
+ /* name: the name of the oper */
+ /* NOTE: operator "opername"{} is also supported */
+ name = "god";
+
+ /*
+ * user: the user@host required for this operator. CIDR is not
+ * supported. Multiple user="" lines are supported.
+ */
+ user = "*god@*";
+ user = "*@127.0.0.1";
+
+ /*
+ * password: the password required to oper. By default this will
+ * need to be encrypted using 'mkpasswd'. MD5 is supported.
+ */
+ password = "iamoperator";
+
+ /*
+ * encrypted: controls whether the oper password above has been
+ * encrypted. (OLD CRYPT_OPER_PASSWORD now optional per operator)
+ */
+ encrypted = no;
+
+ /*
+ * rsa_public_key_file: the public key for this oper when using Challenge.
+ * A password should not be defined when this is used, see
+ * doc/challenge.txt for more information.
+ */
+# rsa_public_key_file = "/usr/local/ircd/etc/oper.pub";
+
+ /* class: the class the oper joins when they successfully /oper */
+ class = "opers";
+
+ /*
+ * umodes: default usermodes opers get when they /oper. If defined,
+ * it will override oper_umodes settings in general {}.
+ * Available usermodes:
+ *
+ * +b - bots - See bot and drone flooding notices
+ * +c - cconn - Client connection/quit notices
+ * +D - deaf - Don't receive channel messages
+ * +d - debug - See debugging notices
+ * +f - full - See I: line full notices
+ * +G - softcallerid - Server Side Ignore for users not on your channels
+ * +g - callerid - Server Side Ignore (for privmsgs etc)
+ * +i - invisible - Not shown in NAMES or WHO unless you share a
+ * a channel
+ * +k - skill - See server generated KILL messages
+ * +l - locops - See LOCOPS messages
+ * +n - nchange - See client nick changes
+ * +r - rej - See rejected client notices
+ * +s - servnotice - See general server notices
+ * +u - unauth - See unauthorized client notices
+ * +w - wallop - See server generated WALLOPS
+ * +x - external - See remote server connection and split notices
+ * +y - spy - See LINKS, STATS, TRACE notices etc.
+ * +z - operwall - See oper generated WALLOPS
+ */
+# umodes = locops, servnotice, operwall, wallop;
+
+ /*
+ * privileges: controls the activities and commands an oper is
+ * allowed to do on the server. All options default to no.
+ * Available options:
+ *
+ * global_kill: allows remote users to be /KILL'd (OLD 'O' flag)
+ * remote: allows remote SQUIT and CONNECT (OLD 'R' flag)
+ * remoteban: allows remote KLINE/UNKLINE
+ * kline: allows KILL, KLINE and DLINE (OLD 'K' flag)
+ * unkline: allows UNKLINE and UNDLINE (OLD 'U' flag)
+ * gline: allows GLINE (OLD 'G' flag)
+ * xline: allows XLINE (OLD 'X' flag)
+ * operwall: allows OPERWALL
+ * nick_changes: allows oper to see nickchanges (OLD 'N' flag)
+ * via usermode +n
+ * rehash: allows oper to REHASH config (OLD 'H' flag)
+ * die: allows DIE and RESTART (OLD 'D' flag)
+ * admin: gives admin privileges. admins
+ * may (un)load modules and see the
+ * real IPs of servers.
+ * hidden_admin: same as 'admin', but noone can recognize you as
+ * being an admin
+ * hidden_oper: not shown in /stats p (except for other operators)
+ */
+ /* You can either use
+ * die = yes;
+ * rehash = yes;
+ *
+ * or in a flags statement i.e.
+ * flags = die, rehash;
+ *
+ * You can also negate a flag with ~ i.e.
+ * flags = ~remote;
+ *
+ */
+ flags = global_kill, remote, kline, unkline, xline,
+ die, rehash, nick_changes, admin, operwall;
+};
+
+/*
+ * shared {}: users that are allowed to remote kline (OLD U:)
+ *
+ * NOTE: This can be effectively used for remote klines.
+ * Please note that there is no password authentication
+ * for users setting remote klines. You must also be
+ * /oper'd in order to issue a remote kline.
+ */
+shared {
+ /*
+ * name: the server the user must be on to set klines. If this is not
+ * specified, the user will be allowed to kline from all servers.
+ */
+ name = "irc2.some.server";
+
+ /*
+ * user: the user@host mask that is allowed to set klines. If this is
+ * not specified, all users on the server above will be allowed to set
+ * a remote kline.
+ */
+ user = "oper@my.host.is.spoofed";
+
+ /*
+ * type: list of what to share, options are as follows:
+ * kline - allow oper/server to kline
+ * tkline - allow temporary klines
+ * unkline - allow oper/server to unkline
+ * xline - allow oper/server to xline
+ * txline - allow temporary xlines
+ * unxline - allow oper/server to unxline
+ * resv - allow oper/server to resv
+ * tresv - allow temporary resvs
+ * unresv - allow oper/server to unresv
+ * locops - allow oper/server to locops - only used for servers that cluster
+ * all - allow oper/server to do all of the above (default)
+ */
+ type = kline, unkline, resv;
+};
+
+/*
+ * kill {}: users that are not allowed to connect (OLD K:)
+ * Oper issued klines will be added to the specified kline config
+ */
+kill {
+ user = "bad@*.hacked.edu";
+ reason = "Obviously hacked account";
+};
+
+kill {
+ user = "^O[[:alpha:]]?[[:digit:]]+(x\.o|\.xo)$@^[[:alnum:]]{4}\.evilnet.org$";
+ type = regex;
+};
+
+/*
+ * deny {}: IPs that are not allowed to connect (before DNS/ident lookup)
+ * Oper issued dlines will be added to the specified dline config
+ */
+deny {
+ ip = "10.0.1.0/24";
+ reason = "Reconnecting vhosted bots";
+};
+
+/*
+ * exempt {}: IPs that are exempt from deny {} and Dlines. (OLD d:)
+ */
+exempt {
+ ip = "192.168.0.0/16";
+};
+
+/*
+ * resv {}: nicks and channels users may not use/join (OLD Q:)
+ */
+resv {
+ /* reason: the reason for the proceeding resv's */
+ reason = "There are no services on this network";
+
+ /* resv: the nicks and channels users may not join/use */
+ nick = "nickserv";
+ nick = "chanserv";
+ channel = "#services";
+
+ /* resv: wildcard masks are also supported in nicks only */
+ reason = "Clone bots";
+ nick = "clone*";
+};
+
+/*
+ * gecos {}: The X: replacement, used for banning users based on
+ * their "realname".
+ */
+gecos {
+ name = "*sex*";
+ reason = "Possible spambot";
+};
+
+gecos {
+ name = "sub7server";
+ reason = "Trojan drone";
+};
+
+gecos {
+ name = "*http*";
+ reason = "Spambot";
+};
+
+gecos {
+ name = "^\[J[0o]hn Do[3e]\]-[0-9]{2,5}$";
+ type = regex;
+};
+
+/*
+ * channel {}: The channel block contains options pertaining to channels
+ */
+channel {
+ /*
+ * disable_fake_channels: this option, if set to 'yes', will
+ * disallow clients to create or join channels that have one
+ * of the following ASCII characters in their name:
+ *
+ * 2 | bold
+ * 3 | mirc color
+ * 15 | plain text
+ * 22 | reverse
+ * 31 | underline
+ * 160 | non-breaking space
+ */
+ disable_fake_channels = yes;
+
+ /*
+ * restrict_channels: reverse channel RESVs logic, only reserved
+ * channels are allowed
+ */
+ restrict_channels = no;
+
+ /*
+ * disable_local_channels: prevent users from joining &channels.
+ */
+ disable_local_channels = no;
+
+ /*
+ * use_invex: Enable/disable channel mode +I, a n!u@h list of masks
+ * that can join a +i channel without an invite.
+ */
+ use_invex = yes;
+
+ /*
+ * use_except: Enable/disable channel mode +e, a n!u@h list of masks
+ * that can join a channel through a ban (+b).
+ */
+ use_except = yes;
+
+ /*
+ * use_knock: Allows users to request an invite to a channel that
+ * is locked somehow (+ikl). If the channel is +p or you are banned
+ * the knock will not be sent.
+ */
+ use_knock = yes;
+
+ /*
+ * knock_delay: The amount of time a user must wait between issuing
+ * the knock command.
+ */
+ knock_delay = 1 minutes;
+
+ /*
+ * knock_delay_channel: How often a knock to any specific channel
+ * is permitted, regardless of the user sending the knock.
+ */
+ knock_delay_channel = 1 minute;
+
+ /*
+ * burst_topicwho: enable sending of who set topic on topicburst
+ * default is yes
+ */
+ burst_topicwho = yes;
+
+ /*
+ * max_chans_per_user: The maximum number of channels a user can
+ * join/be on.
+ */
+ max_chans_per_user = 25;
+
+ /* quiet_on_ban: stop banned people talking in channels. */
+ quiet_on_ban = yes;
+
+ /* max_bans: maximum number of +b/e/I modes in a channel */
+ max_bans = 1000;
+
+ /*
+ * how many joins in how many seconds constitute a flood, use 0 to
+ * disable. +b opers will be notified (changeable via /set)
+ */
+ join_flood_count = 100;
+ join_flood_time = 10 seconds;
+
+ /*
+ * splitcode: The ircd will now check splitmode every few seconds.
+ *
+ * Either split users or split servers can activate splitmode, but
+ * both conditions must be met for the ircd to deactivate splitmode.
+ *
+ * You may force splitmode to be permanent by /quote set splitmode on
+ */
+
+ /*
+ * default_split_user_count: when the usercount is lower than this level,
+ * consider ourselves split. This must be set for automatic splitmode.
+ */
+ default_split_user_count = 0;
+
+ /*
+ * default_split_server_count: when the servercount is lower than this,
+ * consider ourselves split. This must be set for automatic splitmode.
+ */
+ default_split_server_count = 0;
+
+ /* split no create: disallow users creating channels on split. */
+ no_create_on_split = yes;
+
+ /* split: no join: disallow users joining channels at all on a split */
+ no_join_on_split = no;
+};
+
+/*
+ * serverhide {}: The serverhide block contains the options regarding
+ * serverhiding
+ */
+serverhide {
+ /*
+ * flatten_links: this option will show all servers in /links appear
+ * that they are linked to this current server
+ */
+ flatten_links = no;
+
+ /*
+ * links_delay: how often to update the links file when it is
+ * flattened.
+ */
+ links_delay = 5 minutes;
+
+ /*
+ * hidden: hide this server from a /links output on servers that
+ * support it. This allows hub servers to be hidden etc.
+ */
+ hidden = no;
+
+ /*
+ * disable_hidden: prevent servers hiding themselves from a
+ * /links output.
+ */
+ disable_hidden = no;
+
+ /*
+ * hide_servers: hide remote servernames everywhere and instead use
+ * hidden_name and network_desc.
+ */
+ hide_servers = no;
+
+ /*
+ * Use this as the servername users see if hide_servers = yes.
+ */
+ hidden_name = "*.hidden.com";
+
+ /*
+ * hide_server_ips: If this is disabled, opers will be unable to see servers
+ * ips and will be shown a masked ip, admins will be shown the real ip.
+ *
+ * If this is enabled, nobody can see a servers ip. *This is a kludge*, it
+ * has the side effect of hiding the ips everywhere, including logfiles.
+ *
+ * We recommend you leave this disabled, and just take care with who you
+ * give admin=yes; to.
+ */
+ hide_server_ips = no;
+};
+
+/*
+ * general {}: The general block contains many of the options that were once
+ * compiled in options in config.h. The general block is read at start time.
+ */
+general {
+ /*
+ * gline_min_cidr: the minimum required length of a CIDR bitmask
+ * for IPv4 based glines
+ */
+ gline_min_cidr = 16;
+
+ /*
+ * gline_min_cidr6: the minimum required length of a CIDR bitmask
+ * for IPv6 based glines
+ */
+ gline_min_cidr6 = 48;
+
+ /*
+ * Whether to automatically set mode +i on connecting users.
+ */
+ invisible_on_connect = yes;
+
+ /*
+ * If you don't explicitly specify burst_away in your connect blocks, then
+ * they will default to the burst_away value below.
+ */
+ burst_away = no;
+
+ /*
+ * Show "actually using host <ip>" on /whois when possible.
+ */
+ use_whois_actually = yes;
+
+ /*
+ * Max time from the nickname change that still causes KILL
+ * automatically to switch for the current nick of that user. (seconds)
+ */
+ kill_chase_time_limit = 90;
+
+ /*
+ * If hide_spoof_ips is disabled, opers will be allowed to see the real IP of spoofed
+ * users in /trace etc. If this is defined they will be shown a masked IP.
+ */
+ hide_spoof_ips = yes;
+
+ /*
+ * Ignore bogus timestamps from other servers. Yes, this will desync
+ * the network, but it will allow chanops to resync with a valid non TS 0
+ *
+ * This should be enabled network wide, or not at all.
+ */
+ ignore_bogus_ts = no;
+
+ /*
+ * disable_auth: completely disable ident lookups; if you enable this,
+ * be careful of what you set need_ident to in your auth {} blocks
+ */
+ disable_auth = no;
+
+ /* disable_remote_commands: disable users doing commands on remote servers */
+ disable_remote_commands = no;
+
+ /*
+ * tkline_expire_notices: enables or disables temporary kline/xline
+ * expire notices.
+ */
+ tkline_expire_notices = no;
+
+ /*
+ * default_floodcount: the default value of floodcount that is configurable
+ * via /quote set floodcount. This is the amount of lines a user
+ * may send to any other user/channel in one second.
+ */
+ default_floodcount = 10;
+
+ /*
+ * failed_oper_notice: send a notice to all opers on the server when
+ * someone tries to OPER and uses the wrong password, host or ident.
+ */
+ failed_oper_notice = yes;
+
+ /*
+ * dots_in_ident: the amount of '.' characters permitted in an ident
+ * reply before the user is rejected.
+ */
+ dots_in_ident = 2;
+
+ /*
+ * dot_in_ip6_addr: ircd-hybrid-6.0 and earlier will disallow hosts
+ * without a '.' in them. This will add one to the end. Only needed
+ * for older servers.
+ */
+ dot_in_ip6_addr = no;
+
+ /*
+ * min_nonwildcard: the minimum non wildcard characters in k/d/g lines
+ * placed via the server. klines hand placed are exempt from limits.
+ * wildcard chars: '.' ':' '*' '?' '@' '!' '#'
+ */
+ min_nonwildcard = 4;
+
+ /*
+ * min_nonwildcard_simple: the minimum non wildcard characters in
+ * gecos bans. wildcard chars: '*' '?' '#'
+ */
+ min_nonwildcard_simple = 3;
+
+ /* max_accept: maximum allowed /accept's for +g usermode */
+ max_accept = 20;
+
+ /* anti_nick_flood: enable the nickflood control code */
+ anti_nick_flood = yes;
+
+ /* nick flood: the nick changes allowed in the specified period */
+ max_nick_time = 20 seconds;
+ max_nick_changes = 5;
+
+ /*
+ * anti_spam_exit_message_time: the minimum time a user must be connected
+ * before custom quit messages are allowed.
+ */
+ anti_spam_exit_message_time = 5 minutes;
+
+ /*
+ * ts delta: the time delta allowed between server clocks before
+ * a warning is given, or before the link is dropped. all servers
+ * should run ntpdate/rdate to keep clocks in sync
+ */
+ ts_warn_delta = 30 seconds;
+ ts_max_delta = 5 minutes;
+
+ /*
+ * kline_with_reason: show the user the reason why they are k/d/glined
+ * on exit. May give away who set k/dline when set via tcm.
+ */
+ kline_with_reason = yes;
+
+ /*
+ * kline_reason: show this message to users on channel
+ * instead of the oper reason.
+ */
+ kline_reason = "Connection closed";
+
+ /*
+ * reject_hold_time: wait this amount of time before disconnecting
+ * a rejected client. Use 0 to disable.
+ */
+ reject_hold_time = 0;
+
+ /*
+ * warn_no_nline: warn opers about servers that try to connect but
+ * we don't have a connect {} block for. Twits with misconfigured
+ * servers can get really annoying with this enabled.
+ */
+ warn_no_nline = yes;
+
+ /*
+ * stats_e_disabled: set this to 'yes' to disable "STATS e" for both
+ * operators and administrators. Doing so is a good idea in case
+ * there are any exempted (exempt{}) server IPs you don't want to
+ * see leaked.
+ */
+ stats_e_disabled = no;
+
+ /* stats_o_oper only: make stats o (opers) oper only */
+ stats_o_oper_only = yes;
+
+ /* stats_P_oper_only: make stats P (ports) oper only */
+ stats_P_oper_only = yes;
+
+ /*
+ * stats i oper only: make stats i (auth {}) oper only. set to:
+ * yes: show users no auth blocks, made oper only.
+ * masked: show users first matching auth block
+ * no: show users all auth blocks.
+ */
+ stats_i_oper_only = yes;
+
+ /*
+ * stats_k_oper_only: make stats k/K (klines) oper only. set to:
+ * yes: show users no auth blocks, made oper only
+ * masked: show users first matching auth block
+ * no: show users all auth blocks.
+ */
+ stats_k_oper_only = yes;
+
+ /*
+ * caller_id_wait: time between notifying a +g user that somebody
+ * is messaging them.
+ */
+ caller_id_wait = 1 minute;
+
+ /*
+ * opers_bypass_callerid: allows operators to bypass +g and message
+ * anyone who has it set (useful if you use services).
+ */
+ opers_bypass_callerid = no;
+
+ /*
+ * pace_wait_simple: time between use of less intensive commands
+ * (ADMIN, HELP, (L)USERS, VERSION, remote WHOIS)
+ */
+ pace_wait_simple = 1 second;
+
+ /*
+ * pace_wait: time between more intensive commands
+ * (INFO, LINKS, LIST, MAP, MOTD, STATS, WHO, wildcard WHOIS, WHOWAS)
+ */
+ pace_wait = 10 seconds;
+
+ /*
+ * short_motd: send clients a notice telling them to read the motd
+ * instead of forcing a motd to clients who may simply ignore it.
+ */
+ short_motd = no;
+
+ /*
+ * ping_cookie: require clients to respond exactly to a ping command,
+ * can help block certain types of drones and FTP PASV mode spoofing.
+ */
+ ping_cookie = no;
+
+ /* no_oper_flood: increase flood limits for opers. */
+ no_oper_flood = yes;
+
+ /*
+ * true_no_oper_flood: completely eliminate flood limits for opers
+ * and for clients with can_flood = yes in their auth {} blocks
+ */
+ true_no_oper_flood = yes;
+
+ /* oper_pass_resv: allow opers to over-ride RESVs on nicks/channels */
+ oper_pass_resv = yes;
+
+ /*
+ * idletime: the maximum amount of time a user may idle before
+ * they are disconnected
+ */
+ idletime = 0;
+
+ /* REMOVE ME. The following line checks you've been reading. */
+ #havent_read_conf = 1;
+
+ /*
+ * max_targets: the maximum amount of targets in a single
+ * PRIVMSG/NOTICE. Set to 999 NOT 0 for unlimited.
+ */
+ max_targets = 4;
+
+ /*
+ * client_flood: maximum amount of data in a clients queue before
+ * they are dropped for flooding.
+ */
+ client_flood = 2560 bytes;
+
+ /*
+ * message_locale: the default message locale
+ * Use "standard" for the compiled in defaults.
+ * To install the translated messages, go into messages/ in the
+ * source directory and run `make install'.
+ */
+ message_locale = "standard";
+
+ /*
+ * usermodes configurable: a list of usermodes for the options below
+ *
+ * +b - bots - See bot and drone flooding notices
+ * +c - cconn - Client connection/quit notices
+ * +D - deaf - Don't receive channel messages
+ * +d - debug - See debugging notices
+ * +f - full - See I: line full notices
+ * +G - softcallerid - Server Side Ignore for users not on your channels
+ * +g - callerid - Server Side Ignore (for privmsgs etc)
+ * +i - invisible - Not shown in NAMES or WHO unless you share a
+ * a channel
+ * +k - skill - See server generated KILL messages
+ * +l - locops - See LOCOPS messages
+ * +n - nchange - See client nick changes
+ * +r - rej - See rejected client notices
+ * +s - servnotice - See general server notices
+ * +u - unauth - See unauthorized client notices
+ * +w - wallop - See server generated WALLOPS
+ * +x - external - See remote server connection and split notices
+ * +y - spy - See LINKS, STATS, TRACE notices etc.
+ * +z - operwall - See oper generated WALLOPS
+ */
+
+ /* oper_only_umodes: usermodes only opers may set */
+ oper_only_umodes = bots, cconn, debug, full, skill, nchange,
+ rej, spy, external, operwall, locops, unauth;
+
+ /* oper_umodes: default usermodes opers get when they /oper */
+ oper_umodes = bots, locops, servnotice, operwall, wallop;
+
+ /*
+ * servlink_path: path to 'servlink' program used by ircd to handle
+ * encrypted/compressed server <-> server links.
+ *
+ * only define if servlink is not in same directory as ircd itself.
+ */
+ #servlink_path = "/usr/local/ircd/bin/servlink";
+
+ /*
+ * default_cipher_preference: default cipher to use for cryptlink when none is
+ * specified in connect block.
+ */
+ #default_cipher_preference = "BF/168";
+
+ /*
+ * use_egd: if your system does not have *random devices yet you
+ * want to use OpenSSL and encrypted links, enable this. Beware -
+ * EGD is *very* CPU intensive when gathering data for its pool
+ */
+# use_egd = yes;
+
+ /*
+ * egdpool_path: path to EGD pool. Not necessary for OpenSSL >= 0.9.7
+ * which automatically finds the path.
+ */
+# egdpool_path = "/run/egd-pool";
+
+
+ /*
+ * compression_level: level of compression for compressed links between
+ * servers.
+ *
+ * values are between: 1 (least compression, fastest)
+ * and: 9 (most compression, slowest).
+ */
+# compression_level = 6;
+
+ /*
+ * throttle_time: the minimum amount of time between connections from
+ * the same ip. exempt {} blocks are excluded from this throttling.
+ * Offers protection against flooders who reconnect quickly.
+ * Set to 0 to disable.
+ */
+ throttle_time = 10;
+};
+
+glines {
+ /* enable: enable glines, network wide temp klines */
+ enable = yes;
+
+ /*
+ * duration: the amount of time a gline will remain on your
+ * server before expiring
+ */
+ duration = 1 day;
+
+ /*
+ * logging: which types of rules you want to log when triggered
+ * (choose reject or block)
+ */
+ logging = reject, block;
+
+ /*
+ * NOTE: gline ACLs can cause a desync of glines throughout the
+ * network, meaning some servers may have a gline triggered, and
+ * others may not. Also, you only need insert rules for glines
+ * that you want to block and/or reject. If you want to accept and
+ * propagate the gline, do NOT put a rule for it.
+ */
+
+ /* user@host for rule to apply to */
+ user = "god@I.still.hate.packets";
+ /* server for rule to apply to */
+ name = "hades.arpa";
+
+ /*
+ * action: action to take when a matching gline is found. options are:
+ * reject - do not apply the gline locally
+ * block - do not propagate the gline
+ */
+ action = reject, block;
+
+ user = "god@*";
+ name = "*";
+ action = block;
+};
+
diff --git a/nixpkgs/nixos/modules/services/networking/iwd.nix b/nixpkgs/nixos/modules/services/networking/iwd.nix
new file mode 100644
index 00000000000..839fa48d9a4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/iwd.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.networking.wireless.iwd;
+in {
+ options.networking.wireless.iwd.enable = mkEnableOption "iwd";
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = !config.networking.wireless.enable;
+ message = ''
+ Only one wireless daemon is allowed at the time: networking.wireless.enable and networking.wireless.iwd.enable are mutually exclusive.
+ '';
+ }];
+
+ # for iwctl
+ environment.systemPackages = [ pkgs.iwd ];
+
+ services.dbus.packages = [ pkgs.iwd ];
+
+ systemd.packages = [ pkgs.iwd ];
+
+ systemd.services.iwd.wantedBy = [ "multi-user.target" ];
+
+ systemd.tmpfiles.rules = [
+ "d /var/lib/iwd 0700 root root -"
+ "d /var/lib/ead 0700 root root -"
+ ];
+ };
+
+ meta.maintainers = with lib.maintainers; [ mic92 ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/jormungandr.nix b/nixpkgs/nixos/modules/services/networking/jormungandr.nix
new file mode 100644
index 00000000000..152cceb4bf9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/jormungandr.nix
@@ -0,0 +1,102 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.jormungandr;
+
+ inherit (lib) mkEnableOption mkIf mkOption;
+ inherit (lib) optionalString types;
+
+ dataDir = "/var/lib/jormungandr";
+
+ # Default settings so far, as the service matures we will
+ # move these out as separate settings
+ configSettings = {
+ storage = dataDir;
+ p2p = {
+ public_address = "/ip4/127.0.0.1/tcp/8299";
+ topics_of_interest = {
+ messages = "high";
+ blocks = "high";
+ };
+ };
+ rest = {
+ listen = "127.0.0.1:8607";
+ };
+ };
+
+ configFile = if cfg.configFile == null then
+ pkgs.writeText "jormungandr.yaml" (builtins.toJSON configSettings)
+ else cfg.configFile;
+
+in {
+
+ options = {
+
+ services.jormungandr = {
+ enable = mkEnableOption "jormungandr service";
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/var/lib/jormungandr/node.yaml";
+ description = ''
+ The path of the jormungandr blockchain configuration file in YAML format.
+ If no file is specified, a file is generated using the other options.
+ '';
+ };
+
+ secretFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/etc/secret/jormungandr.yaml";
+ description = ''
+ The path of the jormungandr blockchain secret node configuration file in
+ YAML format. Do not store this in nix store!
+ '';
+ };
+
+ genesisBlockHash = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "d70495af81ae8600aca3e642b2427327cb6001ec4d7a0037e96a00dabed163f9";
+ description = ''
+ Set the genesis block hash (the hash of the block0) so we can retrieve
+ the genesis block (and the blockchain configuration) from the existing
+ storage or from the network.
+ '';
+ };
+
+ genesisBlockFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/var/lib/jormungandr/block-0.bin";
+ description = ''
+ The path of the genesis block file if we are hosting it locally.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.jormungandr = {
+ description = "jormungandr server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target" ];
+ environment = {
+ RUST_BACKTRACE = "full";
+ };
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = baseNameOf dataDir;
+ ExecStart = ''
+ ${pkgs.jormungandr}/bin/jormungandr --config ${configFile} \
+ ${optionalString (cfg.secretFile != null) " --secret ${cfg.secretFile}"} \
+ ${optionalString (cfg.genesisBlockHash != null) " --genesis-block-hash ${cfg.genesisBlockHash}"} \
+ ${optionalString (cfg.genesisBlockFile != null) " --genesis-block ${cfg.genesisBlockFile}"}
+ '';
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/keepalived/default.nix b/nixpkgs/nixos/modules/services/networking/keepalived/default.nix
new file mode 100644
index 00000000000..c9ac2ee2599
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/keepalived/default.nix
@@ -0,0 +1,303 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.keepalived;
+
+ keepalivedConf = pkgs.writeText "keepalived.conf" ''
+ global_defs {
+ ${optionalString cfg.enableScriptSecurity "enable_script_security"}
+ ${snmpGlobalDefs}
+ ${cfg.extraGlobalDefs}
+ }
+
+ ${vrrpScriptStr}
+ ${vrrpInstancesStr}
+ ${cfg.extraConfig}
+ '';
+
+ snmpGlobalDefs = with cfg.snmp; optionalString enable (
+ optionalString (socket != null) "snmp_socket ${socket}\n"
+ + optionalString enableKeepalived "enable_snmp_keepalived\n"
+ + optionalString enableChecker "enable_snmp_checker\n"
+ + optionalString enableRfc "enable_snmp_rfc\n"
+ + optionalString enableRfcV2 "enable_snmp_rfcv2\n"
+ + optionalString enableRfcV3 "enable_snmp_rfcv3\n"
+ + optionalString enableTraps "enable_traps"
+ );
+
+ vrrpScriptStr = concatStringsSep "\n" (map (s:
+ ''
+ vrrp_script ${s.name} {
+ script "${s.script}"
+ interval ${toString s.interval}
+ fall ${toString s.fall}
+ rise ${toString s.rise}
+ timeout ${toString s.timeout}
+ weight ${toString s.weight}
+ user ${s.user} ${optionalString (s.group != null) s.group}
+
+ ${s.extraConfig}
+ }
+ ''
+ ) vrrpScripts);
+
+ vrrpInstancesStr = concatStringsSep "\n" (map (i:
+ ''
+ vrrp_instance ${i.name} {
+ interface ${i.interface}
+ state ${i.state}
+ virtual_router_id ${toString i.virtualRouterId}
+ priority ${toString i.priority}
+ ${optionalString i.noPreempt "nopreempt"}
+
+ ${optionalString i.useVmac (
+ "use_vmac" + optionalString (i.vmacInterface != null) " ${i.vmacInterface}"
+ )}
+ ${optionalString i.vmacXmitBase "vmac_xmit_base"}
+
+ ${optionalString (i.unicastSrcIp != null) "unicast_src_ip ${i.unicastSrcIp}"}
+ unicast_peer {
+ ${concatStringsSep "\n" i.unicastPeers}
+ }
+
+ virtual_ipaddress {
+ ${concatMapStringsSep "\n" virtualIpLine i.virtualIps}
+ }
+
+ ${optionalString (builtins.length i.trackScripts > 0) ''
+ track_script {
+ ${concatStringsSep "\n" i.trackScripts}
+ }
+ ''}
+
+ ${optionalString (builtins.length i.trackInterfaces > 0) ''
+ track_interface {
+ ${concatStringsSep "\n" i.trackInterfaces}
+ }
+ ''}
+
+ ${i.extraConfig}
+ }
+ ''
+ ) vrrpInstances);
+
+ virtualIpLine = (ip:
+ ip.addr
+ + optionalString (notNullOrEmpty ip.brd) " brd ${ip.brd}"
+ + optionalString (notNullOrEmpty ip.dev) " dev ${ip.dev}"
+ + optionalString (notNullOrEmpty ip.scope) " scope ${ip.scope}"
+ + optionalString (notNullOrEmpty ip.label) " label ${ip.label}"
+ );
+
+ notNullOrEmpty = s: !(s == null || s == "");
+
+ vrrpScripts = mapAttrsToList (name: config:
+ {
+ inherit name;
+ } // config
+ ) cfg.vrrpScripts;
+
+ vrrpInstances = mapAttrsToList (iName: iConfig:
+ {
+ name = iName;
+ } // iConfig
+ ) cfg.vrrpInstances;
+
+ vrrpInstanceAssertions = i: [
+ { assertion = i.interface != "";
+ message = "services.keepalived.vrrpInstances.${i.name}.interface option cannot be empty.";
+ }
+ { assertion = i.virtualRouterId >= 0 && i.virtualRouterId <= 255;
+ message = "services.keepalived.vrrpInstances.${i.name}.virtualRouterId must be an integer between 0..255.";
+ }
+ { assertion = i.priority >= 0 && i.priority <= 255;
+ message = "services.keepalived.vrrpInstances.${i.name}.priority must be an integer between 0..255.";
+ }
+ { assertion = i.vmacInterface == null || i.useVmac;
+ message = "services.keepalived.vrrpInstances.${i.name}.vmacInterface has no effect when services.keepalived.vrrpInstances.${i.name}.useVmac is not set.";
+ }
+ { assertion = !i.vmacXmitBase || i.useVmac;
+ message = "services.keepalived.vrrpInstances.${i.name}.vmacXmitBase has no effect when services.keepalived.vrrpInstances.${i.name}.useVmac is not set.";
+ }
+ ] ++ flatten (map (virtualIpAssertions i.name) i.virtualIps)
+ ++ flatten (map (vrrpScriptAssertion i.name) i.trackScripts);
+
+ virtualIpAssertions = vrrpName: ip: [
+ { assertion = ip.addr != "";
+ message = "The 'addr' option for an services.keepalived.vrrpInstances.${vrrpName}.virtualIps entry cannot be empty.";
+ }
+ ];
+
+ vrrpScriptAssertion = vrrpName: scriptName: {
+ assertion = builtins.hasAttr scriptName cfg.vrrpScripts;
+ message = "services.keepalived.vrrpInstances.${vrrpName} trackscript ${scriptName} is not defined in services.keepalived.vrrpScripts.";
+ };
+
+ pidFile = "/run/keepalived.pid";
+
+in
+{
+
+ options = {
+ services.keepalived = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Keepalived.
+ '';
+ };
+
+ enableScriptSecurity = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Don't run scripts configured to be run as root if any part of the path is writable by a non-root user.
+ '';
+ };
+
+ snmp = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the builtin AgentX subagent.
+ '';
+ };
+
+ socket = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Socket to use for connecting to SNMP master agent. If this value is
+ set to null, keepalived's default will be used, which is
+ unix:/var/agentx/master, unless using a network namespace, when the
+ default is udp:localhost:705.
+ '';
+ };
+
+ enableKeepalived = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable SNMP handling of vrrp element of KEEPALIVED MIB.
+ '';
+ };
+
+ enableChecker = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable SNMP handling of checker element of KEEPALIVED MIB.
+ '';
+ };
+
+ enableRfc = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable SNMP handling of RFC2787 and RFC6527 VRRP MIBs.
+ '';
+ };
+
+ enableRfcV2 = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable SNMP handling of RFC2787 VRRP MIB.
+ '';
+ };
+
+ enableRfcV3 = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable SNMP handling of RFC6527 VRRP MIB.
+ '';
+ };
+
+ enableTraps = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable SNMP traps.
+ '';
+ };
+
+ };
+
+ vrrpScripts = mkOption {
+ type = types.attrsOf (types.submodule (import ./vrrp-script-options.nix {
+ inherit lib;
+ }));
+ default = {};
+ description = "Declarative vrrp script config";
+ };
+
+ vrrpInstances = mkOption {
+ type = types.attrsOf (types.submodule (import ./vrrp-instance-options.nix {
+ inherit lib;
+ }));
+ default = {};
+ description = "Declarative vhost config";
+ };
+
+ extraGlobalDefs = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines to be added verbatim to the 'global_defs' block of the
+ configuration file
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines to be added verbatim to the configuration file.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = flatten (map vrrpInstanceAssertions vrrpInstances);
+
+ systemd.timers.keepalived-boot-delay = {
+ description = "Keepalive Daemon delay to avoid instant transition to MASTER state";
+ after = [ "network.target" "network-online.target" "syslog.target" ];
+ requires = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ timerConfig = {
+ OnActiveSec = "5s";
+ Unit = "keepalived.service";
+ };
+ };
+
+ systemd.services.keepalived = {
+ description = "Keepalive Daemon (LVS and VRRP)";
+ after = [ "network.target" "network-online.target" "syslog.target" ];
+ wants = [ "network-online.target" ];
+ serviceConfig = {
+ Type = "forking";
+ PIDFile = pidFile;
+ KillMode = "process";
+ ExecStart = "${pkgs.keepalived}/sbin/keepalived"
+ + " -f ${keepalivedConf}"
+ + " -p ${pidFile}"
+ + optionalString cfg.snmp.enable " --snmp";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ Restart = "always";
+ RestartSec = "1s";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/keepalived/virtual-ip-options.nix b/nixpkgs/nixos/modules/services/networking/keepalived/virtual-ip-options.nix
new file mode 100644
index 00000000000..1b8889b1b47
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/keepalived/virtual-ip-options.nix
@@ -0,0 +1,50 @@
+{ lib } :
+
+with lib;
+{
+ options = {
+
+ addr = mkOption {
+ type = types.str;
+ description = ''
+ IP address, optionally with a netmask: IPADDR[/MASK]
+ '';
+ };
+
+ brd = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The broadcast address on the interface.
+ '';
+ };
+
+ dev = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The name of the device to add the address to.
+ '';
+ };
+
+ scope = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The scope of the area where this address is valid.
+ '';
+ };
+
+ label = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Each address may be tagged with a label string. In order to preserve
+ compatibility with Linux-2.0 net aliases, this string must coincide with
+ the name of the device or must be prefixed with the device name followed
+ by colon.
+ '';
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix b/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix
new file mode 100644
index 00000000000..85b9bc33772
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix
@@ -0,0 +1,135 @@
+{ lib } :
+
+with lib;
+{
+ options = {
+
+ interface = mkOption {
+ type = types.str;
+ description = ''
+ Interface for inside_network, bound by vrrp.
+ '';
+ };
+
+ state = mkOption {
+ type = types.enum [ "MASTER" "BACKUP" ];
+ default = "BACKUP";
+ description = ''
+ Initial state. As soon as the other machine(s) come up, an election will
+ be held and the machine with the highest "priority" will become MASTER.
+ So the entry here doesn't matter a whole lot.
+ '';
+ };
+
+ virtualRouterId = mkOption {
+ type = types.int;
+ description = ''
+ Arbitrary unique number 0..255. Used to differentiate multiple instances
+ of vrrpd running on the same NIC (and hence same socket).
+ '';
+ };
+
+ priority = mkOption {
+ type = types.int;
+ default = 100;
+ description = ''
+ For electing MASTER, highest priority wins. To be MASTER, make 50 more
+ than other machines.
+ '';
+ };
+
+ noPreempt = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ VRRP will normally preempt a lower priority machine when a higher
+ priority machine comes online. "nopreempt" allows the lower priority
+ machine to maintain the master role, even when a higher priority machine
+ comes back online. NOTE: For this to work, the initial state of this
+ entry must be BACKUP.
+ '';
+ };
+
+ useVmac = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Use VRRP Virtual MAC.
+ '';
+ };
+
+ vmacInterface = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Name of the vmac interface to use. keepalived will come up with a name
+ if you don't specify one.
+ '';
+ };
+
+ vmacXmitBase = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Send/Recv VRRP messages from base interface instead of VMAC interface.
+ '';
+ };
+
+ unicastSrcIp = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Default IP for binding vrrpd is the primary IP on interface. If you
+ want to hide location of vrrpd, use this IP as src_addr for unicast
+ vrrp packets.
+ '';
+ };
+
+ unicastPeers = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Do not send VRRP adverts over VRRP multicast group. Instead it sends
+ adverts to the following list of ip addresses using unicast design
+ fashion. It can be cool to use VRRP FSM and features in a networking
+ environment where multicast is not supported! IP Addresses specified can
+ IPv4 as well as IPv6.
+ '';
+ };
+
+ virtualIps = mkOption {
+ type = types.listOf (types.submodule (import ./virtual-ip-options.nix {
+ inherit lib;
+ }));
+ default = [];
+ example = literalExample ''
+ TODO: Example
+ '';
+ description = "Declarative vhost config";
+ };
+
+ trackScripts = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "chk_cmd1" "chk_cmd2" ];
+ description = "List of script names to invoke for health tracking.";
+ };
+
+ trackInterfaces = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "eth0" "eth1" ];
+ description = "List of network interfaces to monitor for health tracking.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines to be added verbatim to the vrrp_instance section.
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-script-options.nix b/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-script-options.nix
new file mode 100644
index 00000000000..a3f794c40a8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-script-options.nix
@@ -0,0 +1,64 @@
+{ lib } :
+
+with lib;
+with lib.types;
+{
+ options = {
+
+ script = mkOption {
+ type = str;
+ example = "\${pkgs.curl} -f http://localhost:80";
+ description = "(Path of) Script command to execute followed by args, i.e. cmd [args]...";
+ };
+
+ interval = mkOption {
+ type = int;
+ default = 1;
+ description = "Seconds between script invocations.";
+ };
+
+ timeout = mkOption {
+ type = int;
+ default = 5;
+ description = "Seconds after which script is considered to have failed.";
+ };
+
+ weight = mkOption {
+ type = int;
+ default = 0;
+ description = "Following a failure, adjust the priority by this weight.";
+ };
+
+ rise = mkOption {
+ type = int;
+ default = 5;
+ description = "Required number of successes for OK transition.";
+ };
+
+ fall = mkOption {
+ type = int;
+ default = 3;
+ description = "Required number of failures for KO transition.";
+ };
+
+ user = mkOption {
+ type = str;
+ default = "keepalived_script";
+ description = "Name of user to run the script under.";
+ };
+
+ group = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Name of group to run the script under. Defaults to user group.";
+ };
+
+ extraConfig = mkOption {
+ type = lines;
+ default = "";
+ description = "Extra lines to be added verbatim to the vrrp_script section.";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/keybase.nix b/nixpkgs/nixos/modules/services/networking/keybase.nix
new file mode 100644
index 00000000000..85f52be8a6a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/keybase.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.keybase;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.keybase = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to start the Keybase service.";
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.user.services.keybase = {
+ description = "Keybase service";
+ unitConfig.ConditionUser = "!@system";
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.keybase}/bin/keybase service --auto-forked
+ '';
+ Restart = "on-failure";
+ PrivateTmp = true;
+ };
+ wantedBy = [ "default.target" ];
+ };
+
+ environment.systemPackages = [ pkgs.keybase ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/kippo.nix b/nixpkgs/nixos/modules/services/networking/kippo.nix
new file mode 100644
index 00000000000..bdea6a1d1ca
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/kippo.nix
@@ -0,0 +1,118 @@
+# NixOS module for kippo honeypot ssh server
+# See all the options for configuration details.
+#
+# Default port is 2222. Recommend using something like this for port redirection to default SSH port:
+# networking.firewall.extraCommands = ''
+# iptables -t nat -A PREROUTING -i IN_IFACE -p tcp --dport 22 -j REDIRECT --to-port 2222'';
+#
+# Lastly: use this service at your own risk. I am working on a way to run this inside a VM.
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.kippo;
+in
+{
+ options = {
+ services.kippo = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''Enable the kippo honeypot ssh server.'';
+ };
+ port = mkOption {
+ default = 2222;
+ type = types.int;
+ description = ''TCP port number for kippo to bind to.'';
+ };
+ hostname = mkOption {
+ default = "nas3";
+ type = types.str;
+ description = ''Hostname for kippo to present to SSH login'';
+ };
+ varPath = mkOption {
+ default = "/var/lib/kippo";
+ type = types.path;
+ description = ''Path of read/write files needed for operation and configuration.'';
+ };
+ logPath = mkOption {
+ default = "/var/log/kippo";
+ type = types.path;
+ description = ''Path of log files needed for operation and configuration.'';
+ };
+ pidPath = mkOption {
+ default = "/run/kippo";
+ type = types.path;
+ description = ''Path of pid files needed for operation.'';
+ };
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''Extra verbatim configuration added to the end of kippo.cfg.'';
+ };
+ };
+
+ };
+ config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs.pythonPackages; [
+ python pkgs.kippo.twisted pycrypto pyasn1 ];
+
+ environment.etc."kippo.cfg".text = ''
+ # Automatically generated by NixOS.
+ # See ${pkgs.kippo}/src/kippo.cfg for details.
+ [honeypot]
+ log_path = ${cfg.logPath}
+ download_path = ${cfg.logPath}/dl
+ filesystem_file = ${cfg.varPath}/honeyfs
+ filesystem_file = ${cfg.varPath}/fs.pickle
+ data_path = ${cfg.varPath}/data
+ txtcmds_path = ${cfg.varPath}/txtcmds
+ public_key = ${cfg.varPath}/keys/public.key
+ private_key = ${cfg.varPath}/keys/private.key
+ ssh_port = ${toString cfg.port}
+ hostname = ${cfg.hostname}
+ ${cfg.extraConfig}
+ '';
+
+ users.users = singleton {
+ name = "kippo";
+ description = "kippo web server privilege separation user";
+ uid = 108; # why does config.ids.uids.kippo give an error?
+ };
+ users.groups = singleton { name = "kippo";gid=108; };
+
+ systemd.services.kippo = with pkgs; {
+ description = "Kippo Web Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ environment.PYTHONPATH = "${pkgs.kippo}/src/:${pkgs.pythonPackages.pycrypto}/lib/python2.7/site-packages/:${pkgs.pythonPackages.pyasn1}/lib/python2.7/site-packages/:${pkgs.pythonPackages.python}/lib/python2.7/site-packages/:${pkgs.kippo.twisted}/lib/python2.7/site-packages/:.";
+ preStart = ''
+ if [ ! -d ${cfg.varPath}/ ] ; then
+ mkdir -p ${cfg.logPath}/tty
+ mkdir -p ${cfg.logPath}/dl
+ mkdir -p ${cfg.varPath}/keys
+ cp ${pkgs.kippo}/src/honeyfs ${cfg.varPath} -r
+ cp ${pkgs.kippo}/src/fs.pickle ${cfg.varPath}/fs.pickle
+ cp ${pkgs.kippo}/src/data ${cfg.varPath} -r
+ cp ${pkgs.kippo}/src/txtcmds ${cfg.varPath} -r
+
+ chmod u+rw ${cfg.varPath} -R
+ chown kippo.kippo ${cfg.varPath} -R
+ chown kippo.kippo ${cfg.logPath} -R
+ chmod u+rw ${cfg.logPath} -R
+ fi
+ if [ ! -d ${cfg.pidPath}/ ] ; then
+ mkdir -p ${cfg.pidPath}
+ chmod u+rw ${cfg.pidPath}
+ chown kippo.kippo ${cfg.pidPath}
+ fi
+ '';
+
+ serviceConfig.ExecStart = "${pkgs.kippo.twisted}/bin/twistd -y ${pkgs.kippo}/src/kippo.tac --syslog --rundir=${cfg.varPath}/ --pidfile=${cfg.pidPath}/kippo.pid --prefix=kippo -n";
+ serviceConfig.PermissionsStartOnly = true;
+ serviceConfig.User = "kippo";
+ serviceConfig.Group = "kippo";
+ };
+};
+}
+
+
diff --git a/nixpkgs/nixos/modules/services/networking/knot.nix b/nixpkgs/nixos/modules/services/networking/knot.nix
new file mode 100644
index 00000000000..1cc1dd3f2f6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/knot.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.knot;
+
+ configFile = pkgs.writeText "knot.conf" cfg.extraConfig;
+ socketFile = "/run/knot/knot.sock";
+
+ knotConfCheck = file: pkgs.runCommand "knot-config-checked"
+ { buildInputs = [ cfg.package ]; } ''
+ ln -s ${configFile} $out
+ knotc --config=${configFile} conf-check
+ '';
+
+ knot-cli-wrappers = pkgs.stdenv.mkDerivation {
+ name = "knot-cli-wrappers";
+ buildInputs = [ pkgs.makeWrapper ];
+ buildCommand = ''
+ mkdir -p $out/bin
+ makeWrapper ${cfg.package}/bin/knotc "$out/bin/knotc" \
+ --add-flags "--config=${configFile}" \
+ --add-flags "--socket=${socketFile}"
+ makeWrapper ${cfg.package}/bin/keymgr "$out/bin/keymgr" \
+ --add-flags "--config=${configFile}"
+ for executable in kdig khost kjournalprint knsec3hash knsupdate kzonecheck
+ do
+ ln -s "${cfg.package}/bin/$executable" "$out/bin/$executable"
+ done
+ mkdir -p "$out/share"
+ ln -s '${cfg.package}/share/man' "$out/share/"
+ '';
+ };
+in {
+ options = {
+ services.knot = {
+ enable = mkEnableOption "Knot authoritative-only DNS server";
+
+ extraArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ List of additional command line paramters for knotd
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines to be added verbatim to knot.conf
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.knot-dns;
+ description = ''
+ Which Knot DNS package to use
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.knot.enable {
+ systemd.services.knot = {
+ unitConfig.Documentation = "man:knotd(8) man:knot.conf(5) man:knotc(8) https://www.knot-dns.cz/docs/${cfg.package.version}/html/";
+ description = cfg.package.meta.description;
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network.target" ];
+ after = ["network.target" ];
+
+ serviceConfig = {
+ Type = "notify";
+ ExecStart = "${cfg.package}/bin/knotd --config=${knotConfCheck configFile} --socket=${socketFile} ${concatStringsSep " " cfg.extraArgs}";
+ ExecReload = "${knot-cli-wrappers}/bin/knotc reload";
+ CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETPCAP";
+ AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETPCAP";
+ NoNewPrivileges = true;
+ DynamicUser = "yes";
+ RuntimeDirectory = "knot";
+ StateDirectory = "knot";
+ StateDirectoryMode = "0700";
+ PrivateDevices = true;
+ RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+ SystemCallArchitectures = "native";
+ Restart = "on-abort";
+ };
+ };
+
+ environment.systemPackages = [ knot-cli-wrappers ];
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/kresd.nix b/nixpkgs/nixos/modules/services/networking/kresd.nix
new file mode 100644
index 00000000000..fc516c01230
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/kresd.nix
@@ -0,0 +1,136 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.kresd;
+ package = pkgs.knot-resolver;
+
+ configFile = pkgs.writeText "kresd.conf" cfg.extraConfig;
+in
+
+{
+ meta.maintainers = [ maintainers.vcunat /* upstream developer */ ];
+
+ ###### interface
+ options.services.kresd = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable knot-resolver domain name server.
+ DNSSEC validation is turned on by default.
+ You can run <literal>sudo nc -U /run/kresd/control</literal>
+ and give commands interactively to kresd.
+ '';
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines to be added verbatim to the generated configuration file.
+ '';
+ };
+ cacheDir = mkOption {
+ type = types.path;
+ default = "/var/cache/kresd";
+ description = ''
+ Directory for caches. They are intended to survive reboots.
+ '';
+ };
+ interfaces = mkOption {
+ type = with types; listOf str;
+ default = [ "::1" "127.0.0.1" ];
+ description = ''
+ What addresses the server should listen on. (UDP+TCP 53)
+ '';
+ };
+ listenTLS = mkOption {
+ type = with types; listOf str;
+ default = [];
+ example = [ "198.51.100.1:853" "[2001:db8::1]:853" "853" ];
+ description = ''
+ Addresses on which kresd should provide DNS over TLS (see RFC 7858).
+ For detailed syntax see ListenStream in man systemd.socket.
+ '';
+ };
+ # TODO: perhaps options for more common stuff like cache size or forwarding
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ environment.etc."kresd.conf".source = configFile; # not required
+
+ users.users = singleton
+ { name = "kresd";
+ uid = config.ids.uids.kresd;
+ group = "kresd";
+ description = "Knot-resolver daemon user";
+ };
+ users.groups = singleton
+ { name = "kresd";
+ gid = config.ids.gids.kresd;
+ };
+
+ systemd.sockets.kresd = rec {
+ wantedBy = [ "sockets.target" ];
+ before = wantedBy;
+ listenStreams = map
+ # Syntax depends on being IPv6 or IPv4.
+ (iface: if elem ":" (stringToCharacters iface) then "[${iface}]:53" else "${iface}:53")
+ cfg.interfaces;
+ socketConfig = {
+ ListenDatagram = listenStreams;
+ FreeBind = true;
+ FileDescriptorName = "dns";
+ };
+ };
+
+ systemd.sockets.kresd-tls = mkIf (cfg.listenTLS != []) rec {
+ wantedBy = [ "sockets.target" ];
+ before = wantedBy;
+ partOf = [ "kresd.socket" ];
+ listenStreams = cfg.listenTLS;
+ socketConfig = {
+ FileDescriptorName = "tls";
+ FreeBind = true;
+ Service = "kresd.service";
+ };
+ };
+
+ systemd.sockets.kresd-control = rec {
+ wantedBy = [ "sockets.target" ];
+ before = wantedBy;
+ partOf = [ "kresd.socket" ];
+ listenStreams = [ "/run/kresd/control" ];
+ socketConfig = {
+ FileDescriptorName = "control";
+ Service = "kresd.service";
+ SocketMode = "0660"; # only root user/group may connect and control kresd
+ };
+ };
+
+ systemd.tmpfiles.rules = [ "d '${cfg.cacheDir}' 0770 kresd kresd - -" ];
+
+ systemd.services.kresd = {
+ description = "Knot-resolver daemon";
+
+ serviceConfig = {
+ User = "kresd";
+ Type = "notify";
+ WorkingDirectory = cfg.cacheDir;
+ Restart = "on-failure";
+ Sockets = [ "kresd.socket" "kresd-control.socket" ]
+ ++ optional (cfg.listenTLS != []) "kresd-tls.socket";
+ };
+
+ # Trust anchor goes from dns-root-data by default.
+ script = ''
+ exec '${package}/bin/kresd' --config '${configFile}' --forks=1
+ '';
+
+ requires = [ "kresd.socket" ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/lambdabot.nix b/nixpkgs/nixos/modules/services/networking/lambdabot.nix
new file mode 100644
index 00000000000..b7c8bd008fe
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/lambdabot.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.lambdabot;
+
+ rc = builtins.toFile "script.rc" cfg.script;
+
+in
+
+{
+
+ ### configuration
+
+ options = {
+
+ services.lambdabot = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the Lambdabot IRC bot";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.lambdabot;
+ defaultText = "pkgs.lambdabot";
+ description = "Used lambdabot package";
+ };
+
+ script = mkOption {
+ type = types.str;
+ default = "";
+ description = "Lambdabot script";
+ };
+
+ };
+
+ };
+
+ ### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.lambdabot = {
+ description = "Lambdabot daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ # Workaround for https://github.com/lambdabot/lambdabot/issues/117
+ script = ''
+ mkdir -p ~/.lambdabot
+ cd ~/.lambdabot
+ mkfifo /run/lambdabot/offline
+ (
+ echo 'rc ${rc}'
+ while true; do
+ cat /run/lambdabot/offline
+ done
+ ) | ${cfg.package}/bin/lambdabot
+ '';
+ serviceConfig = {
+ User = "lambdabot";
+ RuntimeDirectory = [ "lambdabot" ];
+ };
+ };
+
+ users.users.lambdabot = {
+ group = "lambdabot";
+ description = "Lambdabot daemon user";
+ home = "/var/lib/lambdabot";
+ createHome = true;
+ uid = config.ids.uids.lambdabot;
+ };
+
+ users.groups.lambdabot.gid = config.ids.gids.lambdabot;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/libreswan.nix b/nixpkgs/nixos/modules/services/networking/libreswan.nix
new file mode 100644
index 00000000000..280158b89f6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/libreswan.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.libreswan;
+
+ libexec = "${pkgs.libreswan}/libexec/ipsec";
+ ipsec = "${pkgs.libreswan}/sbin/ipsec";
+
+ trim = chars: str: let
+ nonchars = filter (x : !(elem x.value chars))
+ (imap0 (i: v: {ind = i; value = v;}) (stringToCharacters str));
+ in
+ if length nonchars == 0 then ""
+ else substring (head nonchars).ind (add 1 (sub (last nonchars).ind (head nonchars).ind)) str;
+ indent = str: concatStrings (concatMap (s: [" " (trim [" " "\t"] s) "\n"]) (splitString "\n" str));
+ configText = indent (toString cfg.configSetup);
+ connectionText = concatStrings (mapAttrsToList (n: v:
+ ''
+ conn ${n}
+ ${indent v}
+
+ '') cfg.connections);
+ configFile = pkgs.writeText "ipsec.conf"
+ ''
+ config setup
+ ${configText}
+
+ ${connectionText}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.libreswan = {
+
+ enable = mkEnableOption "libreswan ipsec service";
+
+ configSetup = mkOption {
+ type = types.lines;
+ default = ''
+ protostack=netkey
+ nat_traversal=yes
+ virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v4:100.64.0.0/10,%v6:fd00::/8,%v6:fe80::/10
+ '';
+ example = ''
+ secretsfile=/root/ipsec.secrets
+ protostack=netkey
+ nat_traversal=yes
+ virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v4:100.64.0.0/10,%v6:fd00::/8,%v6:fe80::/10
+ '';
+ description = "Options to go in the 'config setup' section of the libreswan ipsec configuration";
+ };
+
+ connections = mkOption {
+ type = types.attrsOf types.lines;
+ default = {};
+ example = {
+ myconnection = ''
+ auto=add
+ left=%defaultroute
+ leftid=@user
+
+ right=my.vpn.com
+
+ ikev2=no
+ ikelifetime=8h
+ '';
+ };
+ description = "A set of connections to define for the libreswan ipsec service";
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.libreswan pkgs.iproute ];
+
+ systemd.services.ipsec = {
+ description = "Internet Key Exchange (IKE) Protocol Daemon for IPsec";
+ path = [
+ "${pkgs.libreswan}"
+ "${pkgs.iproute}"
+ "${pkgs.procps}"
+ "${pkgs.nssTools}"
+ "${pkgs.iptables}"
+ "${pkgs.nettools}"
+ ];
+
+ wants = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ Restart = "always";
+ EnvironmentFile = "-${pkgs.libreswan}/etc/sysconfig/pluto";
+ ExecStartPre = [
+ "${libexec}/addconn --config ${configFile} --checkconfig"
+ "${libexec}/_stackmanager start"
+ "${ipsec} --checknss"
+ "${ipsec} --checknflog"
+ ];
+ ExecStart = "${libexec}/pluto --config ${configFile} --nofork \$PLUTO_OPTIONS";
+ ExecStop = "${libexec}/whack --shutdown";
+ ExecStopPost = [
+ "${pkgs.iproute}/bin/ip xfrm policy flush"
+ "${pkgs.iproute}/bin/ip xfrm state flush"
+ "${ipsec} --stopnflog"
+ ];
+ ExecReload = "${libexec}/whack --listen";
+ };
+
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/lldpd.nix b/nixpkgs/nixos/modules/services/networking/lldpd.nix
new file mode 100644
index 00000000000..d5de9c45d84
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/lldpd.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.lldpd;
+
+in
+
+{
+ options.services.lldpd = {
+ enable = mkEnableOption "Link Layer Discovery Protocol Daemon";
+
+ extraArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "-c" "-k" "-I eth0" ];
+ description = "List of command line parameters for lldpd";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users._lldpd = {
+ description = "lldpd user";
+ group = "_lldpd";
+ home = "/run/lldpd";
+ isSystemUser = true;
+ };
+ users.groups._lldpd = {};
+
+ environment.systemPackages = [ pkgs.lldpd ];
+ systemd.packages = [ pkgs.lldpd ];
+
+ systemd.services.lldpd = {
+ wantedBy = [ "multi-user.target" ];
+ environment.LLDPD_OPTIONS = concatStringsSep " " cfg.extraArgs;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/logmein-hamachi.nix b/nixpkgs/nixos/modules/services/networking/logmein-hamachi.nix
new file mode 100644
index 00000000000..11cbdda2f84
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/logmein-hamachi.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.logmein-hamachi;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.logmein-hamachi.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Whether to enable LogMeIn Hamachi, a proprietary
+ (closed source) commercial VPN software.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.logmein-hamachi = {
+ description = "LogMeIn Hamachi Daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.logmein-hamachi}/bin/hamachid";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.logmein-hamachi ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mailpile.nix b/nixpkgs/nixos/modules/services/networking/mailpile.nix
new file mode 100644
index 00000000000..c42d3d5a44c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mailpile.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.mailpile;
+
+ hostname = cfg.hostname;
+ port = cfg.port;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.mailpile = {
+ enable = mkOption {
+ default = false;
+ description = "
+ Whether to enable Mailpile the mail client.
+ ";
+ };
+ hostname = mkOption {
+ default = "localhost";
+ description = "Listen to this hostname or ip.";
+ };
+ port = mkOption {
+ default = "33411";
+ description = "Listen on this port.";
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.mailpile.enable {
+
+ users.users.mailpile =
+ { uid = config.ids.uids.mailpile;
+ description = "Mailpile user";
+ createHome = true;
+ home = "/var/lib/mailpile";
+ };
+
+ users.groups.mailpile =
+ { gid = config.ids.gids.mailpile;
+ };
+
+ systemd.services.mailpile =
+ {
+ description = "Mailpile server.";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "mailpile";
+ ExecStart = "${pkgs.mailpile}/bin/mailpile --www ${hostname}:${port} --wait";
+ # mixed - first send SIGINT to main process,
+ # then after 2min send SIGKILL to whole group if neccessary
+ KillMode = "mixed";
+ KillSignal = "SIGINT"; # like Ctrl+C - safe mailpile shutdown
+ TimeoutSec = 120; # wait 2min untill SIGKILL
+ };
+ environment.MAILPILE_HOME = "/var/lib/mailpile/.local/share/Mailpile";
+ };
+
+ environment.systemPackages = [ pkgs.mailpile ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/matterbridge.nix b/nixpkgs/nixos/modules/services/networking/matterbridge.nix
new file mode 100644
index 00000000000..1fd63348c16
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/matterbridge.nix
@@ -0,0 +1,118 @@
+{ options, config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.matterbridge;
+
+ matterbridgeConfToml =
+ if cfg.configPath == null then
+ pkgs.writeText "matterbridge.toml" (cfg.configFile)
+ else
+ cfg.configPath;
+
+in
+
+{
+ options = {
+ services.matterbridge = {
+ enable = mkEnableOption "Matterbridge chat platform bridge";
+
+ configPath = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "/etc/nixos/matterbridge.toml";
+ description = ''
+ The path to the matterbridge configuration file.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.str;
+ example = ''
+ # WARNING: as this file contains credentials, do not use this option!
+ # It is kept only for backwards compatibility, and would cause your
+ # credentials to be in the nix-store, thus with the world-readable
+ # permission bits.
+ # Use services.matterbridge.configPath instead.
+
+ [irc]
+ [irc.freenode]
+ Server="irc.freenode.net:6667"
+ Nick="matterbot"
+
+ [mattermost]
+ [mattermost.work]
+ # Do not prefix it with http:// or https://
+ Server="yourmattermostserver.domain"
+ Team="yourteam"
+ Login="yourlogin"
+ Password="yourpass"
+ PrefixMessagesWithNick=true
+
+ [[gateway]]
+ name="gateway1"
+ enable=true
+ [[gateway.inout]]
+ account="irc.freenode"
+ channel="#testing"
+
+ [[gateway.inout]]
+ account="mattermost.work"
+ channel="off-topic"
+ '';
+ description = ''
+ WARNING: THIS IS INSECURE, as your password will end up in
+ <filename>/nix/store</filename>, thus publicly readable. Use
+ <literal>services.matterbridge.configPath</literal> instead.
+
+ The matterbridge configuration file in the TOML file format.
+ '';
+ };
+ user = mkOption {
+ type = types.str;
+ default = "matterbridge";
+ description = ''
+ User which runs the matterbridge service.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "matterbridge";
+ description = ''
+ Group which runs the matterbridge service.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ warnings = optional options.services.matterbridge.configFile.isDefined
+ "The option services.matterbridge.configFile is insecure and should be replaced with services.matterbridge.configPath";
+
+ users.users = optional (cfg.user == "matterbridge")
+ { name = "matterbridge";
+ group = "matterbridge";
+ };
+
+ users.groups = optional (cfg.group == "matterbridge")
+ { name = "matterbridge";
+ };
+
+ systemd.services.matterbridge = {
+ description = "Matterbridge chat platform bridge";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${pkgs.matterbridge.bin}/bin/matterbridge -conf ${matterbridgeConfToml}";
+ Restart = "always";
+ RestartSec = "10";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/minidlna.nix b/nixpkgs/nixos/modules/services/networking/minidlna.nix
new file mode 100644
index 00000000000..3ddea3c9757
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/minidlna.nix
@@ -0,0 +1,176 @@
+# Module for MiniDLNA, a simple DLNA server.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.minidlna;
+ port = 8200;
+in
+
+{
+ ###### interface
+ options = {
+ services.minidlna.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Whether to enable MiniDLNA, a simple DLNA server. It serves
+ media files such as video and music to DLNA client devices
+ such as televisions and media players.
+ '';
+ };
+
+ services.minidlna.mediaDirs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "/data/media" "V,/home/alice/video" ];
+ description =
+ ''
+ Directories to be scanned for media files. The prefixes
+ <literal>A,</literal>, <literal>V,</literal> and
+ <literal>P,</literal> restrict a directory to audio, video
+ or image files. The directories must be accessible to the
+ <literal>minidlna</literal> user account.
+ '';
+ };
+
+ services.minidlna.friendlyName = mkOption {
+ type = types.str;
+ default = "${config.networking.hostName} MiniDLNA";
+ defaultText = "$HOSTNAME MiniDLNA";
+ example = "rpi3";
+ description =
+ ''
+ Name that the DLNA server presents to clients.
+ '';
+ };
+
+ services.minidlna.rootContainer = mkOption {
+ type = types.str;
+ default = ".";
+ example = "B";
+ description =
+ ''
+ Use a different container as the root of the directory tree presented
+ to clients. The possible values are:
+ - "." - standard container
+ - "B" - "Browse Directory"
+ - "M" - "Music"
+ - "P" - "Pictures"
+ - "V" - "Video"
+ - Or, you can specify the ObjectID of your desired root container
+ (eg. 1$F for Music/Playlists)
+ If you specify "B" and the client device is audio-only then
+ "Music/Folders" will be used as root.
+ '';
+ };
+
+ services.minidlna.loglevel = mkOption {
+ type = types.str;
+ default = "warn";
+ example = "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn";
+ description =
+ ''
+ Defines the type of messages that should be logged, and down to
+ which level of importance they should be considered.
+
+ The possible types are “artwork”, “database”, “general”, “http”,
+ “inotify”, “metadata”, “scanner”, “ssdp” and “tivo”.
+
+ The levels are “off”, “fatal”, “error”, “warn”, “info” and
+ “debug”, listed here in order of decreasing importance. “off”
+ turns off logging messages entirely, “fatal” logs the most
+ critical messages only, and so on down to “debug” that logs every
+ single messages.
+
+ The types are comma-separated, followed by an equal sign (‘=’),
+ followed by a level that applies to the preceding types. This can
+ be repeated, separating each of these constructs with a comma.
+
+ Defaults to “general,artwork,database,inotify,scanner,metadata,
+ http,ssdp,tivo=warn” which logs every type of message at the
+ “warn” level.
+ '';
+ };
+
+ services.minidlna.config = mkOption {
+ type = types.lines;
+ description =
+ ''
+ The contents of MiniDLNA's configuration file.
+ When the service is activated, a basic template is generated
+ from the current options opened here.
+ '';
+ };
+
+ services.minidlna.extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ # Not exhaustive example
+ # Support for streaming .jpg and .mp3 files to a TiVo supporting HMO.
+ enable_tivo=no
+ # SSDP notify interval, in seconds.
+ notify_interval=10
+ # maximum number of simultaneous connections
+ # note: many clients open several simultaneous connections while
+ # streaming
+ max_connections=50
+ # set this to yes to allow symlinks that point outside user-defined
+ # media_dirs.
+ wide_links=yes
+ '';
+ description =
+ ''
+ Extra minidlna options not yet opened for configuration here
+ (strict_dlna, model_number, model_name, etc...). This is appended
+ to the current service already provided.
+ '';
+ };
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.minidlna.config =
+ ''
+ port=${toString port}
+ friendly_name=${cfg.friendlyName}
+ db_dir=/var/cache/minidlna
+ log_level=${cfg.loglevel}
+ inotify=yes
+ root_container=${cfg.rootContainer}
+ ${concatMapStrings (dir: ''
+ media_dir=${dir}
+ '') cfg.mediaDirs}
+ ${cfg.extraConfig}
+ '';
+
+ users.users.minidlna = {
+ description = "MiniDLNA daemon user";
+ group = "minidlna";
+ uid = config.ids.uids.minidlna;
+ };
+
+ users.groups.minidlna.gid = config.ids.gids.minidlna;
+
+ systemd.services.minidlna =
+ { description = "MiniDLNA Server";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig =
+ { User = "minidlna";
+ Group = "minidlna";
+ CacheDirectory = "minidlna";
+ RuntimeDirectory = "minidlna";
+ PIDFile = "/run/minidlna/pid";
+ ExecStart =
+ "${pkgs.minidlna}/sbin/minidlnad -S -P /run/minidlna/pid" +
+ " -f ${pkgs.writeText "minidlna.conf" cfg.config}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/miniupnpd.nix b/nixpkgs/nixos/modules/services/networking/miniupnpd.nix
new file mode 100644
index 00000000000..c095d994854
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/miniupnpd.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.miniupnpd;
+ configFile = pkgs.writeText "miniupnpd.conf" ''
+ ext_ifname=${cfg.externalInterface}
+ enable_natpmp=${if cfg.natpmp then "yes" else "no"}
+ enable_upnp=${if cfg.upnp then "yes" else "no"}
+
+ ${concatMapStrings (range: ''
+ listening_ip=${range}
+ '') cfg.internalIPs}
+
+ ${cfg.appendConfig}
+ '';
+in
+{
+ options = {
+ services.miniupnpd = {
+ enable = mkEnableOption "MiniUPnP daemon";
+
+ externalInterface = mkOption {
+ type = types.str;
+ description = ''
+ Name of the external interface.
+ '';
+ };
+
+ internalIPs = mkOption {
+ type = types.listOf types.str;
+ example = [ "192.168.1.1/24" "enp1s0" ];
+ description = ''
+ The IP address ranges to listen on.
+ '';
+ };
+
+ natpmp = mkEnableOption "NAT-PMP support";
+
+ upnp = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable UPNP support.
+ '';
+ };
+
+ appendConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Configuration lines appended to the MiniUPnP config.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ networking.firewall.extraCommands = ''
+ ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_init.sh -i ${cfg.externalInterface}
+ '';
+
+ networking.firewall.extraStopCommands = ''
+ ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_removeall.sh -i ${cfg.externalInterface}
+ '';
+
+ systemd.services.miniupnpd = {
+ description = "MiniUPnP daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.miniupnpd}/bin/miniupnpd -f ${configFile}";
+ PIDFile = "/run/miniupnpd.pid";
+ Type = "forking";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/miredo.nix b/nixpkgs/nixos/modules/services/networking/miredo.nix
new file mode 100644
index 00000000000..2c8393fb5b4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/miredo.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.miredo;
+ pidFile = "/run/miredo.pid";
+ miredoConf = pkgs.writeText "miredo.conf" ''
+ InterfaceName ${cfg.interfaceName}
+ ServerAddress ${cfg.serverAddress}
+ ${optionalString (cfg.bindAddress != null) "BindAddress ${cfg.bindAddress}"}
+ ${optionalString (cfg.bindPort != null) "BindPort ${cfg.bindPort}"}
+ '';
+in
+{
+
+ ###### interface
+
+ options = {
+
+ services.miredo = {
+
+ enable = mkEnableOption "the Miredo IPv6 tunneling service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.miredo;
+ defaultText = "pkgs.miredo";
+ description = ''
+ The package to use for the miredo daemon's binary.
+ '';
+ };
+
+ serverAddress = mkOption {
+ default = "teredo.remlab.net";
+ type = types.str;
+ description = ''
+ The hostname or primary IPv4 address of the Teredo server.
+ This setting is required if Miredo runs as a Teredo client.
+ "teredo.remlab.net" is an experimental service for testing only.
+ Please use another server for production and/or large scale deployments.
+ '';
+ };
+
+ interfaceName = mkOption {
+ default = "teredo";
+ type = types.str;
+ description = ''
+ Name of the network tunneling interface.
+ '';
+ };
+
+ bindAddress = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Depending on the local firewall/NAT rules, you might need to force
+ Miredo to use a fixed UDP port and or IPv4 address.
+ '';
+ };
+
+ bindPort = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Depending on the local firewall/NAT rules, you might need to force
+ Miredo to use a fixed UDP port and or IPv4 address.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.miredo = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ description = "Teredo IPv6 Tunneling Daemon";
+ serviceConfig = {
+ Restart = "always";
+ RestartSec = "5s";
+ ExecStart = "${cfg.package}/bin/miredo -c ${miredoConf} -p ${pidFile} -f";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix b/nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix
new file mode 100644
index 00000000000..e0a6c112e3c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.mjpg-streamer;
+
+in {
+
+ options = {
+
+ services.mjpg-streamer = {
+
+ enable = mkEnableOption "mjpg-streamer webcam streamer";
+
+ inputPlugin = mkOption {
+ type = types.str;
+ default = "input_uvc.so";
+ description = ''
+ Input plugin. See plugins documentation for more information.
+ '';
+ };
+
+ outputPlugin = mkOption {
+ type = types.str;
+ default = "output_http.so -w @www@ -n -p 5050";
+ description = ''
+ Output plugin. <literal>@www@</literal> is substituted for default mjpg-streamer www directory.
+ See plugins documentation for more information.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "mjpg-streamer";
+ description = "mjpg-streamer user name.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "video";
+ description = "mjpg-streamer group name.";
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users = optional (cfg.user == "mjpg-streamer") {
+ name = "mjpg-streamer";
+ uid = config.ids.uids.mjpg-streamer;
+ group = cfg.group;
+ };
+
+ systemd.services.mjpg-streamer = {
+ description = "mjpg-streamer webcam streamer";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ Restart = "on-failure";
+ RestartSec = 1;
+ };
+
+ script = ''
+ IPLUGIN="${cfg.inputPlugin}"
+ OPLUGIN="${cfg.outputPlugin}"
+ OPLUGIN="''${OPLUGIN//@www@/${pkgs.mjpg-streamer}/share/mjpg-streamer/www}"
+ exec ${pkgs.mjpg-streamer}/bin/mjpg_streamer -i "$IPLUGIN" -o "$OPLUGIN"
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/monero.nix b/nixpkgs/nixos/modules/services/networking/monero.nix
new file mode 100644
index 00000000000..831e4d60d8d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/monero.nix
@@ -0,0 +1,238 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.monero;
+ dataDir = "/var/lib/monero";
+
+ listToConf = option: list:
+ concatMapStrings (value: "${option}=${value}\n") list;
+
+ login = (cfg.rpc.user != null && cfg.rpc.password != null);
+
+ configFile = with cfg; pkgs.writeText "monero.conf" ''
+ log-file=/dev/stdout
+ data-dir=${dataDir}
+
+ ${optionalString mining.enable ''
+ start-mining=${mining.address}
+ mining-threads=${toString mining.threads}
+ ''}
+
+ rpc-bind-ip=${rpc.address}
+ rpc-bind-port=${toString rpc.port}
+ ${optionalString login ''
+ rpc-login=${rpc.user}:${rpc.password}
+ ''}
+ ${optionalString rpc.restricted ''
+ restrict-rpc=1
+ ''}
+
+ limit-rate-up=${toString limits.upload}
+ limit-rate-down=${toString limits.download}
+ max-concurrency=${toString limits.threads}
+ block-sync-size=${toString limits.syncSize}
+
+ ${listToConf "add-peer" extraNodes}
+ ${listToConf "add-priority-node" priorityNodes}
+ ${listToConf "add-exclusive-node" exclusiveNodes}
+
+ ${extraConfig}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.monero = {
+
+ enable = mkEnableOption "Monero node daemon";
+
+ mining.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to mine moneroj.
+ '';
+ };
+
+ mining.address = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Monero address where to send mining rewards.
+ '';
+ };
+
+ mining.threads = mkOption {
+ type = types.addCheck types.int (x: x>=0);
+ default = 0;
+ description = ''
+ Number of threads used for mining.
+ Set to <literal>0</literal> to use all available.
+ '';
+ };
+
+ rpc.user = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ User name for RPC connections.
+ '';
+ };
+
+ rpc.password = mkOption {
+ type = types.str;
+ default = null;
+ description = ''
+ Password for RPC connections.
+ '';
+ };
+
+ rpc.address = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ IP address the RPC server will bind to.
+ '';
+ };
+
+ rpc.port = mkOption {
+ type = types.int;
+ default = 18081;
+ description = ''
+ Port the RPC server will bind to.
+ '';
+ };
+
+ rpc.restricted = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to restrict RPC to view only commands.
+ '';
+ };
+
+ limits.upload = mkOption {
+ type = types.addCheck types.int (x: x>=-1);
+ default = -1;
+ description = ''
+ Limit of the upload rate in kB/s.
+ Set to <literal>-1</literal> to leave unlimited.
+ '';
+ };
+
+ limits.download = mkOption {
+ type = types.addCheck types.int (x: x>=-1);
+ default = -1;
+ description = ''
+ Limit of the download rate in kB/s.
+ Set to <literal>-1</literal> to leave unlimited.
+ '';
+ };
+
+ limits.threads = mkOption {
+ type = types.addCheck types.int (x: x>=0);
+ default = 0;
+ description = ''
+ Maximum number of threads used for a parallel job.
+ Set to <literal>0</literal> to leave unlimited.
+ '';
+ };
+
+ limits.syncSize = mkOption {
+ type = types.addCheck types.int (x: x>=0);
+ default = 0;
+ description = ''
+ Maximum number of blocks to sync at once.
+ Set to <literal>0</literal> for adaptive.
+ '';
+ };
+
+ extraNodes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ List of additional peer IP addresses to add to the local list.
+ '';
+ };
+
+ priorityNodes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ List of peer IP addresses to connect to and
+ attempt to keep the connection open.
+ '';
+ };
+
+ exclusiveNodes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ List of peer IP addresses to connect to *only*.
+ If given the other peer options will be ignored.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines to be added verbatim to monerod configuration.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton {
+ name = "monero";
+ uid = config.ids.uids.monero;
+ description = "Monero daemon user";
+ home = dataDir;
+ createHome = true;
+ };
+
+ users.groups = singleton {
+ name = "monero";
+ gid = config.ids.gids.monero;
+ };
+
+ systemd.services.monero = {
+ description = "monero daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = "monero";
+ Group = "monero";
+ ExecStart = "${pkgs.monero}/bin/monerod --config-file=${configFile} --non-interactive";
+ Restart = "always";
+ SuccessExitStatus = [ 0 1 ];
+ };
+ };
+
+ assertions = singleton {
+ assertion = cfg.mining.enable -> cfg.mining.address != "";
+ message = ''
+ You need a Monero address to receive mining rewards:
+ specify one using option monero.mining.address.
+ '';
+ };
+
+ };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/morty.nix b/nixpkgs/nixos/modules/services/networking/morty.nix
new file mode 100644
index 00000000000..1b3084fe9ab
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/morty.nix
@@ -0,0 +1,96 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.morty;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.morty = {
+
+ enable = mkEnableOption
+ "Morty proxy server. See https://github.com/asciimoo/morty";
+
+ ipv6 = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Allow IPv6 HTTP requests?";
+ defaultText = "Allow IPv6 HTTP requests.";
+ };
+
+ key = mkOption {
+ type = types.str;
+ default = "";
+ description = "HMAC url validation key (hexadecimal encoded).
+ Leave blank to disable. Without validation key, anyone can
+ submit proxy requests. Leave blank to disable.";
+ defaultText = "No HMAC url validation. Generate with echo -n somevalue | openssl dgst -sha1 -hmac somekey";
+ };
+
+ timeout = mkOption {
+ type = types.int;
+ default = 2;
+ description = "Request timeout in seconds.";
+ defaultText = "A resource now gets 2 seconds to respond.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.morty;
+ defaultText = "pkgs.morty";
+ description = "morty package to use.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 3000;
+ description = "Listing port";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "The address on which the service listens";
+ defaultText = "127.0.0.1 (localhost)";
+ };
+
+ };
+
+ };
+
+ ###### Service definition
+
+ config = mkIf config.services.morty.enable {
+
+ users.users.morty =
+ { description = "Morty user";
+ createHome = true;
+ home = "/var/lib/morty";
+ };
+
+ systemd.services.morty =
+ {
+ description = "Morty sanitizing proxy server.";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "morty";
+ ExecStart = ''${cfg.package}/bin/morty \
+ -listen ${cfg.listenAddress}:${toString cfg.port} \
+ ${optionalString cfg.ipv6 "-ipv6"} \
+ ${optionalString (cfg.key != "") "-key " + cfg.key} \
+ '';
+ };
+ };
+ environment.systemPackages = [ cfg.package ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mosquitto.nix b/nixpkgs/nixos/modules/services/networking/mosquitto.nix
new file mode 100644
index 00000000000..d2feb93e2b7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mosquitto.nix
@@ -0,0 +1,231 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+ cfg = config.services.mosquitto;
+
+ listenerConf = optionalString cfg.ssl.enable ''
+ listener ${toString cfg.ssl.port} ${cfg.ssl.host}
+ cafile ${cfg.ssl.cafile}
+ certfile ${cfg.ssl.certfile}
+ keyfile ${cfg.ssl.keyfile}
+ '';
+
+ passwordConf = optionalString cfg.checkPasswords ''
+ password_file ${cfg.dataDir}/passwd
+ '';
+
+ mosquittoConf = pkgs.writeText "mosquitto.conf" ''
+ acl_file ${aclFile}
+ persistence true
+ allow_anonymous ${boolToString cfg.allowAnonymous}
+ bind_address ${cfg.host}
+ port ${toString cfg.port}
+ ${passwordConf}
+ ${listenerConf}
+ ${cfg.extraConf}
+ '';
+
+ userAcl = (concatStringsSep "\n\n" (mapAttrsToList (n: c:
+ "user ${n}\n" + (concatStringsSep "\n" c.acl)) cfg.users
+ ));
+
+ aclFile = pkgs.writeText "mosquitto.acl" ''
+ ${cfg.aclExtraConf}
+ ${userAcl}
+ '';
+
+in
+
+{
+
+ ###### Interface
+
+ options = {
+ services.mosquitto = {
+ enable = mkEnableOption "the MQTT Mosquitto broker";
+
+ host = mkOption {
+ default = "127.0.0.1";
+ example = "0.0.0.0";
+ type = types.str;
+ description = ''
+ Host to listen on without SSL.
+ '';
+ };
+
+ port = mkOption {
+ default = 1883;
+ example = 1883;
+ type = types.int;
+ description = ''
+ Port on which to listen without SSL.
+ '';
+ };
+
+ ssl = {
+ enable = mkEnableOption "SSL listener";
+
+ cafile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "Path to PEM encoded CA certificates.";
+ };
+
+ certfile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "Path to PEM encoded server certificate.";
+ };
+
+ keyfile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "Path to PEM encoded server key.";
+ };
+
+ host = mkOption {
+ default = "0.0.0.0";
+ example = "localhost";
+ type = types.str;
+ description = ''
+ Host to listen on with SSL.
+ '';
+ };
+
+ port = mkOption {
+ default = 8883;
+ example = 8883;
+ type = types.int;
+ description = ''
+ Port on which to listen with SSL.
+ '';
+ };
+ };
+
+ dataDir = mkOption {
+ default = "/var/lib/mosquitto";
+ type = types.path;
+ description = ''
+ The data directory.
+ '';
+ };
+
+ users = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ password = mkOption {
+ type = with types; uniq (nullOr str);
+ default = null;
+ description = ''
+ Specifies the (clear text) password for the MQTT User.
+ '';
+ };
+
+ hashedPassword = mkOption {
+ type = with types; uniq (nullOr str);
+ default = null;
+ description = ''
+ Specifies the hashed password for the MQTT User.
+ <option>hashedPassword</option> overrides <option>password</option>.
+ To generate hashed password install <literal>mosquitto</literal>
+ package and use <literal>mosquitto_passwd</literal>.
+ '';
+ };
+
+ acl = mkOption {
+ type = types.listOf types.str;
+ example = [ "topic read A/B" "topic A/#" ];
+ description = ''
+ Control client access to topics on the broker.
+ '';
+ };
+ };
+ });
+ example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; };
+ description = ''
+ A set of users and their passwords and ACLs.
+ '';
+ };
+
+ allowAnonymous = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Allow clients to connect without authentication.
+ '';
+ };
+
+ checkPasswords = mkOption {
+ default = false;
+ example = true;
+ type = types.bool;
+ description = ''
+ Refuse connection when clients provide incorrect passwords.
+ '';
+ };
+
+ extraConf = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra config to append to `mosquitto.conf` file.
+ '';
+ };
+
+ aclExtraConf = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra config to prepend to the ACL file.
+ '';
+ };
+
+ };
+ };
+
+
+ ###### Implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.mosquitto = {
+ description = "Mosquitto MQTT Broker Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ Type = "notify";
+ NotifyAccess = "main";
+ User = "mosquitto";
+ Group = "mosquitto";
+ RuntimeDirectory = "mosquitto";
+ WorkingDirectory = cfg.dataDir;
+ Restart = "on-failure";
+ ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${mosquittoConf}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ preStart = ''
+ rm -f ${cfg.dataDir}/passwd
+ touch ${cfg.dataDir}/passwd
+ '' + concatStringsSep "\n" (
+ mapAttrsToList (n: c:
+ if c.hashedPassword != null then
+ "echo '${n}:${c.hashedPassword}' >> ${cfg.dataDir}/passwd"
+ else optionalString (c.password != null)
+ "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} '${c.password}'"
+ ) cfg.users);
+ };
+
+ users.users.mosquitto = {
+ description = "Mosquitto MQTT Broker Daemon owner";
+ group = "mosquitto";
+ uid = config.ids.uids.mosquitto;
+ home = cfg.dataDir;
+ createHome = true;
+ };
+
+ users.groups.mosquitto.gid = config.ids.gids.mosquitto;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mstpd.nix b/nixpkgs/nixos/modules/services/networking/mstpd.nix
new file mode 100644
index 00000000000..5d1fc4a6542
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mstpd.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.services.mstpd;
+in
+with lib;
+{
+ options.services.mstpd = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable the multiple spanning tree protocol daemon.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.mstpd ];
+
+ systemd.services.mstpd = {
+ description = "Multiple Spanning Tree Protocol Daemon";
+ wantedBy = [ "network.target" ];
+ unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "@${pkgs.mstpd}/bin/mstpd mstpd";
+ PIDFile = "/run/mstpd.pid";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix b/nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix
new file mode 100644
index 00000000000..d896f227b82
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix
@@ -0,0 +1,110 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.mtprotoproxy;
+
+ configOpts = {
+ PORT = cfg.port;
+ USERS = cfg.users;
+ SECURE_ONLY = cfg.secureOnly;
+ } // lib.optionalAttrs (cfg.adTag != null) { AD_TAG = cfg.adTag; }
+ // cfg.extraConfig;
+
+ convertOption = opt:
+ if isString opt || isInt opt then
+ builtins.toJSON opt
+ else if isBool opt then
+ if opt then "True" else "False"
+ else if isList opt then
+ "[" + concatMapStringsSep "," convertOption opt + "]"
+ else if isAttrs opt then
+ "{" + concatStringsSep "," (mapAttrsToList (name: opt: "${builtins.toJSON name}: ${convertOption opt}") opt) + "}"
+ else
+ throw "Invalid option type";
+
+ configFile = pkgs.writeText "config.py" (concatStringsSep "\n" (mapAttrsToList (name: opt: "${name} = ${convertOption opt}") configOpts));
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.mtprotoproxy = {
+
+ enable = mkEnableOption "mtprotoproxy";
+
+ port = mkOption {
+ type = types.int;
+ default = 3256;
+ description = ''
+ TCP port to accept mtproto connections on.
+ '';
+ };
+
+ users = mkOption {
+ type = types.attrsOf types.str;
+ example = {
+ tg = "00000000000000000000000000000000";
+ tg2 = "0123456789abcdef0123456789abcdef";
+ };
+ description = ''
+ Allowed users and their secrets. A secret is a 32 characters long hex string.
+ '';
+ };
+
+ secureOnly = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Don't allow users to connect in non-secure mode (without random padding).
+ '';
+ };
+
+ adTag = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ # Taken from mtproxyproto's repo.
+ example = "3c09c680b76ee91a4c25ad51f742267d";
+ description = ''
+ Tag for advertising that can be obtained from @MTProxybot.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ example = {
+ STATS_PRINT_PERIOD = 600;
+ };
+ description = ''
+ Extra configuration options for mtprotoproxy.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.mtprotoproxy = {
+ description = "MTProto Proxy Daemon";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.mtprotoproxy}/bin/mtprotoproxy ${configFile}";
+ DynamicUser = true;
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/murmur.nix b/nixpkgs/nixos/modules/services/networking/murmur.nix
new file mode 100644
index 00000000000..082953d2f6a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/murmur.nix
@@ -0,0 +1,265 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.murmur;
+ forking = cfg.logFile != null;
+ configFile = pkgs.writeText "murmurd.ini" ''
+ database=/var/lib/murmur/murmur.sqlite
+ dbDriver=QSQLITE
+
+ autobanAttempts=${toString cfg.autobanAttempts}
+ autobanTimeframe=${toString cfg.autobanTimeframe}
+ autobanTime=${toString cfg.autobanTime}
+
+ logfile=${optionalString (cfg.logFile != null) cfg.logFile}
+ ${optionalString forking "pidfile=/run/murmur/murmurd.pid"}
+
+ welcometext="${cfg.welcometext}"
+ port=${toString cfg.port}
+
+ ${if cfg.hostName == "" then "" else "host="+cfg.hostName}
+ ${if cfg.password == "" then "" else "serverpassword="+cfg.password}
+
+ bandwidth=${toString cfg.bandwidth}
+ users=${toString cfg.users}
+
+ textmessagelength=${toString cfg.textMsgLength}
+ imagemessagelength=${toString cfg.imgMsgLength}
+ allowhtml=${boolToString cfg.allowHtml}
+ logdays=${toString cfg.logDays}
+ bonjour=${boolToString cfg.bonjour}
+ sendversion=${boolToString cfg.sendVersion}
+
+ ${if cfg.registerName == "" then "" else "registerName="+cfg.registerName}
+ ${if cfg.registerPassword == "" then "" else "registerPassword="+cfg.registerPassword}
+ ${if cfg.registerUrl == "" then "" else "registerUrl="+cfg.registerUrl}
+ ${if cfg.registerHostname == "" then "" else "registerHostname="+cfg.registerHostname}
+
+ certrequired=${boolToString cfg.clientCertRequired}
+ ${if cfg.sslCert == "" then "" else "sslCert="+cfg.sslCert}
+ ${if cfg.sslKey == "" then "" else "sslKey="+cfg.sslKey}
+ ${if cfg.sslCa == "" then "" else "sslCA="+cfg.sslCa}
+
+ ${cfg.extraConfig}
+ '';
+in
+{
+ options = {
+ services.murmur = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "If enabled, start the Murmur Mumble server.";
+ };
+
+ autobanAttempts = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ Number of attempts a client is allowed to make in
+ <literal>autobanTimeframe</literal> seconds, before being
+ banned for <literal>autobanTime</literal>.
+ '';
+ };
+
+ autobanTimeframe = mkOption {
+ type = types.int;
+ default = 120;
+ description = ''
+ Timeframe in which a client can connect without being banned
+ for repeated attempts (in seconds).
+ '';
+ };
+
+ autobanTime = mkOption {
+ type = types.int;
+ default = 300;
+ description = "The amount of time an IP ban lasts (in seconds).";
+ };
+
+ logFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/var/log/murmur/murmurd.log";
+ description = "Path to the log file for Murmur daemon. Empty means log to journald.";
+ };
+
+ welcometext = mkOption {
+ type = types.str;
+ default = "";
+ description = "Welcome message for connected clients.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 64738;
+ description = "Ports to bind to (UDP and TCP).";
+ };
+
+ hostName = mkOption {
+ type = types.str;
+ default = "";
+ description = "Host to bind to. Defaults binding on all addresses.";
+ };
+
+ password = mkOption {
+ type = types.str;
+ default = "";
+ description = "Required password to join server, if specified.";
+ };
+
+ bandwidth = mkOption {
+ type = types.int;
+ default = 72000;
+ description = ''
+ Maximum bandwidth (in bits per second) that clients may send
+ speech at.
+ '';
+ };
+
+ users = mkOption {
+ type = types.int;
+ default = 100;
+ description = "Maximum number of concurrent clients allowed.";
+ };
+
+ textMsgLength = mkOption {
+ type = types.int;
+ default = 5000;
+ description = "Max length of text messages. Set 0 for no limit.";
+ };
+
+ imgMsgLength = mkOption {
+ type = types.int;
+ default = 131072;
+ description = "Max length of image messages. Set 0 for no limit.";
+ };
+
+ allowHtml = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Allow HTML in client messages, comments, and channel
+ descriptions.
+ '';
+ };
+
+ logDays = mkOption {
+ type = types.int;
+ default = 31;
+ description = ''
+ How long to store RPC logs for in the database. Set 0 to
+ keep logs forever, or -1 to disable DB logging.
+ '';
+ };
+
+ bonjour = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable Bonjour auto-discovery, which allows clients over
+ your LAN to automatically discover Murmur servers.
+ '';
+ };
+
+ sendVersion = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Send Murmur version in UDP response.";
+ };
+
+ registerName = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Public server registration name, and also the name of the
+ Root channel. Even if you don't publicly register your
+ server, you probably still want to set this.
+ '';
+ };
+
+ registerPassword = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Public server registry password, used authenticate your
+ server to the registry to prevent impersonation; required for
+ subsequent registry updates.
+ '';
+ };
+
+ registerUrl = mkOption {
+ type = types.str;
+ default = "";
+ description = "URL website for your server.";
+ };
+
+ registerHostname = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ DNS hostname where your server can be reached. This is only
+ needed if you want your server to be accessed by its
+ hostname and not IP - but the name *must* resolve on the
+ internet properly.
+ '';
+ };
+
+ clientCertRequired = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Require clients to authenticate via certificates.";
+ };
+
+ sslCert = mkOption {
+ type = types.str;
+ default = "";
+ description = "Path to your SSL certificate.";
+ };
+
+ sslKey = mkOption {
+ type = types.str;
+ default = "";
+ description = "Path to your SSL key.";
+ };
+
+ sslCa = mkOption {
+ type = types.str;
+ default = "";
+ description = "Path to your SSL CA certificate.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra configuration to put into murmur.ini.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.murmur = {
+ description = "Murmur Service user";
+ home = "/var/lib/murmur";
+ createHome = true;
+ uid = config.ids.uids.murmur;
+ };
+
+ systemd.services.murmur = {
+ description = "Murmur Chat Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target "];
+
+ serviceConfig = {
+ # murmurd doesn't fork when logging to the console.
+ Type = if forking then "forking" else "simple";
+ PIDFile = mkIf forking "/run/murmur/murmurd.pid";
+ RuntimeDirectory = mkIf forking "murmur";
+ User = "murmur";
+ ExecStart = "${pkgs.murmur}/bin/murmurd -ini ${configFile}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mxisd.nix b/nixpkgs/nixos/modules/services/networking/mxisd.nix
new file mode 100644
index 00000000000..02e89f441b3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mxisd.nix
@@ -0,0 +1,116 @@
+{ 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" ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = "mxisd";
+ Group = "mxisd";
+ ExecStart = "${cfg.package}/bin/mxisd -c ${configFile}";
+ WorkingDirectory = cfg.dataDir;
+ Restart = "on-failure";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/namecoind.nix b/nixpkgs/nixos/modules/services/networking/namecoind.nix
new file mode 100644
index 00000000000..c8ee0a2f564
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/namecoind.nix
@@ -0,0 +1,204 @@
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.namecoind;
+ dataDir = "/var/lib/namecoind";
+ useSSL = (cfg.rpc.certificate != null) && (cfg.rpc.key != null);
+ useRPC = (cfg.rpc.user != null) && (cfg.rpc.password != null);
+
+ listToConf = option: list:
+ concatMapStrings (value :"${option}=${value}\n") list;
+
+ configFile = pkgs.writeText "namecoin.conf" (''
+ server=1
+ daemon=0
+ txindex=1
+ txprevcache=1
+ walletpath=${cfg.wallet}
+ gen=${if cfg.generate then "1" else "0"}
+ ${listToConf "addnode" cfg.extraNodes}
+ ${listToConf "connect" cfg.trustedNodes}
+ '' + optionalString useRPC ''
+ rpcbind=${cfg.rpc.address}
+ rpcport=${toString cfg.rpc.port}
+ rpcuser=${cfg.rpc.user}
+ rpcpassword=${cfg.rpc.password}
+ ${listToConf "rpcallowip" cfg.rpc.allowFrom}
+ '' + optionalString useSSL ''
+ rpcssl=1
+ rpcsslcertificatechainfile=${cfg.rpc.certificate}
+ rpcsslprivatekeyfile=${cfg.rpc.key}
+ rpcsslciphers=TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH
+ '');
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.namecoind = {
+
+ enable = mkEnableOption "namecoind, Namecoin client";
+
+ wallet = mkOption {
+ type = types.path;
+ default = "${dataDir}/wallet.dat";
+ description = ''
+ Wallet file. The ownership of the file has to be
+ namecoin:namecoin, and the permissions must be 0640.
+ '';
+ };
+
+ generate = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to generate (mine) Namecoins.
+ '';
+ };
+
+ extraNodes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ List of additional peer IP addresses to connect to.
+ '';
+ };
+
+ trustedNodes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ List of the only peer IP addresses to connect to. If specified
+ no other connection will be made.
+ '';
+ };
+
+ rpc.user = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ User name for RPC connections.
+ '';
+ };
+
+ rpc.password = mkOption {
+ type = types.str;
+ default = null;
+ description = ''
+ Password for RPC connections.
+ '';
+ };
+
+ rpc.address = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ IP address the RPC server will bind to.
+ '';
+ };
+
+ rpc.port = mkOption {
+ type = types.int;
+ default = 8332;
+ description = ''
+ Port the RPC server will bind to.
+ '';
+ };
+
+ rpc.certificate = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/var/lib/namecoind/server.cert";
+ description = ''
+ Certificate file for securing RPC connections.
+ '';
+ };
+
+ rpc.key = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/var/lib/namecoind/server.pem";
+ description = ''
+ Key file for securing RPC connections.
+ '';
+ };
+
+
+ rpc.allowFrom = mkOption {
+ type = types.listOf types.str;
+ default = [ "127.0.0.1" ];
+ description = ''
+ List of IP address ranges allowed to use the RPC API.
+ Wiledcards (*) can be user to specify a range.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.dnschain.extraConfig = ''
+ [namecoin]
+ config = ${configFile}
+ '';
+
+ users.users = singleton {
+ name = "namecoin";
+ uid = config.ids.uids.namecoin;
+ description = "Namecoin daemon user";
+ home = dataDir;
+ createHome = true;
+ };
+
+ users.groups = singleton {
+ name = "namecoin";
+ gid = config.ids.gids.namecoin;
+ };
+
+ systemd.services.namecoind = {
+ description = "Namecoind daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = "namecoin";
+ Group = "namecoin";
+ ExecStart = "${pkgs.namecoind}/bin/namecoind -conf=${configFile} -datadir=${dataDir} -printtoconsole";
+ ExecStop = "${pkgs.coreutils}/bin/kill -KILL $MAINPID";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ Nice = "10";
+ PrivateTmp = true;
+ TimeoutStopSec = "60s";
+ TimeoutStartSec = "2s";
+ Restart = "always";
+ StartLimitInterval = "120s";
+ StartLimitBurst = "5";
+ };
+
+ preStart = optionalString (cfg.wallet != "${dataDir}/wallet.dat") ''
+ # check wallet file permissions
+ if [ "$(stat --printf '%u' ${cfg.wallet})" != "${toString config.ids.uids.namecoin}" \
+ -o "$(stat --printf '%g' ${cfg.wallet})" != "${toString config.ids.gids.namecoin}" \
+ -o "$(stat --printf '%a' ${cfg.wallet})" != "640" ]; then
+ echo "ERROR: bad ownership or rights on ${cfg.wallet}" >&2
+ exit 1
+ fi
+ '';
+
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nat.nix b/nixpkgs/nixos/modules/services/networking/nat.nix
new file mode 100644
index 00000000000..89d8590093d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nat.nix
@@ -0,0 +1,282 @@
+# This module enables Network Address Translation (NAT).
+# XXX: todo: support multiple upstream links
+# see http://yesican.chsoft.biz/lartc/MultihomedLinuxNetworking.html
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.networking.nat;
+
+ dest = if cfg.externalIP == null then "-j MASQUERADE" else "-j SNAT --to-source ${cfg.externalIP}";
+
+ flushNat = ''
+ iptables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true
+ iptables -w -t nat -F nixos-nat-pre 2>/dev/null || true
+ iptables -w -t nat -X nixos-nat-pre 2>/dev/null || true
+ iptables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true
+ iptables -w -t nat -F nixos-nat-post 2>/dev/null || true
+ iptables -w -t nat -X nixos-nat-post 2>/dev/null || true
+
+ ${cfg.extraStopCommands}
+ '';
+
+ setupNat = ''
+ # Create subchain where we store rules
+ iptables -w -t nat -N nixos-nat-pre
+ iptables -w -t nat -N nixos-nat-post
+
+ # We can't match on incoming interface in POSTROUTING, so
+ # mark packets coming from the external interfaces.
+ ${concatMapStrings (iface: ''
+ iptables -w -t nat -A nixos-nat-pre \
+ -i '${iface}' -j MARK --set-mark 1
+ '') cfg.internalInterfaces}
+
+ # NAT the marked packets.
+ ${optionalString (cfg.internalInterfaces != []) ''
+ iptables -w -t nat -A nixos-nat-post -m mark --mark 1 \
+ ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
+ ''}
+
+ # NAT packets coming from the internal IPs.
+ ${concatMapStrings (range: ''
+ iptables -w -t nat -A nixos-nat-post \
+ -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
+ '') cfg.internalIPs}
+
+ # NAT from external ports to internal ports.
+ ${concatMapStrings (fwd: ''
+ iptables -w -t nat -A nixos-nat-pre \
+ -i ${toString cfg.externalInterface} -p ${fwd.proto} \
+ --dport ${builtins.toString fwd.sourcePort} \
+ -j DNAT --to-destination ${fwd.destination}
+
+ ${concatMapStrings (loopbackip:
+ let
+ m = builtins.match "([0-9.]+):([0-9-]+)" fwd.destination;
+ destinationIP = if (m == null) then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0;
+ destinationPorts = if (m == null) then throw "bad ip:ports `${fwd.destination}'" else elemAt m 1;
+ in ''
+ # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself
+ iptables -w -t nat -A OUTPUT \
+ -d ${loopbackip} -p ${fwd.proto} \
+ --dport ${builtins.toString fwd.sourcePort} \
+ -j DNAT --to-destination ${fwd.destination}
+
+ # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT
+ iptables -w -t nat -A nixos-nat-pre \
+ -d ${loopbackip} -p ${fwd.proto} \
+ --dport ${builtins.toString fwd.sourcePort} \
+ -j DNAT --to-destination ${fwd.destination}
+
+ iptables -w -t nat -A nixos-nat-post \
+ -d ${destinationIP} -p ${fwd.proto} \
+ --dport ${destinationPorts} \
+ -j SNAT --to-source ${loopbackip}
+ '') fwd.loopbackIPs}
+ '') cfg.forwardPorts}
+
+ ${optionalString (cfg.dmzHost != null) ''
+ iptables -w -t nat -A nixos-nat-pre \
+ -i ${toString cfg.externalInterface} -j DNAT \
+ --to-destination ${cfg.dmzHost}
+ ''}
+
+ ${cfg.extraCommands}
+
+ # Append our chains to the nat tables
+ iptables -w -t nat -A PREROUTING -j nixos-nat-pre
+ iptables -w -t nat -A POSTROUTING -j nixos-nat-post
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ networking.nat.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Whether to enable Network Address Translation (NAT).
+ '';
+ };
+
+ networking.nat.internalInterfaces = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "eth0" ];
+ description =
+ ''
+ The interfaces for which to perform NAT. Packets coming from
+ these interface and destined for the external interface will
+ be rewritten.
+ '';
+ };
+
+ networking.nat.internalIPs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "192.168.1.0/24" ];
+ description =
+ ''
+ The IP address ranges for which to perform NAT. Packets
+ coming from these addresses (on any interface) and destined
+ for the external interface will be rewritten.
+ '';
+ };
+
+ networking.nat.externalInterface = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "eth1";
+ description =
+ ''
+ The name of the external network interface.
+ '';
+ };
+
+ networking.nat.externalIP = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "203.0.113.123";
+ description =
+ ''
+ The public IP address to which packets from the local
+ network are to be rewritten. If this is left empty, the
+ IP address associated with the external interface will be
+ used.
+ '';
+ };
+
+ networking.nat.forwardPorts = mkOption {
+ type = with types; listOf (submodule {
+ options = {
+ sourcePort = mkOption {
+ type = types.either types.int (types.strMatching "[[:digit:]]+:[[:digit:]]+");
+ example = 8080;
+ description = "Source port of the external interface; to specify a port range, use a string with a colon (e.g. \"60000:61000\")";
+ };
+
+ destination = mkOption {
+ type = types.str;
+ example = "10.0.0.1:80";
+ description = "Forward connection to destination ip:port; to specify a port range, use ip:start-end";
+ };
+
+ proto = mkOption {
+ type = types.str;
+ default = "tcp";
+ example = "udp";
+ description = "Protocol of forwarded connection";
+ };
+
+ loopbackIPs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = literalExample ''[ "55.1.2.3" ]'';
+ description = "Public IPs for NAT reflection; for connections to `loopbackip:sourcePort' from the host itself and from other hosts behind NAT";
+ };
+ };
+ });
+ default = [];
+ example = [ { sourcePort = 8080; destination = "10.0.0.1:80"; proto = "tcp"; } ];
+ description =
+ ''
+ List of forwarded ports from the external interface to
+ internal destinations by using DNAT.
+ '';
+ };
+
+ networking.nat.dmzHost = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "10.0.0.1";
+ description =
+ ''
+ The local IP address to which all traffic that does not match any
+ forwarding rule is forwarded.
+ '';
+ };
+
+ networking.nat.extraCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example = "iptables -A INPUT -p icmp -j ACCEPT";
+ description =
+ ''
+ Additional shell commands executed as part of the nat
+ initialisation script.
+ '';
+ };
+
+ networking.nat.extraStopCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example = "iptables -D INPUT -p icmp -j ACCEPT || true";
+ description =
+ ''
+ Additional shell commands executed as part of the nat
+ teardown script.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkMerge [
+ { networking.firewall.extraCommands = mkBefore flushNat; }
+ (mkIf config.networking.nat.enable {
+
+ assertions = [
+ { assertion = (cfg.dmzHost != null) -> (cfg.externalInterface != null);
+ message = "networking.nat.dmzHost requires networking.nat.externalInterface";
+ }
+ { assertion = (cfg.forwardPorts != []) -> (cfg.externalInterface != null);
+ message = "networking.nat.forwardPorts requires networking.nat.externalInterface";
+ }
+ ];
+
+ environment.systemPackages = [ pkgs.iptables ];
+
+ boot = {
+ kernelModules = [ "nf_nat_ftp" ];
+ kernel.sysctl = {
+ "net.ipv4.conf.all.forwarding" = mkOverride 99 true;
+ "net.ipv4.conf.default.forwarding" = mkOverride 99 true;
+ };
+ };
+
+ networking.firewall = mkIf config.networking.firewall.enable {
+ extraCommands = setupNat;
+ extraStopCommands = flushNat;
+ };
+
+ systemd.services = mkIf (!config.networking.firewall.enable) { nat = {
+ description = "Network Address Translation";
+ wantedBy = [ "network.target" ];
+ after = [ "network-pre.target" "systemd-modules-load.service" ];
+ path = [ pkgs.iptables ];
+ unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+
+ script = flushNat + setupNat;
+
+ postStop = flushNat;
+ }; };
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ndppd.nix b/nixpkgs/nixos/modules/services/networking/ndppd.nix
new file mode 100644
index 00000000000..92088623517
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ndppd.nix
@@ -0,0 +1,167 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.ndppd;
+
+ render = s: f: concatStringsSep "\n" (mapAttrsToList f s);
+ prefer = a: b: if a != null then a else b;
+
+ ndppdConf = prefer cfg.configFile (pkgs.writeText "ndppd.conf" ''
+ route-ttl ${toString cfg.routeTTL}
+ ${render cfg.proxies (proxyInterfaceName: proxy: ''
+ proxy ${prefer proxy.interface proxyInterfaceName} {
+ router ${boolToString proxy.router}
+ timeout ${toString proxy.timeout}
+ ttl ${toString proxy.ttl}
+ ${render proxy.rules (ruleNetworkName: rule: ''
+ rule ${prefer rule.network ruleNetworkName} {
+ ${rule.method}${if rule.method == "iface" then " ${rule.interface}" else ""}
+ }'')}
+ }'')}
+ '');
+
+ proxy = types.submodule {
+ options = {
+ interface = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Listen for any Neighbor Solicitation messages on this interface,
+ and respond to them according to a set of rules.
+ Defaults to the name of the attrset.
+ '';
+ default = null;
+ };
+ router = mkOption {
+ type = types.bool;
+ description = ''
+ Turns on or off the router flag for Neighbor Advertisement Messages.
+ '';
+ default = true;
+ };
+ timeout = mkOption {
+ type = types.int;
+ description = ''
+ Controls how long to wait for a Neighbor Advertisment Message before
+ invalidating the entry, in milliseconds.
+ '';
+ default = 500;
+ };
+ ttl = mkOption {
+ type = types.int;
+ description = ''
+ Controls how long a valid or invalid entry remains in the cache, in
+ milliseconds.
+ '';
+ default = 30000;
+ };
+ rules = mkOption {
+ type = types.attrsOf rule;
+ description = ''
+ This is a rule that the target address is to match against. If no netmask
+ is provided, /128 is assumed. You may have several rule sections, and the
+ addresses may or may not overlap.
+ '';
+ default = {};
+ };
+ };
+ };
+
+ rule = types.submodule {
+ options = {
+ network = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ This is the target address is to match against. If no netmask
+ is provided, /128 is assumed. The addresses of serveral rules
+ may or may not overlap.
+ Defaults to the name of the attrset.
+ '';
+ default = null;
+ };
+ method = mkOption {
+ type = types.enum [ "static" "iface" "auto" ];
+ description = ''
+ static: Immediately answer any Neighbor Solicitation Messages
+ (if they match the IP rule).
+ iface: Forward the Neighbor Solicitation Message through the specified
+ interface and only respond if a matching Neighbor Advertisement
+ Message is received.
+ auto: Same as iface, but instead of manually specifying the outgoing
+ interface, check for a matching route in /proc/net/ipv6_route.
+ '';
+ default = "auto";
+ };
+ interface = mkOption {
+ type = types.nullOr types.str;
+ description = "Interface to use when method is iface.";
+ default = null;
+ };
+ };
+ };
+
+in {
+ options.services.ndppd = {
+ enable = mkEnableOption "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces";
+ interface = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Interface which is on link-level with router.
+ (Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; instead)
+ '';
+ default = null;
+ example = "eth0";
+ };
+ network = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Network that we proxy.
+ (Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; instead)
+ '';
+ default = null;
+ example = "1111::/64";
+ };
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ description = "Path to configuration file.";
+ default = null;
+ };
+ routeTTL = mkOption {
+ type = types.int;
+ description = ''
+ This tells 'ndppd' how often to reload the route file /proc/net/ipv6_route,
+ in milliseconds.
+ '';
+ default = 30000;
+ };
+ proxies = mkOption {
+ type = types.attrsOf proxy;
+ description = ''
+ This sets up a listener, that will listen for any Neighbor Solicitation
+ messages, and respond to them according to a set of rules.
+ '';
+ default = {};
+ example = { eth0.rules."1111::/64" = {}; };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ warnings = mkIf (cfg.interface != null && cfg.network != null) [ ''
+ The options services.ndppd.interface and services.ndppd.network will probably be removed soon,
+ please use services.ndppd.proxies.<interface>.rules.<network> instead.
+ '' ];
+
+ services.ndppd.proxies = mkIf (cfg.interface != null && cfg.network != null) {
+ ${cfg.interface}.rules.${cfg.network} = {};
+ };
+
+ systemd.services.ndppd = {
+ description = "NDP Proxy Daemon";
+ documentation = [ "man:ndppd(1)" "man:ndppd.conf(5)" ];
+ after = [ "network-pre.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${pkgs.ndppd}/bin/ndppd -c ${ndppdConf}";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/networkmanager.nix b/nixpkgs/nixos/modules/services/networking/networkmanager.nix
new file mode 100644
index 00000000000..887c89ddf3a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/networkmanager.nix
@@ -0,0 +1,518 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.networking.networkmanager;
+
+ dynamicHostsEnabled =
+ cfg.dynamicHosts.enable && cfg.dynamicHosts.hostsDirs != {};
+
+ delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [];
+
+ # /var/lib/misc is for dnsmasq.leases.
+ stateDirs = "/var/lib/NetworkManager /var/lib/dhclient /var/lib/misc";
+
+ configFile = pkgs.writeText "NetworkManager.conf" ''
+ [main]
+ plugins=keyfile
+ dhcp=${cfg.dhcp}
+ dns=${cfg.dns}
+ # If resolvconf is disabled that means that resolv.conf is managed by some other module.
+ rc-manager=${if config.networking.resolvconf.enable then "resolvconf" else "unmanaged"}
+
+ [keyfile]
+ ${optionalString (cfg.unmanaged != [])
+ ''unmanaged-devices=${lib.concatStringsSep ";" cfg.unmanaged}''}
+
+ [logging]
+ level=${cfg.logLevel}
+ audit=${lib.boolToString config.security.audit.enable}
+
+ [connection]
+ ipv6.ip6-privacy=2
+ ethernet.cloned-mac-address=${cfg.ethernet.macAddress}
+ wifi.cloned-mac-address=${cfg.wifi.macAddress}
+ ${optionalString (cfg.wifi.powersave != null)
+ ''wifi.powersave=${if cfg.wifi.powersave then "3" else "2"}''}
+
+ [device]
+ wifi.scan-rand-mac-address=${if cfg.wifi.scanRandMacAddress then "yes" else "no"}
+
+ ${cfg.extraConfig}
+ '';
+
+ /*
+ [network-manager]
+ Identity=unix-group:networkmanager
+ Action=org.freedesktop.NetworkManager.*
+ ResultAny=yes
+ ResultInactive=no
+ ResultActive=yes
+
+ [modem-manager]
+ Identity=unix-group:networkmanager
+ Action=org.freedesktop.ModemManager*
+ ResultAny=yes
+ ResultInactive=no
+ ResultActive=yes
+ */
+ polkitConf = ''
+ polkit.addRule(function(action, subject) {
+ if (
+ subject.isInGroup("networkmanager")
+ && (action.id.indexOf("org.freedesktop.NetworkManager.") == 0
+ || action.id.indexOf("org.freedesktop.ModemManager") == 0
+ ))
+ { return polkit.Result.YES; }
+ });
+ '';
+
+ ns = xs: pkgs.writeText "nameservers" (
+ concatStrings (map (s: "nameserver ${s}\n") xs)
+ );
+
+ overrideNameserversScript = pkgs.writeScript "02overridedns" ''
+ #!/bin/sh
+ PATH=${with pkgs; makeBinPath [ gnused gnugrep coreutils ]}
+ tmp=$(mktemp)
+ sed '/nameserver /d' /etc/resolv.conf > $tmp
+ grep 'nameserver ' /etc/resolv.conf | \
+ grep -vf ${ns (cfg.appendNameservers ++ cfg.insertNameservers)} > $tmp.ns
+ cat $tmp ${ns cfg.insertNameservers} $tmp.ns ${ns cfg.appendNameservers} > /etc/resolv.conf
+ rm -f $tmp $tmp.ns
+ '';
+
+ dispatcherTypesSubdirMap = {
+ basic = "";
+ pre-up = "pre-up.d/";
+ pre-down = "pre-down.d/";
+ };
+
+ macAddressOpt = mkOption {
+ type = types.either types.str (types.enum ["permanent" "preserve" "random" "stable"]);
+ default = "preserve";
+ example = "00:11:22:33:44:55";
+ description = ''
+ Set the MAC address of the interface.
+ <variablelist>
+ <varlistentry>
+ <term>"XX:XX:XX:XX:XX:XX"</term>
+ <listitem><para>MAC address of the interface</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>"permanent"</literal></term>
+ <listitem><para>Use the permanent MAC address of the device</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>"preserve"</literal></term>
+ <listitem><para>Don’t change the MAC address of the device upon activation</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>"random"</literal></term>
+ <listitem><para>Generate a randomized value upon each connect</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>"stable"</literal></term>
+ <listitem><para>Generate a stable, hashed MAC address</para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+
+in {
+
+ ###### interface
+
+ options = {
+
+ networking.networkmanager = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use NetworkManager to obtain an IP address and other
+ configuration for all network interfaces that are not manually
+ configured. If enabled, a group <literal>networkmanager</literal>
+ will be created. Add all users that should have permission
+ to change network settings to this group.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Configuration appended to the generated NetworkManager.conf.
+ Refer to
+ <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
+ https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
+ </link>
+ or
+ <citerefentry>
+ <refentrytitle>NetworkManager.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ for more information.
+ '';
+ };
+
+ unmanaged = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ List of interfaces that will not be managed by NetworkManager.
+ Interface name can be specified here, but if you need more fidelity,
+ refer to
+ <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec">
+ https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec
+ </link>
+ or the "Device List Format" Appendix of
+ <citerefentry>
+ <refentrytitle>NetworkManager.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>.
+ '';
+ };
+
+ # Ugly hack for using the correct gnome3 packageSet
+ basePackages = mkOption {
+ type = types.attrsOf types.package;
+ default = { inherit (pkgs)
+ networkmanager modemmanager crda
+ networkmanager-openvpn networkmanager-vpnc
+ networkmanager-openconnect networkmanager-fortisslvpn
+ networkmanager-l2tp networkmanager-iodine; }
+ // optionalAttrs (!delegateWireless) { inherit (pkgs) wpa_supplicant; };
+ internal = true;
+ };
+
+ packages = mkOption {
+ type = types.listOf types.path;
+ default = [ ];
+ description = ''
+ Extra packages that provide NetworkManager plugins.
+ '';
+ apply = list: (attrValues cfg.basePackages) ++ list;
+ };
+
+ dhcp = mkOption {
+ type = types.enum [ "dhclient" "dhcpcd" "internal" ];
+ default = "dhclient";
+ description = ''
+ Which program (or internal library) should be used for DHCP.
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ];
+ default = "WARN";
+ description = ''
+ Set the default logging verbosity level.
+ '';
+ };
+
+ appendNameservers = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ A list of name servers that should be appended
+ to the ones configured in NetworkManager or received by DHCP.
+ '';
+ };
+
+ insertNameservers = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ A list of name servers that should be inserted before
+ the ones configured in NetworkManager or received by DHCP.
+ '';
+ };
+
+ ethernet.macAddress = macAddressOpt;
+
+ wifi = {
+ macAddress = macAddressOpt;
+
+ powersave = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Whether to enable Wi-Fi power saving.
+ '';
+ };
+
+ scanRandMacAddress = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable MAC address randomization of a Wi-Fi device
+ during scanning.
+ '';
+ };
+ };
+
+ dns = mkOption {
+ type = types.enum [ "default" "dnsmasq" "unbound" "systemd-resolved" "none" ];
+ default = "default";
+ description = ''
+ Set the DNS (<literal>resolv.conf</literal>) processing mode.
+ </para>
+ <para>
+ A description of these modes can be found in the main section of
+ <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
+ https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
+ </link>
+ or in
+ <citerefentry>
+ <refentrytitle>NetworkManager.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>.
+ '';
+ };
+
+ dispatcherScripts = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ source = mkOption {
+ type = types.path;
+ description = ''
+ Path to the hook script.
+ '';
+ };
+
+ type = mkOption {
+ type = types.enum (attrNames dispatcherTypesSubdirMap);
+ default = "basic";
+ description = ''
+ Dispatcher hook type. Look up the hooks described at
+ <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.html">https://developer.gnome.org/NetworkManager/stable/NetworkManager.html</link>
+ and choose the type depending on the output folder.
+ You should then filter the event type (e.g., "up"/"down") from within your script.
+ '';
+ };
+ };
+ });
+ default = [];
+ example = literalExample ''
+ [ {
+ source = pkgs.writeText "upHook" '''
+
+ if [ "$2" != "up" ]; then
+ logger "exit: event $2 != up"
+ fi
+
+ # coreutils and iproute are in PATH too
+ logger "Device $DEVICE_IFACE coming up"
+ ''';
+ type = "basic";
+ } ]'';
+ description = ''
+ A list of scripts which will be executed in response to network events.
+ '';
+ };
+
+ enableStrongSwan = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the StrongSwan plugin.
+ </para><para>
+ If you enable this option the
+ <literal>networkmanager_strongswan</literal> plugin will be added to
+ the <option>networking.networkmanager.packages</option> option
+ so you don't need to to that yourself.
+ '';
+ };
+
+ dynamicHosts = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enabling this option requires the
+ <option>networking.networkmanager.dns</option> option to be
+ set to <literal>dnsmasq</literal>. If enabled, the directories
+ defined by the
+ <option>networking.networkmanager.dynamicHosts.hostsDirs</option>
+ option will be set up when the service starts. The dnsmasq instance
+ managed by NetworkManager will then watch those directories for
+ hosts files (see the <literal>--hostsdir</literal> option of
+ dnsmasq). This way a non-privileged user can add or override DNS
+ entries on the local system (depending on what hosts directories
+ that are configured)..
+ '';
+ };
+ hostsDirs = mkOption {
+ type = with types; attrsOf (submodule {
+ options = {
+ user = mkOption {
+ type = types.str;
+ default = "root";
+ description = ''
+ The user that will own the hosts directory.
+ '';
+ };
+ group = mkOption {
+ type = types.str;
+ default = "root";
+ description = ''
+ The group that will own the hosts directory.
+ '';
+ };
+ };
+ });
+ default = {};
+ description = ''
+ Defines a set of directories (relative to
+ <literal>/run/NetworkManager/hostdirs</literal>) that dnsmasq will
+ watch for hosts files.
+ '';
+ };
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [];
+ message = ''
+ You can not use networking.networkmanager with networking.wireless.
+ Except if you mark some interfaces as <literal>unmanaged</literal> by NetworkManager.
+ '';
+ }
+ { assertion = !dynamicHostsEnabled || (dynamicHostsEnabled && cfg.dns == "dnsmasq");
+ message = ''
+ To use networking.networkmanager.dynamicHosts you also need to set
+ networking.networkmanager.dns = "dnsmasq"
+ '';
+ }
+ ];
+
+ environment.etc = with cfg.basePackages; [
+ { source = configFile;
+ target = "NetworkManager/NetworkManager.conf";
+ }
+ { source = "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name";
+ target = "NetworkManager/VPN/nm-openvpn-service.name";
+ }
+ { source = "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name";
+ target = "NetworkManager/VPN/nm-vpnc-service.name";
+ }
+ { source = "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name";
+ target = "NetworkManager/VPN/nm-openconnect-service.name";
+ }
+ { source = "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name";
+ target = "NetworkManager/VPN/nm-fortisslvpn-service.name";
+ }
+ { source = "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name";
+ target = "NetworkManager/VPN/nm-l2tp-service.name";
+ }
+ { source = "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name";
+ target = "NetworkManager/VPN/nm-iodine-service.name";
+ }
+ ] ++ optional (cfg.appendNameservers != [] || cfg.insertNameservers != [])
+ { source = overrideNameserversScript;
+ target = "NetworkManager/dispatcher.d/02overridedns";
+ }
+ ++ lib.imap1 (i: s: {
+ inherit (s) source;
+ target = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
+ mode = "0544";
+ }) cfg.dispatcherScripts
+ ++ optional dynamicHostsEnabled
+ { target = "NetworkManager/dnsmasq.d/dyndns.conf";
+ text = concatMapStrings (n: ''
+ hostsdir=/run/NetworkManager/hostsdirs/${n}
+ '') (attrNames cfg.dynamicHosts.hostsDirs);
+ }
+ ++ optional cfg.enableStrongSwan
+ { source = "${pkgs.networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
+ target = "NetworkManager/VPN/nm-strongswan-service.name";
+ };
+
+ environment.systemPackages = cfg.packages;
+
+ users.groups = [{
+ name = "networkmanager";
+ gid = config.ids.gids.networkmanager;
+ }
+ {
+ name = "nm-openvpn";
+ gid = config.ids.gids.nm-openvpn;
+ }];
+ users.users = [{
+ name = "nm-openvpn";
+ uid = config.ids.uids.nm-openvpn;
+ extraGroups = [ "networkmanager" ];
+ }
+ {
+ name = "nm-iodine";
+ isSystemUser = true;
+ group = "networkmanager";
+ }];
+
+ systemd.packages = cfg.packages;
+
+ systemd.services.NetworkManager = {
+ wantedBy = [ "network.target" ];
+ restartTriggers = [ configFile ];
+
+ preStart = ''
+ mkdir -m 700 -p /etc/NetworkManager/system-connections
+ mkdir -m 700 -p /etc/ipsec.d
+ mkdir -m 755 -p ${stateDirs}
+ '';
+ };
+
+ systemd.services.NetworkManager-wait-online = {
+ wantedBy = [ "network-online.target" ];
+ };
+
+ systemd.services.nm-setup-hostsdirs = mkIf dynamicHostsEnabled {
+ wantedBy = [ "NetworkManager.service" ];
+ before = [ "NetworkManager.service" ];
+ partOf = [ "NetworkManager.service" ];
+ script = concatStrings (mapAttrsToList (n: d: ''
+ mkdir -p "/run/NetworkManager/hostsdirs/${n}"
+ chown "${d.user}:${d.group}" "/run/NetworkManager/hostsdirs/${n}"
+ chmod 0775 "/run/NetworkManager/hostsdirs/${n}"
+ '') cfg.dynamicHosts.hostsDirs);
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ };
+
+ systemd.services.NetworkManager-dispatcher = {
+ wantedBy = [ "network.target" ];
+ restartTriggers = [ configFile ];
+
+ # useful binaries for user-specified hooks
+ path = [ pkgs.iproute pkgs.utillinux pkgs.coreutils ];
+ };
+
+ # Turn off NixOS' network management when networking is managed entirely by NetworkManager
+ networking = (mkIf (!delegateWireless) {
+ useDHCP = false;
+ # Use mkDefault to trigger the assertion about the conflict above
+ wireless.enable = mkDefault false;
+ }) // (mkIf cfg.enableStrongSwan {
+ networkmanager.packages = [ pkgs.networkmanager_strongswan ];
+ });
+
+ security.polkit.extraConfig = polkitConf;
+
+ services.dbus.packages = cfg.packages
+ ++ optional cfg.enableStrongSwan pkgs.strongswanNM
+ ++ optional (cfg.dns == "dnsmasq") pkgs.dnsmasq;
+
+ services.udev.packages = cfg.packages;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nftables.nix b/nixpkgs/nixos/modules/services/networking/nftables.nix
new file mode 100644
index 00000000000..ad7c013a544
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nftables.nix
@@ -0,0 +1,136 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+ cfg = config.networking.nftables;
+in
+{
+ ###### interface
+
+ options = {
+ networking.nftables.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Whether to enable nftables. nftables is a Linux-based packet
+ filtering framework intended to replace frameworks like iptables.
+
+ This conflicts with the standard networking firewall, so make sure to
+ disable it before using nftables.
+
+ Note that if you have Docker enabled you will not be able to use
+ nftables without intervention. Docker uses iptables internally to
+ setup NAT for containers. This module disables the ip_tables kernel
+ module, however Docker automatically loads the module. Please see [1]
+ for more information.
+
+ There are other programs that use iptables internally too, such as
+ libvirt.
+
+ [1]: https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273
+ '';
+ };
+ networking.nftables.ruleset = mkOption {
+ type = types.lines;
+ example = ''
+ # Check out https://wiki.nftables.org/ for better documentation.
+ # Table for both IPv4 and IPv6.
+ table inet filter {
+ # Block all incomming connections traffic except SSH and "ping".
+ chain input {
+ type filter hook input priority 0;
+
+ # accept any localhost traffic
+ iifname lo accept
+
+ # accept traffic originated from us
+ ct state {established, related} accept
+
+ # ICMP
+ # routers may also want: mld-listener-query, nd-router-solicit
+ ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
+ ip protocol icmp icmp type { destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept
+
+ # allow "ping"
+ ip6 nexthdr icmp icmpv6 type echo-request accept
+ ip protocol icmp icmp type echo-request accept
+
+ # accept SSH connections (required for a server)
+ tcp dport 22 accept
+
+ # count and drop any other traffic
+ counter drop
+ }
+
+ # Allow all outgoing connections.
+ chain output {
+ type filter hook output priority 0;
+ accept
+ }
+
+ chain forward {
+ type filter hook forward priority 0;
+ accept
+ }
+ }
+ '';
+ description =
+ ''
+ The ruleset to be used with nftables. Should be in a format that
+ can be loaded using "/bin/nft -f". The ruleset is updated atomically.
+ '';
+ };
+ networking.nftables.rulesetFile = mkOption {
+ type = types.path;
+ default = pkgs.writeTextFile {
+ name = "nftables-rules";
+ text = cfg.ruleset;
+ };
+ description =
+ ''
+ The ruleset file to be used with nftables. Should be in a format that
+ can be loaded using "nft -f". The ruleset is updated atomically.
+ '';
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = config.networking.firewall.enable == false;
+ message = "You can not use nftables with services.networking.firewall.";
+ }];
+ boot.blacklistedKernelModules = [ "ip_tables" ];
+ environment.systemPackages = [ pkgs.nftables ];
+ systemd.services.nftables = {
+ description = "nftables firewall";
+ before = [ "network-pre.target" ];
+ wants = [ "network-pre.target" ];
+ wantedBy = [ "multi-user.target" ];
+ reloadIfChanged = true;
+ serviceConfig = let
+ rulesScript = pkgs.writeScript "nftables-rules" ''
+ #! ${pkgs.nftables}/bin/nft -f
+ flush ruleset
+ include "${cfg.rulesetFile}"
+ '';
+ checkScript = pkgs.writeScript "nftables-check" ''
+ #! ${pkgs.runtimeShell} -e
+ if $(${pkgs.kmod}/bin/lsmod | grep -q ip_tables); then
+ echo "Unload ip_tables before using nftables!" 1>&2
+ exit 1
+ else
+ ${rulesScript}
+ fi
+ '';
+ in {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = checkScript;
+ ExecReload = checkScript;
+ ExecStop = "${pkgs.nftables}/bin/nft flush ruleset";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix
new file mode 100644
index 00000000000..6523f4b8b9e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix
@@ -0,0 +1,131 @@
+{ lib, ...}:
+{ options = {
+ proto = lib.mkOption {
+ type = lib.types.enum [ "h2" "http/1.1" ];
+ default = "http/1.1";
+ description = ''
+ This option configures the protocol the backend server expects
+ to use.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+ for more detail.
+ '';
+ };
+
+ tls = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ This option determines whether nghttpx will negotiate its
+ connection with a backend server using TLS or not. The burden
+ is on the backend server to provide the TLS certificate!
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+ for more detail.
+ '';
+ };
+
+ sni = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = ''
+ Override the TLS SNI field value. This value (in nghttpx)
+ defaults to the host value of the backend configuration.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+ for more detail.
+ '';
+ };
+
+ fall = lib.mkOption {
+ type = lib.types.int;
+ default = 0;
+ description = ''
+ If nghttpx cannot connect to the backend N times in a row, the
+ backend is assumed to be offline and is excluded from load
+ balancing. If N is 0 the backend is never excluded from load
+ balancing.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+ for more detail.
+ '';
+ };
+
+ rise = lib.mkOption {
+ type = lib.types.int;
+ default = 0;
+ description = ''
+ If the backend is excluded from load balancing, nghttpx will
+ periodically attempt to make a connection to the backend. If
+ the connection is successful N times in a row the backend is
+ re-included in load balancing. If N is 0 a backend is never
+ reconsidered for load balancing once it falls.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+ for more detail.
+ '';
+ };
+
+ affinity = lib.mkOption {
+ type = lib.types.enum [ "ip" "none" ];
+ default = "none";
+ description = ''
+ If "ip" is given, client IP based session affinity is
+ enabled. If "none" is given, session affinity is disabled.
+
+ Session affinity is enabled (by nghttpx) per-backend
+ pattern. If at least one backend has a non-"none" affinity,
+ then session affinity is enabled for all backend servers
+ sharing the same pattern.
+
+ It is advised to set affinity on all backends explicitly if
+ session affinity is desired. The session affinity may break if
+ one of the backend gets unreachable, or backend settings are
+ reloaded or replaced by API.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+ for more detail.
+ '';
+ };
+
+ dns = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Name resolution of a backends host name is done at start up,
+ or configuration reload. If "dns" is true, name resolution
+ takes place dynamically.
+
+ This is useful if a backends address changes frequently. If
+ "dns" is true, name resolution of a backend's host name at
+ start up, or configuration reload is skipped.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+ for more detail.
+ '';
+ };
+
+ redirect-if-not-tls = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ If true, a backend match requires the frontend connection be
+ TLS encrypted. If it is not, nghttpx responds to the request
+ with a 308 status code and https URI the client should use
+ instead in the Location header.
+
+ The port number in the redirect URI is 443 by default and can
+ be changed using 'services.nghttpx.redirect-https-port'
+ option.
+
+ If at least one backend has "redirect-if-not-tls" set to true,
+ this feature is enabled for all backend servers with the same
+ pattern. It is advised to set "redirect-if-no-tls" parameter
+ to all backends explicitly if this feature is desired.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+ for more detail.
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/backend-submodule.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/backend-submodule.nix
new file mode 100644
index 00000000000..eb559e926e7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/backend-submodule.nix
@@ -0,0 +1,50 @@
+{ lib, ... }:
+{ options = {
+ server = lib.mkOption {
+ type =
+ lib.types.either
+ (lib.types.submodule (import ./server-options.nix))
+ (lib.types.path);
+ example = {
+ host = "127.0.0.1";
+ port = 8888;
+ };
+ default = {
+ host = "127.0.0.1";
+ port = 80;
+ };
+ description = ''
+ Backend server location specified as either a host:port pair
+ or a unix domain docket.
+ '';
+ };
+
+ patterns = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ example = [
+ "*.host.net/v1/"
+ "host.org/v2/mypath"
+ "/somepath"
+ ];
+ default = [];
+ description = ''
+ List of nghttpx backend patterns.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+ for more information on the pattern syntax and nghttpxs behavior.
+ '';
+ };
+
+ params = lib.mkOption {
+ type = lib.types.nullOr (lib.types.submodule (import ./backend-params-submodule.nix));
+ example = {
+ proto = "h2";
+ tls = true;
+ };
+ default = null;
+ description = ''
+ Parameters to configure a backend.
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/default.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/default.nix
new file mode 100644
index 00000000000..d6e1906e388
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/default.nix
@@ -0,0 +1,117 @@
+{config, pkgs, lib, ...}:
+let
+ cfg = config.services.nghttpx;
+
+ # renderHost :: Either ServerOptions Path -> String
+ renderHost = server:
+ if builtins.isString server
+ then "unix://${server}"
+ else "${server.host},${builtins.toString server.port}";
+
+ # Filter out submodule parameters whose value is null or false or is
+ # the key _module.
+ #
+ # filterParams :: ParamsSubmodule -> ParamsSubmodule
+ filterParams = p:
+ lib.filterAttrs
+ (n: v: ("_module" != n) && (null != v) && (false != v))
+ (lib.optionalAttrs (null != p) p);
+
+ # renderBackend :: BackendSubmodule -> String
+ renderBackend = backend:
+ let
+ host = renderHost backend.server;
+ patterns = lib.concatStringsSep ":" backend.patterns;
+
+ # Render a set of backend parameters, this is somewhat
+ # complicated because nghttpx backend patterns can be entirely
+ # omitted and the params may be given as a mixed collection of
+ # 'key=val' pairs or atoms (e.g: 'proto=h2;tls')
+ params =
+ lib.mapAttrsToList
+ (n: v:
+ if builtins.isBool v
+ then n
+ else if builtins.isString v
+ then "${n}=${v}"
+ else "${n}=${builtins.toString v}")
+ (filterParams backend.params);
+
+ # NB: params are delimited by a ";" which is the same delimiter
+ # to separate the host;[pattern];[params] sections of a backend
+ sections =
+ builtins.filter (e: "" != e) ([
+ host
+ patterns
+ ]++params);
+ formattedSections = lib.concatStringsSep ";" sections;
+ in
+ "backend=${formattedSections}";
+
+ # renderFrontend :: FrontendSubmodule -> String
+ renderFrontend = frontend:
+ let
+ host = renderHost frontend.server;
+ params0 =
+ lib.mapAttrsToList
+ (n: v: if builtins.isBool v then n else v)
+ (filterParams frontend.params);
+
+ # NB: nghttpx doesn't accept "tls", you must omit "no-tls" for
+ # the default behavior of turning on TLS.
+ params1 = lib.remove "tls" params0;
+
+ sections = [ host] ++ params1;
+ formattedSections = lib.concatStringsSep ";" sections;
+ in
+ "frontend=${formattedSections}";
+
+ configurationFile = pkgs.writeText "nghttpx.conf" ''
+ ${lib.optionalString (null != cfg.tls) ("private-key-file="+cfg.tls.key)}
+ ${lib.optionalString (null != cfg.tls) ("certificate-file="+cfg.tls.crt)}
+
+ user=nghttpx
+
+ ${lib.concatMapStringsSep "\n" renderFrontend cfg.frontends}
+ ${lib.concatMapStringsSep "\n" renderBackend cfg.backends}
+
+ backlog=${builtins.toString cfg.backlog}
+ backend-address-family=${cfg.backend-address-family}
+
+ workers=${builtins.toString cfg.workers}
+ rlimit-nofile=${builtins.toString cfg.rlimit-nofile}
+
+ ${lib.optionalString cfg.single-thread "single-thread=yes"}
+ ${lib.optionalString cfg.single-process "single-process=yes"}
+
+ ${cfg.extraConfig}
+ '';
+in
+{ imports = [
+ ./nghttpx-options.nix
+ ];
+
+ config = lib.mkIf cfg.enable {
+
+ users.groups.nghttpx = { };
+ users.users.nghttpx = {
+ group = config.users.groups.nghttpx.name;
+ };
+
+
+ systemd.services = {
+ nghttpx = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ script = ''
+ ${pkgs.nghttp2}/bin/nghttpx --conf=${configurationFile}
+ '';
+
+ serviceConfig = {
+ Restart = "on-failure";
+ RestartSec = 60;
+ };
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix
new file mode 100644
index 00000000000..33c8572bd14
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix
@@ -0,0 +1,64 @@
+{ lib, ...}:
+{ options = {
+ tls = lib.mkOption {
+ type = lib.types.enum [ "tls" "no-tls" ];
+ default = "tls";
+ description = ''
+ Enable or disable TLS. If true (enabled) the key and
+ certificate must be configured for nghttpx.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
+ for more detail.
+ '';
+ };
+
+ sni-fwd = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ When performing a match to select a backend server, SNI host
+ name received from the client is used instead of the request
+ host. See --backend option about the pattern match.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
+ for more detail.
+ '';
+ };
+
+ api = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Enable API access for this frontend. This enables you to
+ dynamically modify nghttpx at run-time therefore this feature
+ is disabled by default and should be turned on with care.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
+ for more detail.
+ '';
+ };
+
+ healthmon = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Make this frontend a health monitor endpoint. Any request
+ received on this frontend is responded to with a 200 OK.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
+ for more detail.
+ '';
+ };
+
+ proxyproto = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Accept PROXY protocol version 1 on frontend connection.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
+ for more detail.
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-submodule.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-submodule.nix
new file mode 100644
index 00000000000..887ef450213
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-submodule.nix
@@ -0,0 +1,36 @@
+{ lib, ... }:
+{ options = {
+ server = lib.mkOption {
+ type =
+ lib.types.either
+ (lib.types.submodule (import ./server-options.nix))
+ (lib.types.path);
+ example = {
+ host = "127.0.0.1";
+ port = 8888;
+ };
+ default = {
+ host = "127.0.0.1";
+ port = 80;
+ };
+ description = ''
+ Frontend server interface binding specification as either a
+ host:port pair or a unix domain docket.
+
+ NB: a host of "*" listens on all interfaces and includes IPv6
+ addresses.
+ '';
+ };
+
+ params = lib.mkOption {
+ type = lib.types.nullOr (lib.types.submodule (import ./frontend-params-submodule.nix));
+ example = {
+ tls = "tls";
+ };
+ default = null;
+ description = ''
+ Parameters to configure a backend.
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix
new file mode 100644
index 00000000000..51f1d081b97
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix
@@ -0,0 +1,142 @@
+{ lib, ... }:
+{ options.services.nghttpx = {
+ enable = lib.mkEnableOption "nghttpx";
+
+ frontends = lib.mkOption {
+ type = lib.types.listOf (lib.types.submodule (import ./frontend-submodule.nix));
+ description = ''
+ A list of frontend listener specifications.
+ '';
+ example = [
+ { server = {
+ host = "*";
+ port = 80;
+ };
+
+ params = {
+ tls = "no-tls";
+ };
+ }
+ ];
+ };
+
+ backends = lib.mkOption {
+ type = lib.types.listOf (lib.types.submodule (import ./backend-submodule.nix));
+ description = ''
+ A list of backend specifications.
+ '';
+ example = [
+ { server = {
+ host = "172.16.0.22";
+ port = 8443;
+ };
+ patterns = [ "/" ];
+ params = {
+ proto = "http/1.1";
+ redirect-if-not-tls = true;
+ };
+ }
+ ];
+ };
+
+ tls = lib.mkOption {
+ type = lib.types.nullOr (lib.types.submodule (import ./tls-submodule.nix));
+ default = null;
+ description = ''
+ TLS certificate and key paths. Note that this does not enable
+ TLS for a frontend listener, to do so, a frontend
+ specification must set <literal>params.tls</literal> to true.
+ '';
+ example = {
+ key = "/etc/ssl/keys/server.key";
+ crt = "/etc/ssl/certs/server.crt";
+ };
+ };
+
+ extraConfig = lib.mkOption {
+ type = lib.types.lines;
+ default = "";
+ description = ''
+ Extra configuration options to be appended to the generated
+ configuration file.
+ '';
+ };
+
+ single-process = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Run this program in a single process mode for debugging
+ purpose. Without this option, nghttpx creates at least 2
+ processes: master and worker processes. If this option is
+ used, master and worker are unified into a single
+ process. nghttpx still spawns additional process if neverbleed
+ is used. In the single process mode, the signal handling
+ feature is disabled.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--single-process
+ '';
+ };
+
+ backlog = lib.mkOption {
+ type = lib.types.int;
+ default = 65536;
+ description = ''
+ Listen backlog size.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--backlog
+ '';
+ };
+
+ backend-address-family = lib.mkOption {
+ type = lib.types.enum [
+ "auto"
+ "IPv4"
+ "IPv6"
+ ];
+ default = "auto";
+ description = ''
+ Specify address family of backend connections. If "auto" is
+ given, both IPv4 and IPv6 are considered. If "IPv4" is given,
+ only IPv4 address is considered. If "IPv6" is given, only IPv6
+ address is considered.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--backend-address-family
+ '';
+ };
+
+ workers = lib.mkOption {
+ type = lib.types.int;
+ default = 1;
+ description = ''
+ Set the number of worker threads.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-n
+ '';
+ };
+
+ single-thread = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Run everything in one thread inside the worker process. This
+ feature is provided for better debugging experience, or for
+ the platforms which lack thread support. If threading is
+ disabled, this option is always enabled.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--single-thread
+ '';
+ };
+
+ rlimit-nofile = lib.mkOption {
+ type = lib.types.int;
+ default = 0;
+ description = ''
+ Set maximum number of open files (RLIMIT_NOFILE) to &lt;N&gt;. If 0
+ is given, nghttpx does not set the limit.
+
+ Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--rlimit-nofile
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/server-options.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/server-options.nix
new file mode 100644
index 00000000000..ef23bfd793c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/server-options.nix
@@ -0,0 +1,18 @@
+{ lib, ... }:
+{ options = {
+ host = lib.mkOption {
+ type = lib.types.str;
+ example = "127.0.0.1";
+ description = ''
+ Server host address.
+ '';
+ };
+ port = lib.mkOption {
+ type = lib.types.int;
+ example = 5088;
+ description = ''
+ Server host port.
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/tls-submodule.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/tls-submodule.nix
new file mode 100644
index 00000000000..8f3cdaae2c8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/tls-submodule.nix
@@ -0,0 +1,21 @@
+{lib, ...}:
+{ options = {
+ key = lib.mkOption {
+ type = lib.types.str;
+ example = "/etc/ssl/keys/mykeyfile.key";
+ default = "/etc/ssl/keys/server.key";
+ description = ''
+ Path to the TLS key file.
+ '';
+ };
+
+ crt = lib.mkOption {
+ type = lib.types.str;
+ example = "/etc/ssl/certs/mycert.crt";
+ default = "/etc/ssl/certs/server.crt";
+ description = ''
+ Path to the TLS certificate file.
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ngircd.nix b/nixpkgs/nixos/modules/services/networking/ngircd.nix
new file mode 100644
index 00000000000..4b2fa779592
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ngircd.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.ngircd;
+
+ configFile = pkgs.stdenv.mkDerivation {
+ name = "ngircd.conf";
+
+ text = cfg.config;
+
+ preferLocalBuild = true;
+
+ buildCommand = ''
+ echo -n "$text" > $out
+ ${cfg.package}/sbin/ngircd --config $out --configtest
+ '';
+ };
+in {
+ options = {
+ services.ngircd = {
+ enable = mkEnableOption "the ngircd IRC server";
+
+ config = mkOption {
+ description = "The ngircd configuration (see ngircd.conf(5)).";
+
+ type = types.lines;
+ };
+
+ package = mkOption {
+ description = "The ngircd package.";
+
+ type = types.package;
+
+ default = pkgs.ngircd;
+ defaultText = "pkgs.ngircd";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ #!!! TODO: Use ExecReload (see https://github.com/NixOS/nixpkgs/issues/1988)
+ systemd.services.ngircd = {
+ description = "The ngircd IRC server";
+
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig.ExecStart = "${cfg.package}/sbin/ngircd --config ${configFile} --nodaemon";
+
+ serviceConfig.User = "ngircd";
+ };
+
+ users.users.ngircd = {
+ uid = config.ids.uids.ngircd;
+ description = "ngircd user.";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nix-serve.nix b/nixpkgs/nixos/modules/services/networking/nix-serve.nix
new file mode 100644
index 00000000000..347d87b3f38
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nix-serve.nix
@@ -0,0 +1,81 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nix-serve;
+in
+{
+ options = {
+ services.nix-serve = {
+ enable = mkEnableOption "nix-serve, the standalone Nix binary cache server";
+
+ port = mkOption {
+ type = types.int;
+ default = 5000;
+ description = ''
+ Port number where nix-serve will listen on.
+ '';
+ };
+
+ bindAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ IP address where nix-serve will bind its listening socket.
+ '';
+ };
+
+ secretKeyFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The path to the file used for signing derivation data.
+ Generate with:
+
+ ```
+ nix-store --generate-binary-cache-key key-name secret-key-file public-key-file
+ ```
+
+ Make sure user `nix-serve` has read access to the private key file.
+
+ For more details see <citerefentry><refentrytitle>nix-store</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+ '';
+ };
+
+ extraParams = mkOption {
+ type = types.separatedString " ";
+ default = "";
+ description = ''
+ Extra command line parameters for nix-serve.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.nix-serve = {
+ description = "nix-serve binary cache server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ config.nix.package.out pkgs.bzip2.bin ];
+ environment.NIX_REMOTE = "daemon";
+ environment.NIX_SECRET_KEY_FILE = cfg.secretKeyFile;
+
+ serviceConfig = {
+ Restart = "always";
+ RestartSec = "5s";
+ ExecStart = "${pkgs.nix-serve}/bin/nix-serve " +
+ "--listen ${cfg.bindAddress}:${toString cfg.port} ${cfg.extraParams}";
+ User = "nix-serve";
+ Group = "nogroup";
+ };
+ };
+
+ users.users.nix-serve = {
+ description = "Nix-serve user";
+ uid = config.ids.uids.nix-serve;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nixops-dns.nix b/nixpkgs/nixos/modules/services/networking/nixops-dns.nix
new file mode 100644
index 00000000000..2bb1263b7fa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nixops-dns.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ pkg = pkgs.nixops-dns;
+ cfg = config.services.nixops-dns;
+in
+
+{
+ options = {
+ services.nixops-dns = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the nixops-dns resolution
+ of NixOps virtual machines via dnsmasq and fake domain name.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ description = ''
+ The user the nixops-dns daemon should run as.
+ This should be the user, which is also used for nixops and
+ have the .nixops directory in its home.
+ '';
+ };
+
+ domain = mkOption {
+ type = types.str;
+ description = ''
+ Fake domain name to resolve to NixOps virtual machines.
+
+ For example "ops" will resolve "vm.ops".
+ '';
+ example = "ops";
+ default = "ops";
+ };
+
+ dnsmasq = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable dnsmasq forwarding to nixops-dns. This allows to use
+ nixops-dns for `services.nixops-dns.domain` resolution
+ while forwarding the rest of the queries to original resolvers.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.nixops-dns = {
+ description = "nixops-dns: DNS server for resolving NixOps machines";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ ExecStart="${pkg}/bin/nixops-dns --domain=.${cfg.domain}";
+ };
+ };
+
+ services.dnsmasq = mkIf cfg.dnsmasq {
+ enable = true;
+ resolveLocalQueries = true;
+ servers = [
+ "/${cfg.domain}/127.0.0.1#5300"
+ ];
+ extraConfig = ''
+ bind-interfaces
+ listen-address=127.0.0.1
+ '';
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nntp-proxy.nix b/nixpkgs/nixos/modules/services/networking/nntp-proxy.nix
new file mode 100644
index 00000000000..d24d6f77a49
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nntp-proxy.nix
@@ -0,0 +1,235 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs) nntp-proxy;
+
+ proxyUser = "nntp-proxy";
+
+ cfg = config.services.nntp-proxy;
+
+ configBool = b: if b then "TRUE" else "FALSE";
+
+ confFile = pkgs.writeText "nntp-proxy.conf" ''
+ nntp_server:
+ {
+ # NNTP Server host and port address
+ server = "${cfg.upstreamServer}";
+ port = ${toString cfg.upstreamPort};
+ # NNTP username
+ username = "${cfg.upstreamUser}";
+ # NNTP password in clear text
+ password = "${cfg.upstreamPassword}";
+ # Maximum number of connections allowed by the NNTP
+ max_connections = ${toString cfg.upstreamMaxConnections};
+ };
+
+ proxy:
+ {
+ # Local address and port to bind to
+ bind_ip = "${cfg.listenAddress}";
+ bind_port = ${toString cfg.port};
+
+ # SSL key and cert file
+ ssl_key = "${cfg.sslKey}";
+ ssl_cert = "${cfg.sslCert}";
+
+ # prohibit users from posting
+ prohibit_posting = ${configBool cfg.prohibitPosting};
+ # Verbose levels: ERROR, WARNING, NOTICE, INFO, DEBUG
+ verbose = "${toUpper cfg.verbosity}";
+ # Password is made with: 'mkpasswd -m sha-512 <password>'
+ users = (${concatStringsSep ",\n" (mapAttrsToList (username: userConfig:
+ ''
+ {
+ username = "${username}";
+ password = "${userConfig.passwordHash}";
+ max_connections = ${toString userConfig.maxConnections};
+ }
+ '') cfg.users)});
+ };
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.nntp-proxy = {
+ enable = mkEnableOption "NNTP-Proxy";
+
+ upstreamServer = mkOption {
+ type = types.str;
+ default = "";
+ example = "ssl-eu.astraweb.com";
+ description = ''
+ Upstream server address
+ '';
+ };
+
+ upstreamPort = mkOption {
+ type = types.int;
+ default = 563;
+ description = ''
+ Upstream server port
+ '';
+ };
+
+ upstreamMaxConnections = mkOption {
+ type = types.int;
+ default = 20;
+ description = ''
+ Upstream server maximum allowed concurrent connections
+ '';
+ };
+
+ upstreamUser = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Upstream server username
+ '';
+ };
+
+ upstreamPassword = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Upstream server password
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ example = "[::]";
+ description = ''
+ Proxy listen address (IPv6 literal addresses need to be enclosed in "[" and "]" characters)
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 5555;
+ description = ''
+ Proxy listen port
+ '';
+ };
+
+ sslKey = mkOption {
+ type = types.str;
+ default = "key.pem";
+ example = "/path/to/your/key.file";
+ description = ''
+ Proxy ssl key path
+ '';
+ };
+
+ sslCert = mkOption {
+ type = types.str;
+ default = "cert.pem";
+ example = "/path/to/your/cert.file";
+ description = ''
+ Proxy ssl certificate path
+ '';
+ };
+
+ prohibitPosting = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to prohibit posting to the upstream server
+ '';
+ };
+
+ verbosity = mkOption {
+ type = types.enum [ "error" "warning" "notice" "info" "debug" ];
+ default = "info";
+ example = "error";
+ description = ''
+ Verbosity level
+ '';
+ };
+
+ users = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ username = mkOption {
+ type = types.str;
+ default = null;
+ description = ''
+ Username
+ '';
+ };
+
+ passwordHash = mkOption {
+ type = types.str;
+ default = null;
+ example = "$6$GtzE7FrpE$wwuVgFYU.TZH4Rz.Snjxk9XGua89IeVwPQ/fEUD8eujr40q5Y021yhn0aNcsQ2Ifw.BLclyzvzgegopgKcneL0";
+ description = ''
+ SHA-512 password hash (can be generated by
+ <code>mkpasswd -m sha-512 &lt;password&gt;</code>)
+ '';
+ };
+
+ maxConnections = mkOption {
+ type = types.int;
+ default = 1;
+ description = ''
+ Maximum number of concurrent connections to the proxy for this user
+ '';
+ };
+ };
+ });
+ description = ''
+ NNTP-Proxy user configuration
+ '';
+
+ default = {};
+ example = literalExample ''
+ "user1" = {
+ passwordHash = "$6$1l0t5Kn2Dk$appzivc./9l/kjq57eg5UCsBKlcfyCr0zNWYNerKoPsI1d7eAwiT0SVsOVx/CTgaBNT/u4fi2vN.iGlPfv1ek0";
+ maxConnections = 5;
+ };
+ "anotheruser" = {
+ passwordHash = "$6$6lwEsWB.TmsS$W7m1riUx4QrA8pKJz8hvff0dnF1NwtZXgdjmGqA1Dx2MDPj07tI9GNcb0SWlMglE.2/hBgynDdAd/XqqtRqVQ0";
+ maxConnections = 7;
+ };
+ '';
+ };
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton
+ { name = proxyUser;
+ uid = config.ids.uids.nntp-proxy;
+ description = "NNTP-Proxy daemon user";
+ };
+
+ systemd.services.nntp-proxy = {
+ description = "NNTP proxy";
+ after = [ "network.target" "nss-lookup.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = { User="${proxyUser}"; };
+ serviceConfig.ExecStart = "${nntp-proxy}/bin/nntp-proxy ${confFile}";
+ preStart = ''
+ if [ ! \( -f ${cfg.sslCert} -a -f ${cfg.sslKey} \) ]; then
+ ${pkgs.openssl.bin}/bin/openssl req -subj '/CN=AutoGeneratedCert/O=NixOS Service/C=US' \
+ -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout ${cfg.sslKey} -out ${cfg.sslCert};
+ fi
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nsd.nix b/nixpkgs/nixos/modules/services/networking/nsd.nix
new file mode 100644
index 00000000000..bc0966e6b8e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nsd.nix
@@ -0,0 +1,984 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nsd;
+
+ username = "nsd";
+ stateDir = "/var/lib/nsd";
+ pidFile = stateDir + "/var/nsd.pid";
+
+ # build nsd with the options needed for the given config
+ nsdPkg = pkgs.nsd.override {
+ configFile = "${configFile}/nsd.conf";
+
+ bind8Stats = cfg.bind8Stats;
+ ipv6 = cfg.ipv6;
+ ratelimit = cfg.ratelimit.enable;
+ rootServer = cfg.rootServer;
+ zoneStats = length (collect (x: (x.zoneStats or null) != null) cfg.zones) > 0;
+ };
+
+ mkZoneFileName = name: if name == "." then "root" else name;
+
+ nsdEnv = pkgs.buildEnv {
+ name = "nsd-env";
+
+ paths = [ configFile ]
+ ++ mapAttrsToList (name: zone: writeZoneData name zone.data) zoneConfigs;
+
+ postBuild = ''
+ echo "checking zone files"
+ cd $out/zones
+
+ for zoneFile in *; do
+ echo "|- checking zone '$out/zones/$zoneFile'"
+ ${nsdPkg}/sbin/nsd-checkzone "$zoneFile" "$zoneFile" || {
+ if grep -q \\\\\\$ "$zoneFile"; then
+ echo zone "$zoneFile" contains escaped dollar signes \\\$
+ echo Escaping them is not needed any more. Please make shure \
+ to unescape them where they prefix a variable name
+ fi
+
+ exit 1
+ }
+ done
+
+ echo "checking configuration file"
+ ${nsdPkg}/sbin/nsd-checkconf $out/nsd.conf
+ '';
+ };
+
+ writeZoneData = name: text: pkgs.writeTextFile {
+ name = "nsd-zone-${mkZoneFileName name}";
+ inherit text;
+ destination = "/zones/${mkZoneFileName name}";
+ };
+
+
+ # options are ordered alphanumerically by the nixos option name
+ configFile = pkgs.writeTextDir "nsd.conf" ''
+ server:
+ chroot: "${stateDir}"
+ username: ${username}
+
+ # The directory for zonefile: files. The daemon chdirs here.
+ zonesdir: "${stateDir}"
+
+ # the list of dynamically added zones.
+ database: "${stateDir}/var/nsd.db"
+ pidfile: "${pidFile}"
+ xfrdfile: "${stateDir}/var/xfrd.state"
+ xfrdir: "${stateDir}/tmp"
+ zonelistfile: "${stateDir}/var/zone.list"
+
+ # interfaces
+ ${forEach " ip-address: " cfg.interfaces}
+
+ ip-freebind: ${yesOrNo cfg.ipFreebind}
+ hide-version: ${yesOrNo cfg.hideVersion}
+ identity: "${cfg.identity}"
+ ip-transparent: ${yesOrNo cfg.ipTransparent}
+ do-ip4: ${yesOrNo cfg.ipv4}
+ ipv4-edns-size: ${toString cfg.ipv4EDNSSize}
+ do-ip6: ${yesOrNo cfg.ipv6}
+ ipv6-edns-size: ${toString cfg.ipv6EDNSSize}
+ log-time-ascii: ${yesOrNo cfg.logTimeAscii}
+ ${maybeString "nsid: " cfg.nsid}
+ port: ${toString cfg.port}
+ reuseport: ${yesOrNo cfg.reuseport}
+ round-robin: ${yesOrNo cfg.roundRobin}
+ server-count: ${toString cfg.serverCount}
+ ${maybeToString "statistics: " cfg.statistics}
+ tcp-count: ${toString cfg.tcpCount}
+ tcp-query-count: ${toString cfg.tcpQueryCount}
+ tcp-timeout: ${toString cfg.tcpTimeout}
+ verbosity: ${toString cfg.verbosity}
+ ${maybeString "version: " cfg.version}
+ xfrd-reload-timeout: ${toString cfg.xfrdReloadTimeout}
+ zonefiles-check: ${yesOrNo cfg.zonefilesCheck}
+
+ ${maybeString "rrl-ipv4-prefix-length: " cfg.ratelimit.ipv4PrefixLength}
+ ${maybeString "rrl-ipv6-prefix-length: " cfg.ratelimit.ipv6PrefixLength}
+ rrl-ratelimit: ${toString cfg.ratelimit.ratelimit}
+ ${maybeString "rrl-slip: " cfg.ratelimit.slip}
+ rrl-size: ${toString cfg.ratelimit.size}
+ rrl-whitelist-ratelimit: ${toString cfg.ratelimit.whitelistRatelimit}
+
+ ${keyConfigFile}
+
+ remote-control:
+ control-enable: ${yesOrNo cfg.remoteControl.enable}
+ control-key-file: "${cfg.remoteControl.controlKeyFile}"
+ control-cert-file: "${cfg.remoteControl.controlCertFile}"
+ ${forEach " control-interface: " cfg.remoteControl.interfaces}
+ control-port: ${toString cfg.remoteControl.port}
+ server-key-file: "${cfg.remoteControl.serverKeyFile}"
+ server-cert-file: "${cfg.remoteControl.serverCertFile}"
+
+ ${concatStrings (mapAttrsToList zoneConfigFile zoneConfigs)}
+
+ ${cfg.extraConfig}
+ '';
+
+ yesOrNo = b: if b then "yes" else "no";
+ maybeString = prefix: x: if x == null then "" else ''${prefix} "${x}"'';
+ maybeToString = prefix: x: if x == null then "" else ''${prefix} ${toString x}'';
+ forEach = pre: l: concatMapStrings (x: pre + x + "\n") l;
+
+
+ keyConfigFile = concatStrings (mapAttrsToList (keyName: keyOptions: ''
+ key:
+ name: "${keyName}"
+ algorithm: "${keyOptions.algorithm}"
+ include: "${stateDir}/private/${keyName}"
+ '') cfg.keys);
+
+ copyKeys = concatStrings (mapAttrsToList (keyName: keyOptions: ''
+ secret=$(cat "${keyOptions.keyFile}")
+ dest="${stateDir}/private/${keyName}"
+ echo " secret: \"$secret\"" > "$dest"
+ chown ${username}:${username} "$dest"
+ chmod 0400 "$dest"
+ '') cfg.keys);
+
+
+ # options are ordered alphanumerically by the nixos option name
+ zoneConfigFile = name: zone: ''
+ zone:
+ name: "${name}"
+ zonefile: "${stateDir}/zones/${mkZoneFileName name}"
+ ${maybeString "outgoing-interface: " zone.outgoingInterface}
+ ${forEach " rrl-whitelist: " zone.rrlWhitelist}
+ ${maybeString "zonestats: " zone.zoneStats}
+
+ ${maybeToString "max-refresh-time: " zone.maxRefreshSecs}
+ ${maybeToString "min-refresh-time: " zone.minRefreshSecs}
+ ${maybeToString "max-retry-time: " zone.maxRetrySecs}
+ ${maybeToString "min-retry-time: " zone.minRetrySecs}
+
+ allow-axfr-fallback: ${yesOrNo zone.allowAXFRFallback}
+ ${forEach " allow-notify: " zone.allowNotify}
+ ${forEach " request-xfr: " zone.requestXFR}
+
+ ${forEach " notify: " zone.notify}
+ notify-retry: ${toString zone.notifyRetry}
+ ${forEach " provide-xfr: " zone.provideXFR}
+ '';
+
+ zoneConfigs = zoneConfigs' {} "" { children = cfg.zones; };
+
+ zoneConfigs' = parent: name: zone:
+ if !(zone ? children) || zone.children == null || zone.children == { }
+ # leaf -> actual zone
+ then listToAttrs [ (nameValuePair name (parent // zone)) ]
+
+ # fork -> pattern
+ else zipAttrsWith (name: head) (
+ mapAttrsToList (name: child: zoneConfigs' (parent // zone // { children = {}; }) name child)
+ zone.children
+ );
+
+ # fighting infinite recursion
+ zoneOptions = zoneOptionsRaw // childConfig zoneOptions1 true;
+ zoneOptions1 = zoneOptionsRaw // childConfig zoneOptions2 false;
+ zoneOptions2 = zoneOptionsRaw // childConfig zoneOptions3 false;
+ zoneOptions3 = zoneOptionsRaw // childConfig zoneOptions4 false;
+ zoneOptions4 = zoneOptionsRaw // childConfig zoneOptions5 false;
+ zoneOptions5 = zoneOptionsRaw // childConfig zoneOptions6 false;
+ zoneOptions6 = zoneOptionsRaw // childConfig null false;
+
+ childConfig = x: v: { options.children = { type = types.attrsOf x; visible = v; }; };
+
+ # options are ordered alphanumerically
+ zoneOptionsRaw = types.submodule {
+ options = {
+
+ allowAXFRFallback = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If NSD as secondary server should be allowed to AXFR if the primary
+ server does not allow IXFR.
+ '';
+ };
+
+ allowNotify = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name"
+ "10.0.3.4&255.255.0.0 BLOCKED"
+ ];
+ description = ''
+ Listed primary servers are allowed to notify this secondary server.
+ <screen><![CDATA[
+ Format: <ip> <key-name | NOKEY | BLOCKED>
+
+ <ip> either a plain IPv4/IPv6 address or range. Valid patters for ranges:
+ * 10.0.0.0/24 # via subnet size
+ * 10.0.0.0&255.255.255.0 # via subnet mask
+ * 10.0.0.1-10.0.0.254 # via range
+
+ A optional port number could be added with a '@':
+ * 2001:1234::1@1234
+
+ <key-name | NOKEY | BLOCKED>
+ * <key-name> will use the specified TSIG key
+ * NOKEY no TSIG signature is required
+ * BLOCKED notifies from non-listed or blocked IPs will be ignored
+ * ]]></screen>
+ '';
+ };
+
+ children = mkOption {
+ default = {};
+ description = ''
+ Children zones inherit all options of their parents. Attributes
+ defined in a child will overwrite the ones of its parent. Only
+ leaf zones will be actually served. This way it's possible to
+ define maybe zones which share most attributes without
+ duplicating everything. This mechanism replaces nsd's patterns
+ in a save and functional way.
+ '';
+ };
+
+ data = mkOption {
+ type = types.str;
+ default = "";
+ example = "";
+ description = ''
+ The actual zone data. This is the content of your zone file.
+ Use imports or pkgs.lib.readFile if you don't want this data in your config file.
+ '';
+ };
+
+ dnssec = mkEnableOption "DNSSEC";
+
+ dnssecPolicy = {
+ algorithm = mkOption {
+ type = types.str;
+ default = "RSASHA256";
+ description = "Which algorithm to use for DNSSEC";
+ };
+ keyttl = mkOption {
+ type = types.str;
+ default = "1h";
+ description = "TTL for dnssec records";
+ };
+ coverage = mkOption {
+ type = types.str;
+ default = "1y";
+ description = ''
+ The length of time to ensure that keys will be correct; no action will be taken to create new keys to be activated after this time.
+ '';
+ };
+ zsk = mkOption {
+ type = keyPolicy;
+ default = { keySize = 2048;
+ prePublish = "1w";
+ postPublish = "1w";
+ rollPeriod = "1mo";
+ };
+ description = "Key policy for zone signing keys";
+ };
+ ksk = mkOption {
+ type = keyPolicy;
+ default = { keySize = 4096;
+ prePublish = "1mo";
+ postPublish = "1mo";
+ rollPeriod = "0";
+ };
+ description = "Key policy for key signing keys";
+ };
+ };
+
+ maxRefreshSecs = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Limit refresh time for secondary zones. This is the timer which
+ checks to see if the zone has to be refetched when it expires.
+ Normally the value from the SOA record is used, but this option
+ restricts that value.
+ '';
+ };
+
+ minRefreshSecs = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Limit refresh time for secondary zones.
+ '';
+ };
+
+ maxRetrySecs = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Limit retry time for secondary zones. This is the timeout after
+ a failed fetch attempt for the zone. Normally the value from
+ the SOA record is used, but this option restricts that value.
+ '';
+ };
+
+ minRetrySecs = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Limit retry time for secondary zones.
+ '';
+ };
+
+
+ notify = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ];
+ description = ''
+ This primary server will notify all given secondary servers about
+ zone changes.
+ <screen><![CDATA[
+ Format: <ip> <key-name | NOKEY>
+
+ <ip> a plain IPv4/IPv6 address with on optional port number (ip@port)
+
+ <key-name | NOKEY>
+ * <key-name> sign notifies with the specified key
+ * NOKEY don't sign notifies
+ ]]></screen>
+ '';
+ };
+
+ notifyRetry = mkOption {
+ type = types.int;
+ default = 5;
+ description = ''
+ Specifies the number of retries for failed notifies. Set this along with notify.
+ '';
+ };
+
+ outgoingInterface = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "2000::1@1234";
+ description = ''
+ This address will be used for zone-transfere requests if configured
+ as a secondary server or notifications in case of a primary server.
+ Supply either a plain IPv4 or IPv6 address with an optional port
+ number (ip@port).
+ '';
+ };
+
+ provideXFR = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ];
+ description = ''
+ Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED
+ address range 192.0.2.0/24, 1.2.3.4&amp;255.255.0.0, 3.0.2.20-3.0.2.40
+ '';
+ };
+
+ requestXFR = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [];
+ description = ''
+ Format: <code>[AXFR|UDP] &lt;ip-address&gt; &lt;key-name | NOKEY&gt;</code>
+ '';
+ };
+
+ rrlWhitelist = mkOption {
+ type = with types; listOf (enum [ "nxdomain" "error" "referral" "any" "rrsig" "wildcard" "nodata" "dnskey" "positive" "all" ]);
+ default = [];
+ description = ''
+ Whitelists the given rrl-types.
+ '';
+ };
+
+ zoneStats = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "%s";
+ description = ''
+ When set to something distinct to null NSD is able to collect
+ statistics per zone. All statistics of this zone(s) will be added
+ to the group specified by this given name. Use "%s" to use the zones
+ name as the group. The groups are output from nsd-control stats
+ and stats_noreset.
+ '';
+ };
+ };
+ };
+
+ keyPolicy = types.submodule {
+ options = {
+ keySize = mkOption {
+ type = types.int;
+ description = "Key size in bits";
+ };
+ prePublish = mkOption {
+ type = types.str;
+ description = "How long in advance to publish new keys";
+ };
+ postPublish = mkOption {
+ type = types.str;
+ description = "How long after deactivation to keep a key in the zone";
+ };
+ rollPeriod = mkOption {
+ type = types.str;
+ description = "How frequently to change keys";
+ };
+ };
+ };
+
+ dnssecZones = (filterAttrs (n: v: if v ? dnssec then v.dnssec else false) zoneConfigs);
+
+ dnssec = dnssecZones != {};
+
+ dnssecTools = pkgs.bind.override { enablePython = true; };
+
+ signZones = optionalString dnssec ''
+ mkdir -p ${stateDir}/dnssec
+ chown ${username}:${username} ${stateDir}/dnssec
+ chmod 0600 ${stateDir}/dnssec
+
+ ${concatStrings (mapAttrsToList signZone dnssecZones)}
+ '';
+ signZone = name: zone: ''
+ ${dnssecTools}/bin/dnssec-keymgr -g ${dnssecTools}/bin/dnssec-keygen -s ${dnssecTools}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${policyFile name zone.dnssecPolicy} ${name}
+ ${dnssecTools}/bin/dnssec-signzone -S -K ${stateDir}/dnssec -o ${name} -O full -N date ${stateDir}/zones/${name}
+ ${nsdPkg}/sbin/nsd-checkzone ${name} ${stateDir}/zones/${name}.signed && mv -v ${stateDir}/zones/${name}.signed ${stateDir}/zones/${name}
+ '';
+ policyFile = name: policy: pkgs.writeText "${name}.policy" ''
+ zone ${name} {
+ algorithm ${policy.algorithm};
+ key-size zsk ${toString policy.zsk.keySize};
+ key-size ksk ${toString policy.ksk.keySize};
+ keyttl ${policy.keyttl};
+ pre-publish zsk ${policy.zsk.prePublish};
+ pre-publish ksk ${policy.ksk.prePublish};
+ post-publish zsk ${policy.zsk.postPublish};
+ post-publish ksk ${policy.ksk.postPublish};
+ roll-period zsk ${policy.zsk.rollPeriod};
+ roll-period ksk ${policy.ksk.rollPeriod};
+ coverage ${policy.coverage};
+ };
+ '';
+in
+{
+ # options are ordered alphanumerically
+ options.services.nsd = {
+
+ enable = mkEnableOption "NSD authoritative DNS server";
+
+ bind8Stats = mkEnableOption "BIND8 like statistics";
+
+ dnssecInterval = mkOption {
+ type = types.str;
+ default = "1h";
+ description = ''
+ How often to check whether dnssec key rollover is required
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Extra nsd config.
+ '';
+ };
+
+ hideVersion = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether NSD should answer VERSION.BIND and VERSION.SERVER CHAOS class queries.
+ '';
+ };
+
+ identity = mkOption {
+ type = types.str;
+ default = "unidentified server";
+ description = ''
+ Identify the server (CH TXT ID.SERVER entry).
+ '';
+ };
+
+ interfaces = mkOption {
+ type = types.listOf types.str;
+ default = [ "127.0.0.0" "::1" ];
+ description = ''
+ What addresses the server should listen to.
+ '';
+ };
+
+ ipFreebind = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to bind to nonlocal addresses and interfaces that are down.
+ Similar to ip-transparent.
+ '';
+ };
+
+ ipTransparent = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow binding to non local addresses.
+ '';
+ };
+
+ ipv4 = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to listen on IPv4 connections.
+ '';
+ };
+
+ ipv4EDNSSize = mkOption {
+ type = types.int;
+ default = 4096;
+ description = ''
+ Preferred EDNS buffer size for IPv4.
+ '';
+ };
+
+ ipv6 = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to listen on IPv6 connections.
+ '';
+ };
+
+ ipv6EDNSSize = mkOption {
+ type = types.int;
+ default = 4096;
+ description = ''
+ Preferred EDNS buffer size for IPv6.
+ '';
+ };
+
+ logTimeAscii = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Log time in ascii, if false then in unix epoch seconds.
+ '';
+ };
+
+ nsid = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ NSID identity (hex string, or "ascii_somestring").
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 53;
+ description = ''
+ Port the service should bind do.
+ '';
+ };
+
+ reuseport = mkOption {
+ type = types.bool;
+ default = pkgs.stdenv.isLinux;
+ description = ''
+ Whether to enable SO_REUSEPORT on all used sockets. This lets multiple
+ processes bind to the same port. This speeds up operation especially
+ if the server count is greater than one and makes fast restarts less
+ prone to fail
+ '';
+ };
+
+ rootServer = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether this server will be a root server (a DNS root server, you
+ usually don't want that).
+ '';
+ };
+
+ roundRobin = mkEnableOption "round robin rotation of records";
+
+ serverCount = mkOption {
+ type = types.int;
+ default = 1;
+ description = ''
+ Number of NSD servers to fork. Put the number of CPUs to use here.
+ '';
+ };
+
+ statistics = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Statistics are produced every number of seconds. Prints to log.
+ If null no statistics are logged.
+ '';
+ };
+
+ tcpCount = mkOption {
+ type = types.int;
+ default = 100;
+ description = ''
+ Maximum number of concurrent TCP connections per server.
+ '';
+ };
+
+ tcpQueryCount = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Maximum number of queries served on a single TCP connection.
+ 0 means no maximum.
+ '';
+ };
+
+ tcpTimeout = mkOption {
+ type = types.int;
+ default = 120;
+ description = ''
+ TCP timeout in seconds.
+ '';
+ };
+
+ verbosity = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Verbosity level.
+ '';
+ };
+
+ version = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The version string replied for CH TXT version.server and version.bind
+ queries. Will use the compiled package version on null.
+ See hideVersion for enabling/disabling this responses.
+ '';
+ };
+
+ xfrdReloadTimeout = mkOption {
+ type = types.int;
+ default = 1;
+ description = ''
+ Number of seconds between reloads triggered by xfrd.
+ '';
+ };
+
+ zonefilesCheck = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to check mtime of all zone files on start and sighup.
+ '';
+ };
+
+
+ keys = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+
+ algorithm = mkOption {
+ type = types.str;
+ default = "hmac-sha256";
+ description = ''
+ Authentication algorithm for this key.
+ '';
+ };
+
+ keyFile = mkOption {
+ type = types.path;
+ description = ''
+ Path to the file which contains the actual base64 encoded
+ key. The key will be copied into "${stateDir}/private" before
+ NSD starts. The copied file is only accessibly by the NSD
+ user.
+ '';
+ };
+
+ };
+ });
+ default = {};
+ example = literalExample ''
+ { "tsig.example.org" = {
+ algorithm = "hmac-md5";
+ keyFile = "/path/to/my/key";
+ };
+ }
+ '';
+ description = ''
+ Define your TSIG keys here.
+ '';
+ };
+
+
+ ratelimit = {
+
+ enable = mkEnableOption "ratelimit capabilities";
+
+ ipv4PrefixLength = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ IPv4 prefix length. Addresses are grouped by netblock.
+ '';
+ };
+
+ ipv6PrefixLength = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ IPv6 prefix length. Addresses are grouped by netblock.
+ '';
+ };
+
+ ratelimit = mkOption {
+ type = types.int;
+ default = 200;
+ description = ''
+ Max qps allowed from any query source.
+ 0 means unlimited. With an verbosity of 2 blocked and
+ unblocked subnets will be logged.
+ '';
+ };
+
+ slip = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Number of packets that get discarded before replying a SLIP response.
+ 0 disables SLIP responses. 1 will make every response a SLIP response.
+ '';
+ };
+
+ size = mkOption {
+ type = types.int;
+ default = 1000000;
+ description = ''
+ Size of the hashtable. More buckets use more memory but lower
+ the chance of hash hash collisions.
+ '';
+ };
+
+ whitelistRatelimit = mkOption {
+ type = types.int;
+ default = 2000;
+ description = ''
+ Max qps allowed from whitelisted sources.
+ 0 means unlimited. Set the rrl-whitelist option for specific
+ queries to apply this limit instead of the default to them.
+ '';
+ };
+
+ };
+
+
+ remoteControl = {
+
+ enable = mkEnableOption "remote control via nsd-control";
+
+ controlCertFile = mkOption {
+ type = types.path;
+ default = "/etc/nsd/nsd_control.pem";
+ description = ''
+ Path to the client certificate signed with the server certificate.
+ This file is used by nsd-control and generated by nsd-control-setup.
+ '';
+ };
+
+ controlKeyFile = mkOption {
+ type = types.path;
+ default = "/etc/nsd/nsd_control.key";
+ description = ''
+ Path to the client private key, which is used by nsd-control
+ but not by the server. This file is generated by nsd-control-setup.
+ '';
+ };
+
+ interfaces = mkOption {
+ type = types.listOf types.str;
+ default = [ "127.0.0.1" "::1" ];
+ description = ''
+ Which interfaces NSD should bind to for remote control.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8952;
+ description = ''
+ Port number for remote control operations (uses TLS over TCP).
+ '';
+ };
+
+ serverCertFile = mkOption {
+ type = types.path;
+ default = "/etc/nsd/nsd_server.pem";
+ description = ''
+ Path to the server self signed certificate, which is used by the server
+ but and by nsd-control. This file is generated by nsd-control-setup.
+ '';
+ };
+
+ serverKeyFile = mkOption {
+ type = types.path;
+ default = "/etc/nsd/nsd_server.key";
+ description = ''
+ Path to the server private key, which is used by the server
+ but not by nsd-control. This file is generated by nsd-control-setup.
+ '';
+ };
+
+ };
+
+ zones = mkOption {
+ type = types.attrsOf zoneOptions;
+ default = {};
+ example = literalExample ''
+ { "serverGroup1" = {
+ provideXFR = [ "10.1.2.3 NOKEY" ];
+ children = {
+ "example.com." = {
+ data = '''
+ $ORIGIN example.com.
+ $TTL 86400
+ @ IN SOA a.ns.example.com. admin.example.com. (
+ ...
+ ''';
+ };
+ "example.org." = {
+ data = '''
+ $ORIGIN example.org.
+ $TTL 86400
+ @ IN SOA a.ns.example.com. admin.example.com. (
+ ...
+ ''';
+ };
+ };
+ };
+
+ "example.net." = {
+ provideXFR = [ "10.3.2.1 NOKEY" ];
+ data = '''
+ ...
+ ''';
+ };
+ }
+ '';
+ description = ''
+ Define your zones here. Zones can cascade other zones and therefore
+ inherit settings from parent zones. Look at the definition of
+ children to learn about inheritance and child zones.
+ The given example will define 3 zones (example.(com|org|net).). Both
+ example.com. and example.org. inherit their configuration from
+ serverGroup1.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = singleton {
+ assertion = zoneConfigs ? "." -> cfg.rootServer;
+ message = "You have a root zone configured. If this is really what you "
+ + "want, please enable 'services.nsd.rootServer'.";
+ };
+
+ environment.systemPackages = [ nsdPkg ];
+
+ users.groups = singleton {
+ name = username;
+ gid = config.ids.gids.nsd;
+ };
+
+ users.users = singleton {
+ name = username;
+ description = "NSD service user";
+ home = stateDir;
+ createHome = true;
+ uid = config.ids.uids.nsd;
+ group = username;
+ };
+
+ systemd.services.nsd = {
+ description = "NSD authoritative only domain name service";
+
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "${nsdPkg}/sbin/nsd -d -c ${nsdEnv}/nsd.conf";
+ StandardError = "null";
+ PIDFile = pidFile;
+ Restart = "always";
+ RestartSec = "4s";
+ StartLimitBurst = 4;
+ StartLimitInterval = "5min";
+ };
+
+ preStart = ''
+ rm -Rf "${stateDir}/private/"
+ rm -Rf "${stateDir}/tmp/"
+
+ mkdir -m 0700 -p "${stateDir}/private"
+ mkdir -m 0700 -p "${stateDir}/tmp"
+ mkdir -m 0700 -p "${stateDir}/var"
+
+ cat > "${stateDir}/don't touch anything in here" << EOF
+ Everything in this directory except NSD's state in var and dnssec
+ is automatically generated and will be purged and redeployed by
+ the nsd.service pre-start script.
+ EOF
+
+ chown ${username}:${username} -R "${stateDir}/private"
+ chown ${username}:${username} -R "${stateDir}/tmp"
+ chown ${username}:${username} -R "${stateDir}/var"
+
+ rm -rf "${stateDir}/zones"
+ cp -rL "${nsdEnv}/zones" "${stateDir}/zones"
+
+ ${copyKeys}
+ '';
+ };
+
+ systemd.timers.nsd-dnssec = mkIf dnssec {
+ description = "Automatic DNSSEC key rollover";
+
+ wantedBy = [ "nsd.service" ];
+
+ timerConfig = {
+ OnActiveSec = cfg.dnssecInterval;
+ OnUnitActiveSec = cfg.dnssecInterval;
+ };
+ };
+
+ systemd.services.nsd-dnssec = mkIf dnssec {
+ description = "DNSSEC key rollover";
+
+ wantedBy = [ "nsd.service" ];
+ before = [ "nsd.service" ];
+
+ script = signZones;
+
+ postStop = ''
+ ${pkgs.systemd}/bin/systemctl kill -s SIGHUP nsd.service
+ '';
+ };
+
+ };
+
+ meta.maintainers = with lib.maintainers; [ hrdinka ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ntopng.nix b/nixpkgs/nixos/modules/services/networking/ntopng.nix
new file mode 100644
index 00000000000..c1525711713
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ntopng.nix
@@ -0,0 +1,116 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.ntopng;
+ redisCfg = config.services.redis;
+
+ configFile = if cfg.configText != "" then
+ pkgs.writeText "ntopng.conf" ''
+ ${cfg.configText}
+ ''
+ else
+ pkgs.writeText "ntopng.conf" ''
+ ${concatStringsSep " " (map (e: "--interface=" + e) cfg.interfaces)}
+ --http-port=${toString cfg.http-port}
+ --redis=localhost:${toString redisCfg.port}
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+
+ options = {
+
+ services.ntopng = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable ntopng, a high-speed web-based traffic analysis and flow
+ collection tool.
+
+ With the default configuration, ntopng monitors all network
+ interfaces and displays its findings at http://localhost:${toString
+ cfg.http-port}. Default username and password is admin/admin.
+
+ See the ntopng(8) manual page and http://www.ntop.org/products/ntop/
+ for more info.
+
+ Note that enabling ntopng will also enable redis (key-value
+ database server) for persistent data storage.
+ '';
+ };
+
+ interfaces = mkOption {
+ default = [ "any" ];
+ example = [ "eth0" "wlan0" ];
+ type = types.listOf types.str;
+ description = ''
+ List of interfaces to monitor. Use "any" to monitor all interfaces.
+ '';
+ };
+
+ http-port = mkOption {
+ default = 3000;
+ type = types.int;
+ description = ''
+ Sets the HTTP port of the embedded web server.
+ '';
+ };
+
+ configText = mkOption {
+ default = "";
+ example = ''
+ --interface=any
+ --http-port=3000
+ --disable-login
+ '';
+ type = types.lines;
+ description = ''
+ Overridable configuration file contents to use for ntopng. By
+ default, use the contents automatically generated by NixOS.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Configuration lines that will be appended to the generated ntopng
+ configuration file. Note that this mechanism does not work when the
+ manual <option>configText</option> option is used.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ # ntopng uses redis for data storage
+ services.redis.enable = true;
+
+ # nice to have manual page and ntopng command in PATH
+ environment.systemPackages = [ pkgs.ntopng ];
+
+ systemd.services.ntopng = {
+ description = "Ntopng Network Monitor";
+ requires = [ "redis.service" ];
+ after = [ "network.target" "redis.service" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = "mkdir -p /var/lib/ntopng/";
+ serviceConfig.ExecStart = "${pkgs.ntopng}/bin/ntopng ${configFile}";
+ unitConfig.Documentation = "man:ntopng(8)";
+ };
+
+ # ntopng drops priveleges to user "nobody" and that user is already defined
+ # in users-groups.nix.
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix b/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix
new file mode 100644
index 00000000000..c74476c7a15
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix
@@ -0,0 +1,130 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.chrony;
+
+ stateDir = "/var/lib/chrony";
+ keyFile = "${stateDir}/chrony.keys";
+
+ configFile = pkgs.writeText "chrony.conf" ''
+ ${concatMapStringsSep "\n" (server: "server " + server + " iburst") cfg.servers}
+
+ ${optionalString
+ (cfg.initstepslew.enabled && (cfg.servers != []))
+ "initstepslew ${toString cfg.initstepslew.threshold} ${concatStringsSep " " cfg.servers}"
+ }
+
+ driftfile ${stateDir}/chrony.drift
+ keyfile ${keyFile}
+
+ ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"}
+
+ ${cfg.extraConfig}
+ '';
+
+ chronyFlags = "-n -m -u chrony -f ${configFile} ${toString cfg.extraFlags}";
+in
+{
+ options = {
+ services.chrony = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to synchronise your machine's time using chrony.
+ Make sure you disable NTP if you enable this service.
+ '';
+ };
+
+ servers = mkOption {
+ default = config.networking.timeServers;
+ description = ''
+ The set of NTP servers from which to synchronise.
+ '';
+ };
+
+ initstepslew = mkOption {
+ default = {
+ enabled = true;
+ threshold = 1000; # by default, same threshold as 'ntpd -g' (1000s)
+ };
+ description = ''
+ Allow chronyd to make a rapid measurement of the system clock error at
+ boot time, and to correct the system clock by stepping before normal
+ operation begins.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration directives that should be added to
+ <literal>chrony.conf</literal>
+ '';
+ };
+
+ extraFlags = mkOption {
+ default = [];
+ example = [ "-s" ];
+ type = types.listOf types.str;
+ description = "Extra flags passed to the chronyd command.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+
+ environment.systemPackages = [ pkgs.chrony ];
+
+ users.groups = singleton
+ { name = "chrony";
+ gid = config.ids.gids.chrony;
+ };
+
+ users.users = singleton
+ { name = "chrony";
+ uid = config.ids.uids.chrony;
+ group = "chrony";
+ description = "chrony daemon user";
+ home = stateDir;
+ };
+
+ services.timesyncd.enable = mkForce false;
+
+ systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "chronyd.service"; };
+
+ systemd.services.chronyd =
+ { description = "chrony NTP daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "time-sync.target" ];
+ before = [ "time-sync.target" ];
+ after = [ "network.target" ];
+ conflicts = [ "ntpd.service" "systemd-timesyncd.service" ];
+
+ path = [ pkgs.chrony ];
+
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ touch ${keyFile}
+ chmod 0640 ${keyFile}
+ chown chrony:chrony ${stateDir} ${keyFile}
+ '';
+
+ unitConfig.ConditionCapability = "CAP_SYS_TIME";
+ serviceConfig =
+ { Type = "simple";
+ ExecStart = "${pkgs.chrony}/bin/chronyd ${chronyFlags}";
+
+ ProtectHome = "yes";
+ ProtectSystem = "full";
+ PrivateTmp = "yes";
+
+ };
+
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix b/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix
new file mode 100644
index 00000000000..1197c84f045
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs) ntp;
+
+ cfg = config.services.ntp;
+
+ stateDir = "/var/lib/ntp";
+
+ ntpUser = "ntp";
+
+ configFile = pkgs.writeText "ntp.conf" ''
+ driftfile ${stateDir}/ntp.drift
+
+ restrict default ${toString cfg.restrictDefault}
+ restrict -6 default ${toString cfg.restrictDefault}
+ restrict source ${toString cfg.restrictSource}
+
+ restrict 127.0.0.1
+ restrict -6 ::1
+
+ ${toString (map (server: "server " + server + " iburst\n") cfg.servers)}
+ '';
+
+ ntpFlags = "-c ${configFile} -u ${ntpUser}:nogroup ${toString cfg.extraFlags}";
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.ntp = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to synchronise your machine's time using ntpd, as a peer in
+ the NTP network.
+ </para>
+ <para>
+ Disables <literal>systemd.timesyncd</literal> if enabled.
+ '';
+ };
+
+ restrictDefault = mkOption {
+ type = types.listOf types.str;
+ description = ''
+ The restriction flags to be set by default.
+ </para>
+ <para>
+ The default flags prevent external hosts from using ntpd as a DDoS
+ reflector, setting system time, and querying OS/ntpd version. As
+ recommended in section 6.5.1.1.3, answer "No" of
+ http://support.ntp.org/bin/view/Support/AccessRestrictions
+ '';
+ default = [ "limited" "kod" "nomodify" "notrap" "noquery" "nopeer" ];
+ };
+
+ restrictSource = mkOption {
+ type = types.listOf types.str;
+ description = ''
+ The restriction flags to be set on source.
+ </para>
+ <para>
+ The default flags allow peers to be added by ntpd from configured
+ pool(s), but not by other means.
+ '';
+ default = [ "limited" "kod" "nomodify" "notrap" "noquery" ];
+ };
+
+ servers = mkOption {
+ default = config.networking.timeServers;
+ description = ''
+ The set of NTP servers from which to synchronise.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ description = "Extra flags passed to the ntpd command.";
+ example = literalExample ''[ "--interface=eth0" ]'';
+ default = [];
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.ntp.enable {
+ meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+
+ # Make tools such as ntpq available in the system path.
+ environment.systemPackages = [ pkgs.ntp ];
+ services.timesyncd.enable = mkForce false;
+
+ systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd.service"; };
+
+ users.users = singleton
+ { name = ntpUser;
+ uid = config.ids.uids.ntp;
+ description = "NTP daemon user";
+ home = stateDir;
+ };
+
+ systemd.services.ntpd =
+ { description = "NTP Daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "time-sync.target" ];
+ before = [ "time-sync.target" ];
+
+ preStart =
+ ''
+ mkdir -m 0755 -p ${stateDir}
+ chown ${ntpUser} ${stateDir}
+ '';
+
+ serviceConfig = {
+ ExecStart = "@${ntp}/bin/ntpd ntpd -g ${ntpFlags}";
+ Type = "forking";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix b/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix
new file mode 100644
index 00000000000..471d15b1687
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix
@@ -0,0 +1,83 @@
+{ pkgs, lib, config, options, ... }:
+
+with lib;
+
+let
+ cfg = config.services.openntpd;
+
+ package = pkgs.openntpd_nixos;
+
+ configFile = ''
+ ${concatStringsSep "\n" (map (s: "server ${s}") cfg.servers)}
+ ${cfg.extraConfig}
+ '';
+
+ pidFile = "/run/openntpd.pid";
+
+in
+{
+ ###### interface
+
+ options.services.openntpd = {
+ enable = mkEnableOption "OpenNTP time synchronization server";
+
+ servers = mkOption {
+ default = config.services.ntp.servers;
+ type = types.listOf types.str;
+ inherit (options.services.ntp.servers) description;
+ };
+
+ extraConfig = mkOption {
+ type = with types; lines;
+ default = "";
+ example = ''
+ listen on 127.0.0.1
+ listen on ::1
+ '';
+ description = ''
+ Additional text appended to <filename>openntpd.conf</filename>.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = with types; separatedString " ";
+ default = "";
+ example = "-s";
+ description = ''
+ Extra options used when launching openntpd.
+ '';
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+ services.timesyncd.enable = mkForce false;
+
+ # Add ntpctl to the environment for status checking
+ environment.systemPackages = [ package ];
+
+ environment.etc."ntpd.conf".text = configFile;
+
+ users.users = singleton {
+ name = "ntp";
+ uid = config.ids.uids.ntp;
+ description = "OpenNTP daemon user";
+ home = "/var/empty";
+ };
+
+ systemd.services.openntpd = {
+ description = "OpenNTP Server";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" "time-sync.target" ];
+ before = [ "time-sync.target" ];
+ after = [ "dnsmasq.service" "bind.service" "network-online.target" ];
+ serviceConfig = {
+ ExecStart = "${package}/sbin/ntpd -p ${pidFile} ${cfg.extraOptions}";
+ Type = "forking";
+ PIDFile = pidFile;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nullidentdmod.nix b/nixpkgs/nixos/modules/services/networking/nullidentdmod.nix
new file mode 100644
index 00000000000..b0d338a2794
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nullidentdmod.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }: with lib; let
+ cfg = config.services.nullidentdmod;
+
+in {
+ options.services.nullidentdmod = with types; {
+ enable = mkEnableOption "the nullidentdmod identd daemon";
+
+ userid = mkOption {
+ type = nullOr str;
+ description = "User ID to return. Set to null to return a random string each time.";
+ default = null;
+ example = "alice";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.sockets.nullidentdmod = {
+ description = "Socket for identd (NullidentdMod)";
+ listenStreams = [ "113" ];
+ socketConfig.Accept = true;
+ wantedBy = [ "sockets.target" ];
+ };
+
+ systemd.services."nullidentdmod@" = {
+ description = "NullidentdMod service";
+ serviceConfig = {
+ DynamicUser = true;
+ ExecStart = "${pkgs.nullidentdmod}/bin/nullidentdmod${optionalString (cfg.userid != null) " ${cfg.userid}"}";
+ StandardInput = "socket";
+ StandardOutput = "socket";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nylon.nix b/nixpkgs/nixos/modules/services/networking/nylon.nix
new file mode 100644
index 00000000000..7c171281a92
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nylon.nix
@@ -0,0 +1,166 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.nylon;
+
+ homeDir = "/var/lib/nylon";
+
+ configFile = cfg: pkgs.writeText "nylon-${cfg.name}.conf" ''
+ [General]
+ No-Simultaneous-Conn=${toString cfg.nrConnections}
+ Log=${if cfg.logging then "1" else "0"}
+ Verbose=${if cfg.verbosity then "1" else "0"}
+
+ [Server]
+ Binding-Interface=${cfg.acceptInterface}
+ Connecting-Interface=${cfg.bindInterface}
+ Port=${toString cfg.port}
+ Allow-IP=${concatStringsSep " " cfg.allowedIPRanges}
+ Deny-IP=${concatStringsSep " " cfg.deniedIPRanges}
+ '';
+
+ nylonOpts = { name, ... }: {
+
+ options = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables nylon as a running service upon activation.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "";
+ description = "The name of this nylon instance.";
+ };
+
+ nrConnections = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ The number of allowed simultaneous connections to the daemon, default 10.
+ '';
+ };
+
+ logging = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable logging, default is no logging.
+ '';
+ };
+
+ verbosity = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable verbose output, default is to not be verbose.
+ '';
+ };
+
+ acceptInterface = mkOption {
+ type = types.str;
+ default = "lo";
+ description = ''
+ Tell nylon which interface to listen for client requests on, default is "lo".
+ '';
+ };
+
+ bindInterface = mkOption {
+ type = types.str;
+ default = "enp3s0f0";
+ description = ''
+ Tell nylon which interface to use as an uplink, default is "enp3s0f0".
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 1080;
+ description = ''
+ What port to listen for client requests, default is 1080.
+ '';
+ };
+
+ allowedIPRanges = mkOption {
+ type = with types; listOf str;
+ default = [ "192.168.0.0/16" "127.0.0.1/8" "172.16.0.1/12" "10.0.0.0/8" ];
+ description = ''
+ Allowed client IP ranges are evaluated first, defaults to ARIN IPv4 private ranges:
+ [ "192.168.0.0/16" "127.0.0.0/8" "172.16.0.0/12" "10.0.0.0/8" ]
+ '';
+ };
+
+ deniedIPRanges = mkOption {
+ type = with types; listOf str;
+ default = [ "0.0.0.0/0" ];
+ description = ''
+ Denied client IP ranges, these gets evaluated after the allowed IP ranges, defaults to all IPv4 addresses:
+ [ "0.0.0.0/0" ]
+ To block all other access than the allowed.
+ '';
+ };
+ };
+ config = { name = mkDefault name; };
+ };
+
+ mkNamedNylon = cfg: {
+ "nylon-${cfg.name}" = {
+ description = "Nylon, a lightweight SOCKS proxy server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig =
+ {
+ User = "nylon";
+ Group = "nylon";
+ WorkingDirectory = homeDir;
+ ExecStart = "${pkgs.nylon}/bin/nylon -f -c ${configFile cfg}";
+ };
+ };
+ };
+
+ anyNylons = collect (p: p ? enable) cfg;
+ enabledNylons = filter (p: p.enable == true) anyNylons;
+ nylonUnits = map (nylon: mkNamedNylon nylon) enabledNylons;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.nylon = mkOption {
+ default = {};
+ description = "Collection of named nylon instances";
+ type = with types; loaOf (submodule nylonOpts);
+ internal = true;
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf (length(enabledNylons) > 0) {
+
+ users.users.nylon = {
+ group = "nylon";
+ description = "Nylon SOCKS Proxy";
+ home = homeDir;
+ createHome = true;
+ uid = config.ids.uids.nylon;
+ };
+
+ users.groups.nylon.gid = config.ids.gids.nylon;
+
+ systemd.services = fold (a: b: a // b) {} nylonUnits;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ocserv.nix b/nixpkgs/nixos/modules/services/networking/ocserv.nix
new file mode 100644
index 00000000000..dc26ffeafee
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ocserv.nix
@@ -0,0 +1,99 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.ocserv;
+
+in
+
+{
+ options.services.ocserv = {
+ enable = mkEnableOption "ocserv";
+
+ config = mkOption {
+ type = types.lines;
+
+ description = ''
+ Configuration content to start an OCServ server.
+
+ For a full configuration reference,please refer to the online documentation
+ (https://ocserv.gitlab.io/www/manual.html), the openconnect
+ recipes (https://github.com/openconnect/recipes) or `man ocserv`.
+ '';
+
+ example = ''
+ # configuration examples from $out/doc without explanatory comments.
+ # for a full reference please look at the installed man pages.
+ auth = "plain[passwd=./sample.passwd]"
+ tcp-port = 443
+ udp-port = 443
+ run-as-user = nobody
+ run-as-group = nogroup
+ socket-file = /run/ocserv-socket
+ server-cert = certs/server-cert.pem
+ server-key = certs/server-key.pem
+ keepalive = 32400
+ dpd = 90
+ mobile-dpd = 1800
+ switch-to-tcp-timeout = 25
+ try-mtu-discovery = false
+ cert-user-oid = 0.9.2342.19200300.100.1.1
+ tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-VERS-SSL3.0"
+ auth-timeout = 240
+ min-reauth-time = 300
+ max-ban-score = 80
+ ban-reset-time = 1200
+ cookie-timeout = 300
+ deny-roaming = false
+ rekey-time = 172800
+ rekey-method = ssl
+ use-occtl = true
+ pid-file = /run/ocserv.pid
+ device = vpns
+ predictable-ips = true
+ default-domain = example.com
+ ipv4-network = 192.168.1.0
+ ipv4-netmask = 255.255.255.0
+ dns = 192.168.1.2
+ ping-leases = false
+ route = 10.10.10.0/255.255.255.0
+ route = 192.168.0.0/255.255.0.0
+ no-route = 192.168.5.0/255.255.255.0
+ cisco-client-compat = true
+ dtls-legacy = true
+
+ [vhost:www.example.com]
+ auth = "certificate"
+ ca-cert = certs/ca.pem
+ server-cert = certs/server-cert-secp521r1.pem
+ server-key = cersts/certs/server-key-secp521r1.pem
+ ipv4-network = 192.168.2.0
+ ipv4-netmask = 255.255.255.0
+ cert-user-oid = 0.9.2342.19200300.100.1.1
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.ocserv ];
+ environment.etc."ocserv/ocserv.conf".text = cfg.config;
+
+ security.pam.services.ocserv = {};
+
+ systemd.services.ocserv = {
+ description = "OpenConnect SSL VPN server";
+ documentation = [ "man:ocserv(8)" ];
+ after = [ "dbus.service" "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ PrivateTmp = true;
+ PIDFile = "/run/ocserv.pid";
+ ExecStart = "${pkgs.ocserv}/bin/ocserv --foreground --pid-file /run/ocesrv.pid --config /etc/ocserv/ocserv.conf";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ofono.nix b/nixpkgs/nixos/modules/services/networking/ofono.nix
new file mode 100644
index 00000000000..40ef9433de0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ofono.nix
@@ -0,0 +1,44 @@
+# Ofono daemon.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.ofono;
+
+ plugin_path =
+ lib.concatMapStringsSep ":"
+ (plugin: "${plugin}/lib/ofono/plugins")
+ cfg.plugins
+ ;
+
+in
+
+{
+ ###### interface
+ options = {
+ services.ofono = {
+ enable = mkEnableOption "Ofono";
+
+ plugins = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "[ pkgs.modem-manager-gui ]";
+ description = ''
+ The list of plugins to install.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.dbus.packages = [ pkgs.ofono ];
+
+ systemd.packages = [ pkgs.ofono ];
+
+ systemd.services.ofono.environment.OFONO_PLUGIN_PATH = mkIf (cfg.plugins != []) plugin_path;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/oidentd.nix b/nixpkgs/nixos/modules/services/networking/oidentd.nix
new file mode 100644
index 00000000000..feb84806ba9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/oidentd.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.oidentd.enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable ‘oidentd’, an implementation of the Ident
+ protocol (RFC 1413). It allows remote systems to identify the
+ name of the user associated with a TCP connection.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.oidentd.enable {
+ systemd.services.oidentd = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.Type = "forking";
+ script = "${pkgs.oidentd}/sbin/oidentd -u oidentd -g nogroup";
+ };
+
+ users.users.oidentd = {
+ description = "Ident Protocol daemon user";
+ group = "oidentd";
+ uid = config.ids.uids.oidentd;
+ };
+
+ users.groups.oidentd.gid = config.ids.gids.oidentd;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/openfire.nix b/nixpkgs/nixos/modules/services/networking/openfire.nix
new file mode 100644
index 00000000000..4059eb3db83
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/openfire.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+
+ options = {
+
+ services.openfire = {
+
+ enable = mkOption {
+ default = false;
+ description = "
+ Whether to enable OpenFire XMPP server.
+ ";
+ };
+
+ usePostgreSQL = mkOption {
+ default = true;
+ description = "
+ Whether you use PostgreSQL service for your storage back-end.
+ ";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.openfire.enable {
+
+ assertions = singleton
+ { assertion = !(config.services.openfire.usePostgreSQL -> config.services.postgresql.enable);
+ message = "OpenFire configured to use PostgreSQL but services.postgresql.enable is not enabled.";
+ };
+
+ systemd.services.openfire = {
+ description = "OpenFire XMPP server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "networking.target" ] ++
+ optional config.services.openfire.usePostgreSQL "postgresql.service";
+ path = with pkgs; [ jre openfire coreutils which gnugrep gawk gnused ];
+ script = ''
+ export HOME=/tmp
+ mkdir /var/log/openfire || true
+ mkdir /etc/openfire || true
+ for i in ${pkgs.openfire}/conf.inst/*; do
+ if ! test -f /etc/openfire/$(basename $i); then
+ cp $i /etc/openfire/
+ fi
+ done
+ openfire start
+ ''; # */
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/openvpn.nix b/nixpkgs/nixos/modules/services/networking/openvpn.nix
new file mode 100644
index 00000000000..05be97e66a3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/openvpn.nix
@@ -0,0 +1,216 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.openvpn;
+
+ inherit (pkgs) openvpn;
+
+ makeOpenVPNJob = cfg: name:
+ let
+
+ path = (getAttr "openvpn-${name}" config.systemd.services).path;
+
+ upScript = ''
+ #! /bin/sh
+ export PATH=${path}
+
+ # For convenience in client scripts, extract the remote domain
+ # name and name server.
+ for var in ''${!foreign_option_*}; do
+ x=(''${!var})
+ if [ "''${x[0]}" = dhcp-option ]; then
+ if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}"
+ elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}"
+ fi
+ fi
+ done
+
+ ${cfg.up}
+ ${optionalString cfg.updateResolvConf
+ "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
+ '';
+
+ downScript = ''
+ #! /bin/sh
+ export PATH=${path}
+ ${optionalString cfg.updateResolvConf
+ "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
+ ${cfg.down}
+ '';
+
+ configFile = pkgs.writeText "openvpn-config-${name}"
+ ''
+ errors-to-stderr
+ ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
+ ${cfg.config}
+ ${optionalString (cfg.up != "" || cfg.updateResolvConf)
+ "up ${pkgs.writeScript "openvpn-${name}-up" upScript}"}
+ ${optionalString (cfg.down != "" || cfg.updateResolvConf)
+ "down ${pkgs.writeScript "openvpn-${name}-down" downScript}"}
+ ${optionalString (cfg.authUserPass != null)
+ "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
+ ${cfg.authUserPass.username}
+ ${cfg.authUserPass.password}
+ ''}"}
+ '';
+
+ in {
+ description = "OpenVPN instance ‘${name}’";
+
+ wantedBy = optional cfg.autoStart "multi-user.target";
+ after = [ "network.target" ];
+
+ path = [ pkgs.iptables pkgs.iproute pkgs.nettools ];
+
+ serviceConfig.ExecStart = "@${openvpn}/sbin/openvpn openvpn --suppress-timestamps --config ${configFile}";
+ serviceConfig.Restart = "always";
+ serviceConfig.Type = "notify";
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.openvpn.servers = mkOption {
+ default = {};
+
+ example = literalExample ''
+ {
+ server = {
+ config = '''
+ # Simplest server configuration: https://community.openvpn.net/openvpn/wiki/StaticKeyMiniHowto
+ # server :
+ dev tun
+ ifconfig 10.8.0.1 10.8.0.2
+ secret /root/static.key
+ ''';
+ up = "ip route add ...";
+ down = "ip route del ...";
+ };
+
+ client = {
+ config = '''
+ client
+ remote vpn.example.org
+ dev tun
+ proto tcp-client
+ port 8080
+ ca /root/.vpn/ca.crt
+ cert /root/.vpn/alice.crt
+ key /root/.vpn/alice.key
+ ''';
+ up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
+ down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
+ };
+ }
+ '';
+
+ description = ''
+ Each attribute of this option defines a systemd service that
+ runs an OpenVPN instance. These can be OpenVPN servers or
+ clients. The name of each systemd service is
+ <literal>openvpn-<replaceable>name</replaceable>.service</literal>,
+ where <replaceable>name</replaceable> is the corresponding
+ attribute name.
+ '';
+
+ type = with types; attrsOf (submodule {
+
+ options = {
+
+ config = mkOption {
+ type = types.lines;
+ description = ''
+ Configuration of this OpenVPN instance. See
+ <citerefentry><refentrytitle>openvpn</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ for details.
+
+ To import an external config file, use the following definition:
+ <literal>config = "config /path/to/config.ovpn"</literal>
+ '';
+ };
+
+ up = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands executed when the instance is starting.
+ '';
+ };
+
+ down = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands executed when the instance is shutting down.
+ '';
+ };
+
+ autoStart = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether this OpenVPN instance should be started automatically.";
+ };
+
+ updateResolvConf = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Use the script from the update-resolv-conf package to automatically
+ update resolv.conf with the DNS information provided by openvpn. The
+ script will be run after the "up" commands and before the "down" commands.
+ '';
+ };
+
+ authUserPass = mkOption {
+ default = null;
+ description = ''
+ This option can be used to store the username / password credentials
+ with the "auth-user-pass" authentication method.
+
+ WARNING: Using this option will put the credentials WORLD-READABLE in the Nix store!
+ '';
+ type = types.nullOr (types.submodule {
+
+ options = {
+ username = mkOption {
+ description = "The username to store inside the credentials file.";
+ type = types.str;
+ };
+
+ password = mkOption {
+ description = "The password to store inside the credentials file.";
+ type = types.str;
+ };
+ };
+ });
+ };
+ };
+
+ });
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf (cfg.servers != {}) {
+
+ systemd.services = listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers);
+
+ environment.systemPackages = [ openvpn ];
+
+ boot.kernelModules = [ "tun" ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ostinato.nix b/nixpkgs/nixos/modules/services/networking/ostinato.nix
new file mode 100644
index 00000000000..5e8cce5b89a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ostinato.nix
@@ -0,0 +1,104 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ pkg = pkgs.ostinato;
+ cfg = config.services.ostinato;
+ configFile = pkgs.writeText "drone.ini" ''
+ [General]
+ RateAccuracy=${cfg.rateAccuracy}
+
+ [RpcServer]
+ Address=${cfg.rpcServer.address}
+
+ [PortList]
+ Include=${concatStringsSep "," cfg.portList.include}
+ Exclude=${concatStringsSep "," cfg.portList.exclude}
+ '';
+
+in
+{
+
+ ###### interface
+
+ options = {
+
+ services.ostinato = {
+
+ enable = mkEnableOption "Ostinato agent-controller (Drone)";
+
+ port = mkOption {
+ type = types.int;
+ default = 7878;
+ description = ''
+ Port to listen on.
+ '';
+ };
+
+ rateAccuracy = mkOption {
+ type = types.enum [ "High" "Low" ];
+ default = "High";
+ description = ''
+ To ensure that the actual transmit rate is as close as possible to
+ the configured transmit rate, Drone runs a busy-wait loop.
+ While this provides the maximum accuracy possible, the CPU
+ utilization is 100% while the transmit is on. You can however,
+ sacrifice the accuracy to reduce the CPU load.
+ '';
+ };
+
+ rpcServer = {
+ address = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ By default, the Drone RPC server will listen on all interfaces and
+ local IPv4 adresses for incoming connections from clients. Specify
+ a single IPv4 or IPv6 address if you want to restrict that.
+ To listen on any IPv6 address, use ::
+ '';
+ };
+ };
+
+ portList = {
+ include = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ''[ "eth*" "lo*" ]'';
+ description = ''
+ For a port to pass the filter and appear on the port list managed
+ by drone, it be allowed by this include list.
+ '';
+ };
+ exclude = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ''[ "usbmon*" "eth0" ]'';
+ description = ''
+ A list of ports does not appear on the port list managed by drone.
+ '';
+ };
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkg ];
+
+ systemd.services.drone = {
+ description = "Ostinato agent-controller";
+ wantedBy = [ "multi-user.target" ];
+ script = ''
+ ${pkg}/bin/drone ${toString cfg.port} ${configFile}
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/owamp.nix b/nixpkgs/nixos/modules/services/networking/owamp.nix
new file mode 100644
index 00000000000..821a0258f4b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/owamp.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.owamp;
+in
+{
+
+ ###### interface
+
+ options = {
+ services.owamp.enable = mkEnableOption ''Enable OWAMP server'';
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ users.users = singleton {
+ name = "owamp";
+ group = "owamp";
+ description = "Owamp daemon";
+ };
+
+ users.groups = singleton {
+ name = "owamp";
+ };
+
+ systemd.services.owamp = {
+ description = "Owamp server";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart="${pkgs.owamp}/bin/owampd -R /run/owamp -d /run/owamp -v -Z ";
+ PrivateTmp = true;
+ Restart = "always";
+ Type="simple";
+ User = "owamp";
+ Group = "owamp";
+ RuntimeDirectory = "owamp";
+ StateDirectory = "owamp";
+ AmbientCapabilities = "cap_net_bind_service";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/pdns-recursor.nix b/nixpkgs/nixos/modules/services/networking/pdns-recursor.nix
new file mode 100644
index 00000000000..ebfdd9f35b7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/pdns-recursor.nix
@@ -0,0 +1,213 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ dataDir = "/var/lib/pdns-recursor";
+ username = "pdns-recursor";
+
+ cfg = config.services.pdns-recursor;
+
+ oneOrMore = type: with types; either type (listOf type);
+ valueType = with types; oneOf [ int str bool path ];
+ configType = with types; attrsOf (nullOr (oneOrMore valueType));
+
+ toBool = val: if val then "yes" else "no";
+ serialize = val: with types;
+ if str.check val then val
+ else if int.check val then toString val
+ else if path.check val then toString val
+ else if bool.check val then toBool val
+ else if builtins.isList val then (concatMapStringsSep "," serialize val)
+ else "";
+
+ configFile = pkgs.writeText "recursor.conf"
+ (concatStringsSep "\n"
+ (flip mapAttrsToList cfg.settings
+ (name: val: "${name}=${serialize val}")));
+
+ mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
+
+in {
+ options.services.pdns-recursor = {
+ enable = mkEnableOption "PowerDNS Recursor, a recursive DNS server";
+
+ dns.address = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ IP address Recursor DNS server will bind to.
+ '';
+ };
+
+ dns.port = mkOption {
+ type = types.int;
+ default = 53;
+ description = ''
+ Port number Recursor DNS server will bind to.
+ '';
+ };
+
+ dns.allowFrom = mkOption {
+ type = types.listOf types.str;
+ default = [ "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" ];
+ example = [ "0.0.0.0/0" ];
+ description = ''
+ IP address ranges of clients allowed to make DNS queries.
+ '';
+ };
+
+ api.address = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ IP address Recursor REST API server will bind to.
+ '';
+ };
+
+ api.port = mkOption {
+ type = types.int;
+ default = 8082;
+ description = ''
+ Port number Recursor REST API server will bind to.
+ '';
+ };
+
+ api.allowFrom = mkOption {
+ type = types.listOf types.str;
+ default = [ "0.0.0.0/0" ];
+ description = ''
+ IP address ranges of clients allowed to make API requests.
+ '';
+ };
+
+ exportHosts = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to export names and IP addresses defined in /etc/hosts.
+ '';
+ };
+
+ forwardZones = mkOption {
+ type = types.attrs;
+ example = { eth = "127.0.0.1:5353"; };
+ default = {};
+ description = ''
+ DNS zones to be forwarded to other servers.
+ '';
+ };
+
+ dnssecValidation = mkOption {
+ type = types.enum ["off" "process-no-validate" "process" "log-fail" "validate"];
+ default = "validate";
+ description = ''
+ Controls the level of DNSSEC processing done by the PowerDNS Recursor.
+ See https://doc.powerdns.com/md/recursor/dnssec/ for a detailed explanation.
+ '';
+ };
+
+ serveRFC1918 = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to directly resolve the RFC1918 reverse-mapping domains:
+ <literal>10.in-addr.arpa</literal>,
+ <literal>168.192.in-addr.arpa</literal>,
+ <literal>16-31.172.in-addr.arpa</literal>
+ This saves load on the AS112 servers.
+ '';
+ };
+
+ settings = mkOption {
+ type = configType;
+ default = { };
+ example = literalExample ''
+ {
+ loglevel = 8;
+ log-common-errors = true;
+ }
+ '';
+ description = ''
+ PowerDNS Recursor settings. Use this option to configure Recursor
+ settings not exposed in a NixOS option or to bypass one.
+ See the full documentation at
+ <link xlink:href="https://doc.powerdns.com/recursor/settings.html"/>
+ for the available options.
+ '';
+ };
+
+ luaConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ The content Lua configuration file for PowerDNS Recursor. See
+ <link xlink:href="https://doc.powerdns.com/recursor/lua-config/index.html"/>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ services.pdns-recursor.settings = mkDefaultAttrs {
+ local-address = cfg.dns.address;
+ local-port = cfg.dns.port;
+ allow-from = cfg.dns.allowFrom;
+
+ webserver-address = cfg.api.address;
+ webserver-port = cfg.api.port;
+ webserver-allow-from = cfg.api.allowFrom;
+
+ forward-zones = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones;
+ export-etc-hosts = cfg.exportHosts;
+ dnssec = cfg.dnssecValidation;
+ serve-rfc1918 = cfg.serveRFC1918;
+ lua-config-file = pkgs.writeText "recursor.lua" cfg.luaConfig;
+
+ log-timestamp = false;
+ disable-syslog = true;
+ };
+
+ users.users.${username} = {
+ home = dataDir;
+ createHome = true;
+ uid = config.ids.uids.pdns-recursor;
+ description = "PowerDNS Recursor daemon user";
+ };
+
+ systemd.services.pdns-recursor = {
+ unitConfig.Documentation = "man:pdns_recursor(1) man:rec_control(1)";
+ description = "PowerDNS recursive server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ User = username;
+ Restart ="on-failure";
+ RestartSec = "5";
+ PrivateTmp = true;
+ PrivateDevices = true;
+ AmbientCapabilities = "cap_net_bind_service";
+ ExecStart = ''${pkgs.pdns-recursor}/bin/pdns_recursor \
+ --config-dir=${dataDir} \
+ --socket-dir=${dataDir}
+ '';
+ };
+
+ preStart = ''
+ # Link configuration file into recursor home directory
+ configPath=${dataDir}/recursor.conf
+ if [ "$(realpath $configPath)" != "${configFile}" ]; then
+ rm -f $configPath
+ ln -s ${configFile} $configPath
+ fi
+ '';
+ };
+ };
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "pdns-recursor" "extraConfig" ]
+ "To change extra Recursor settings use services.pdns-recursor.settings instead.")
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/pdnsd.nix b/nixpkgs/nixos/modules/services/networking/pdnsd.nix
new file mode 100644
index 00000000000..f5b174dd7b7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/pdnsd.nix
@@ -0,0 +1,93 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.pdnsd;
+ pdnsd = pkgs.pdnsd;
+ pdnsdUser = "pdnsd";
+ pdnsdGroup = "pdnsd";
+ pdnsdConf = pkgs.writeText "pdnsd.conf"
+ ''
+ global {
+ run_as=${pdnsdUser};
+ cache_dir="${cfg.cacheDir}";
+ ${cfg.globalConfig}
+ }
+
+ server {
+ ${cfg.serverConfig}
+ }
+ ${cfg.extraConfig}
+ '';
+in
+
+{ options =
+ { services.pdnsd =
+ { enable = mkEnableOption "pdnsd";
+
+ cacheDir = mkOption {
+ type = types.str;
+ default = "/var/cache/pdnsd";
+ description = "Directory holding the pdnsd cache";
+ };
+
+ globalConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Global configuration that should be added to the global directory
+ of <literal>pdnsd.conf</literal>.
+ '';
+ };
+
+ serverConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Server configuration that should be added to the server directory
+ of <literal>pdnsd.conf</literal>.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration directives that should be added to
+ <literal>pdnsd.conf</literal>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users = singleton {
+ name = pdnsdUser;
+ uid = config.ids.uids.pdnsd;
+ group = pdnsdGroup;
+ description = "pdnsd user";
+ };
+
+ users.groups = singleton {
+ name = pdnsdGroup;
+ gid = config.ids.gids.pdnsd;
+ };
+
+ systemd.services.pdnsd =
+ { wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ preStart =
+ ''
+ mkdir -p "${cfg.cacheDir}"
+ touch "${cfg.cacheDir}/pdnsd.cache"
+ chown -R ${pdnsdUser}:${pdnsdGroup} "${cfg.cacheDir}"
+ '';
+ description = "pdnsd";
+ serviceConfig =
+ {
+ ExecStart = "${pdnsd}/bin/pdnsd -c ${pdnsdConf}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/polipo.nix b/nixpkgs/nixos/modules/services/networking/polipo.nix
new file mode 100644
index 00000000000..dbe3b738097
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/polipo.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.polipo;
+
+ polipoConfig = pkgs.writeText "polipo.conf" ''
+ proxyAddress = ${cfg.proxyAddress}
+ proxyPort = ${toString cfg.proxyPort}
+ allowedClients = ${concatStringsSep ", " cfg.allowedClients}
+ ${optionalString (cfg.parentProxy != "") "parentProxy = ${cfg.parentProxy}" }
+ ${optionalString (cfg.socksParentProxy != "") "socksParentProxy = ${cfg.socksParentProxy}" }
+ ${config.services.polipo.extraConfig}
+ '';
+
+in
+
+{
+
+ options = {
+
+ services.polipo = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to run the polipo caching web proxy.";
+ };
+
+ proxyAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "IP address on which Polipo will listen.";
+ };
+
+ proxyPort = mkOption {
+ type = types.int;
+ default = 8123;
+ description = "TCP port on which Polipo will listen.";
+ };
+
+ allowedClients = mkOption {
+ type = types.listOf types.str;
+ default = [ "127.0.0.1" "::1" ];
+ example = [ "127.0.0.1" "::1" "134.157.168.0/24" "2001:660:116::/48" ];
+ description = ''
+ List of IP addresses or network addresses that may connect to Polipo.
+ '';
+ };
+
+ parentProxy = mkOption {
+ type = types.str;
+ default = "";
+ example = "localhost:8124";
+ description = ''
+ Hostname and port number of an HTTP parent proxy;
+ it should have the form ‘host:port’.
+ '';
+ };
+
+ socksParentProxy = mkOption {
+ type = types.str;
+ default = "";
+ example = "localhost:9050";
+ description = ''
+ Hostname and port number of an SOCKS parent proxy;
+ it should have the form ‘host:port’.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Polio configuration. Contents will be added
+ verbatim to the configuration file.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton
+ { name = "polipo";
+ uid = config.ids.uids.polipo;
+ description = "Polipo caching proxy user";
+ home = "/var/cache/polipo";
+ createHome = true;
+ };
+
+ users.groups = singleton
+ { name = "polipo";
+ gid = config.ids.gids.polipo;
+ members = [ "polipo" ];
+ };
+
+ systemd.services.polipo = {
+ description = "caching web proxy";
+ after = [ "network.target" "nss-lookup.target" ];
+ wantedBy = [ "multi-user.target"];
+ serviceConfig = {
+ ExecStart = "${pkgs.polipo}/bin/polipo -c ${polipoConfig}";
+ User = "polipo";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/powerdns.nix b/nixpkgs/nixos/modules/services/networking/powerdns.nix
new file mode 100644
index 00000000000..ba05e15389f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/powerdns.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.powerdns;
+ configDir = pkgs.writeTextDir "pdns.conf" "${cfg.extraConfig}";
+in {
+ options = {
+ services.powerdns = {
+ enable = mkEnableOption "Powerdns domain name server";
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "launch=bind";
+ description = ''
+ Extra lines to be added verbatim to pdns.conf.
+ Powerdns will chroot to /var/lib/powerdns.
+ So any file, powerdns is supposed to be read,
+ should be in /var/lib/powerdns and needs to specified
+ relative to the chroot.
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.powerdns.enable {
+ systemd.services.pdns = {
+ unitConfig.Documentation = "man:pdns_server(1) man:pdns_control(1)";
+ description = "Powerdns name server";
+ wantedBy = [ "multi-user.target" ];
+ after = ["network.target" "mysql.service" "postgresql.service" "openldap.service"];
+
+ serviceConfig = {
+ Restart="on-failure";
+ RestartSec="1";
+ StartLimitInterval="0";
+ PrivateDevices=true;
+ CapabilityBoundingSet="CAP_CHOWN CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_SYS_CHROOT";
+ NoNewPrivileges=true;
+ ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/lib/powerdns";
+ ExecStart = "${pkgs.powerdns}/bin/pdns_server --setuid=nobody --setgid=nogroup --chroot=/var/lib/powerdns --socket-dir=/ --daemon=no --guardian=no --disable-syslog --write-pid=no --config-dir=${configDir}";
+ ProtectSystem="full";
+ ProtectHome=true;
+ RestrictAddressFamilies="AF_UNIX AF_INET AF_INET6";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/pptpd.nix b/nixpkgs/nixos/modules/services/networking/pptpd.nix
new file mode 100644
index 00000000000..3e7753b9dd3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/pptpd.nix
@@ -0,0 +1,124 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+ options = {
+ services.pptpd = {
+ enable = mkEnableOption "pptpd, the Point-to-Point Tunneling Protocol daemon";
+
+ serverIp = mkOption {
+ type = types.str;
+ description = "The server-side IP address.";
+ default = "10.124.124.1";
+ };
+
+ clientIpRange = mkOption {
+ type = types.str;
+ description = "The range from which client IPs are drawn.";
+ default = "10.124.124.2-11";
+ };
+
+ maxClients = mkOption {
+ type = types.int;
+ description = "The maximum number of simultaneous connections.";
+ default = 10;
+ };
+
+ extraPptpdOptions = mkOption {
+ type = types.lines;
+ description = "Adds extra lines to the pptpd configuration file.";
+ default = "";
+ };
+
+ extraPppdOptions = mkOption {
+ type = types.lines;
+ description = "Adds extra lines to the pppd options file.";
+ default = "";
+ example = ''
+ ms-dns 8.8.8.8
+ ms-dns 8.8.4.4
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.pptpd.enable {
+ systemd.services.pptpd = let
+ cfg = config.services.pptpd;
+
+ pptpd-conf = pkgs.writeText "pptpd.conf" ''
+ # Inspired from pptpd-1.4.0/samples/pptpd.conf
+ ppp ${ppp-pptpd-wrapped}/bin/pppd
+ option ${pppd-options}
+ pidfile /run/pptpd.pid
+ localip ${cfg.serverIp}
+ remoteip ${cfg.clientIpRange}
+ connections ${toString cfg.maxClients} # (Will get harmless warning if inconsistent with IP range)
+
+ # Extra
+ ${cfg.extraPptpdOptions}
+ '';
+
+ pppd-options = pkgs.writeText "ppp-options-pptpd.conf" ''
+ # From: cat pptpd-1.4.0/samples/options.pptpd | grep -v ^# | grep -v ^$
+ name pptpd
+ refuse-pap
+ refuse-chap
+ refuse-mschap
+ require-mschap-v2
+ require-mppe-128
+ proxyarp
+ lock
+ nobsdcomp
+ novj
+ novjccomp
+ nologfd
+
+ # Extra:
+ ${cfg.extraPppdOptions}
+ '';
+
+ ppp-pptpd-wrapped = pkgs.stdenv.mkDerivation {
+ name = "ppp-pptpd-wrapped";
+ phases = [ "installPhase" ];
+ buildInputs = with pkgs; [ makeWrapper ];
+ installPhase = ''
+ mkdir -p $out/bin
+ makeWrapper ${pkgs.ppp}/bin/pppd $out/bin/pppd \
+ --set LD_PRELOAD "${pkgs.libredirect}/lib/libredirect.so" \
+ --set NIX_REDIRECTS "/etc/ppp=/etc/ppp-pptpd"
+ '';
+ };
+ in {
+ description = "pptpd server";
+
+ requires = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ mkdir -p -m 700 /etc/ppp-pptpd
+
+ secrets="/etc/ppp-pptpd/chap-secrets"
+
+ [ -f "$secrets" ] || cat > "$secrets" << EOF
+ # From: pptpd-1.4.0/samples/chap-secrets
+ # Secrets for authentication using CHAP
+ # client server secret IP addresses
+ #username pptpd password *
+ EOF
+
+ chown root.root "$secrets"
+ chmod 600 "$secrets"
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkgs.pptpd}/bin/pptpd --conf ${pptpd-conf}";
+ KillMode = "process";
+ Restart = "on-success";
+ Type = "forking";
+ PIDFile = "/run/pptpd.pid";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/prayer.nix b/nixpkgs/nixos/modules/services/networking/prayer.nix
new file mode 100644
index 00000000000..c936417e68c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/prayer.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs) prayer;
+
+ cfg = config.services.prayer;
+
+ stateDir = "/var/lib/prayer";
+
+ prayerUser = "prayer";
+ prayerGroup = "prayer";
+
+ prayerExtraCfg = pkgs.writeText "extraprayer.cf" ''
+ prefix = "${prayer}"
+ var_prefix = "${stateDir}"
+ prayer_user = "${prayerUser}"
+ prayer_group = "${prayerGroup}"
+ sendmail_path = "/run/wrappers/bin/sendmail"
+
+ use_http_port ${cfg.port}
+
+ ${cfg.extraConfig}
+ '';
+
+ prayerCfg = pkgs.runCommand "prayer.cf" { preferLocalBuild = true; } ''
+ # We have to remove the http_port 80, or it will start a server there
+ cat ${prayer}/etc/prayer.cf | grep -v http_port > $out
+ cat ${prayerExtraCfg} >> $out
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.prayer = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to run the prayer webmail http server.
+ '';
+ };
+
+ port = mkOption {
+ default = "2080";
+ description = ''
+ Port the prayer http server is listening to.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "" ;
+ description = ''
+ Extra configuration. Contents will be added verbatim to the configuration file.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.prayer.enable {
+ environment.systemPackages = [ prayer ];
+
+ users.users = singleton
+ { name = prayerUser;
+ uid = config.ids.uids.prayer;
+ description = "Prayer daemon user";
+ home = stateDir;
+ };
+
+ users.groups = singleton
+ { name = prayerGroup;
+ gid = config.ids.gids.prayer;
+ };
+
+ systemd.services.prayer = {
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.Type = "forking";
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ chown ${prayerUser}.${prayerGroup} ${stateDir}
+ '';
+ script = "${prayer}/sbin/prayer --config-file=${prayerCfg}";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/privoxy.nix b/nixpkgs/nixos/modules/services/networking/privoxy.nix
new file mode 100644
index 00000000000..49ca839a2c3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/privoxy.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs) privoxy;
+
+ cfg = config.services.privoxy;
+
+ confFile = pkgs.writeText "privoxy.conf" ''
+ user-manual ${privoxy}/share/doc/privoxy/user-manual
+ confdir ${privoxy}/etc/
+ listen-address ${cfg.listenAddress}
+ enable-edit-actions ${if (cfg.enableEditActions == true) then "1" else "0"}
+ ${concatMapStrings (f: "actionsfile ${f}\n") cfg.actionsFiles}
+ ${concatMapStrings (f: "filterfile ${f}\n") cfg.filterFiles}
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.privoxy = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the Privoxy non-caching filtering proxy.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8118";
+ description = ''
+ Address the proxy server is listening to.
+ '';
+ };
+
+ actionsFiles = mkOption {
+ type = types.listOf types.str;
+ example = [ "match-all.action" "default.action" "/etc/privoxy/user.action" ];
+ default = [ "match-all.action" "default.action" ];
+ description = ''
+ List of paths to Privoxy action files.
+ These paths may either be absolute or relative to the privoxy configuration directory.
+ '';
+ };
+
+ filterFiles = mkOption {
+ type = types.listOf types.str;
+ example = [ "default.filter" "/etc/privoxy/user.filter" ];
+ default = [ "default.filter" ];
+ description = ''
+ List of paths to Privoxy filter files.
+ These paths may either be absolute or relative to the privoxy configuration directory.
+ '';
+ };
+
+ enableEditActions = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether or not the web-based actions file editor may be used.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "" ;
+ description = ''
+ Extra configuration. Contents will be added verbatim to the configuration file.
+ '';
+ };
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users.privoxy = {
+ isSystemUser = true;
+ home = "/var/empty";
+ group = "privoxy";
+ };
+
+ users.groups.privoxy = {};
+
+ systemd.services.privoxy = {
+ description = "Filtering web proxy";
+ after = [ "network.target" "nss-lookup.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${privoxy}/bin/privoxy --no-daemon --user privoxy ${confFile}";
+
+ serviceConfig.PrivateDevices = true;
+ serviceConfig.PrivateTmp = true;
+ serviceConfig.ProtectHome = true;
+ serviceConfig.ProtectSystem = "full";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/prosody.nix b/nixpkgs/nixos/modules/services/networking/prosody.nix
new file mode 100644
index 00000000000..7a503e71166
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/prosody.nix
@@ -0,0 +1,530 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.prosody;
+
+ sslOpts = { ... }: {
+
+ options = {
+
+ key = mkOption {
+ type = types.path;
+ description = "Path to the key file.";
+ };
+
+ # TODO: rename to certificate to match the prosody config
+ cert = mkOption {
+ type = types.path;
+ description = "Path to the certificate file.";
+ };
+
+ extraOptions = mkOption {
+ type = types.attrs;
+ default = {};
+ description = "Extra SSL configuration options.";
+ };
+
+ };
+ };
+
+ moduleOpts = {
+ # Generally required
+ roster = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Allow users to have a roster";
+ };
+
+ saslauth = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Authentication for clients and servers. Recommended if you want to log in.";
+ };
+
+ tls = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Add support for secure TLS on c2s/s2s connections";
+ };
+
+ dialback = mkOption {
+ type = types.bool;
+ default = true;
+ description = "s2s dialback support";
+ };
+
+ disco = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Service discovery";
+ };
+
+ # Not essential, but recommended
+ carbons = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Keep multiple clients in sync";
+ };
+
+ pep = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Enables users to publish their mood, activity, playing music and more";
+ };
+
+ private = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Private XML storage (for room bookmarks, etc.)";
+ };
+
+ blocklist = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Allow users to block communications with other users";
+ };
+
+ vcard = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Allow users to set vCards";
+ };
+
+ # Nice to have
+ version = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Replies to server version requests";
+ };
+
+ uptime = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Report how long server has been running";
+ };
+
+ time = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Let others know the time here on this server";
+ };
+
+ ping = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Replies to XMPP pings with pongs";
+ };
+
+ register = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Allow users to register on this server using a client and change passwords";
+ };
+
+ mam = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Store messages in an archive and allow users to access it";
+ };
+
+ # Admin interfaces
+ admin_adhoc = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Allows administration via an XMPP client that supports ad-hoc commands";
+ };
+
+ admin_telnet = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Opens telnet console interface on localhost port 5582";
+ };
+
+ # HTTP modules
+ bosh = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable BOSH clients, aka 'Jabber over HTTP'";
+ };
+
+ websocket = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable WebSocket support";
+ };
+
+ http_files = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Serve static files from a directory over HTTP";
+ };
+
+ # Other specific functionality
+ limits = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable bandwidth limiting for XMPP connections";
+ };
+
+ groups = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Shared roster support";
+ };
+
+ server_contact_info = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Publish contact information for this service";
+ };
+
+ announce = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Send announcement to all online users";
+ };
+
+ welcome = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Welcome users who register accounts";
+ };
+
+ watchregistrations = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Alert admins of registrations";
+ };
+
+ motd = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Send a message to users when they log in";
+ };
+
+ legacyauth = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Legacy authentication. Only used by some old clients and bots";
+ };
+
+ proxy65 = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enables a file transfer proxy service which clients behind NAT can use";
+ };
+
+ };
+
+ toLua = x:
+ if builtins.isString x then ''"${x}"''
+ else if builtins.isBool x then (if x == true then "true" else "false")
+ else if builtins.isInt x then toString x
+ else if builtins.isList x then ''{ ${lib.concatStringsSep ", " (map (n: toLua n) x) } }''
+ else throw "Invalid Lua value";
+
+ createSSLOptsStr = o: ''
+ ssl = {
+ cafile = "/etc/ssl/certs/ca-bundle.crt";
+ key = "${o.key}";
+ certificate = "${o.cert}";
+ ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name} = ${toLua value};") o.extraOptions)}
+ };
+ '';
+
+ vHostOpts = { ... }: {
+
+ options = {
+
+ # TODO: require attribute
+ domain = mkOption {
+ type = types.str;
+ description = "Domain name";
+ };
+
+ enabled = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the virtual host";
+ };
+
+ ssl = mkOption {
+ type = types.nullOr (types.submodule sslOpts);
+ default = null;
+ description = "Paths to SSL files";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional virtual host specific configuration";
+ };
+
+ };
+
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.prosody = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the prosody server";
+ };
+
+ package = mkOption {
+ type = types.package;
+ description = "Prosody package to use";
+ default = pkgs.prosody;
+ defaultText = "pkgs.prosody";
+ example = literalExample ''
+ pkgs.prosody.override {
+ withExtraLibs = [ pkgs.luaPackages.lpty ];
+ withCommunityModules = [ "auth_external" ];
+ };
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ description = "Directory where Prosody stores its data";
+ default = "/var/lib/prosody";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "prosody";
+ description = "User account under which prosody runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "prosody";
+ description = "Group account under which prosody runs.";
+ };
+
+ allowRegistration = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Allow account creation";
+ };
+
+ c2sRequireEncryption = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Force clients to use encrypted connections? This option will
+ prevent clients from authenticating unless they are using encryption.
+ '';
+ };
+
+ s2sRequireEncryption = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Force servers to use encrypted connections? This option will
+ prevent servers from authenticating unless they are using encryption.
+ Note that this is different from authentication.
+ '';
+ };
+
+ s2sSecureAuth = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Force certificate authentication for server-to-server connections?
+ This provides ideal security, but requires servers you communicate
+ with to support encryption AND present valid, trusted certificates.
+ For more information see https://prosody.im/doc/s2s#security
+ '';
+ };
+
+ s2sInsecureDomains = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "insecure.example.com" ];
+ description = ''
+ Some servers have invalid or self-signed certificates. You can list
+ remote domains here that will not be required to authenticate using
+ certificates. They will be authenticated using DNS instead, even
+ when s2s_secure_auth is enabled.
+ '';
+ };
+
+ s2sSecureDomains = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "jabber.org" ];
+ description = ''
+ Even if you leave s2s_secure_auth disabled, you can still require valid
+ certificates for some domains by specifying a list here.
+ '';
+ };
+
+
+ modules = moduleOpts;
+
+ extraModules = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Enable custom modules";
+ };
+
+ extraPluginPaths = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = "Addtional path in which to look find plugins/modules";
+ };
+
+ virtualHosts = mkOption {
+
+ description = "Define the virtual hosts";
+
+ type = with types; loaOf (submodule vHostOpts);
+
+ example = {
+ myhost = {
+ domain = "my-xmpp-example-host.org";
+ enabled = true;
+ };
+ };
+
+ default = {
+ localhost = {
+ domain = "localhost";
+ enabled = true;
+ };
+ };
+
+ };
+
+ ssl = mkOption {
+ type = types.nullOr (types.submodule sslOpts);
+ default = null;
+ description = "Paths to SSL files";
+ };
+
+ admins = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "admin1@example.com" "admin2@example.com" ];
+ description = "List of administrators of the current host";
+ };
+
+ authentication = mkOption {
+ type = types.enum [ "internal_plain" "internal_hashed" "cyrus" "anonymous" ];
+ default = "internal_hashed";
+ example = "internal_plain";
+ description = "Authentication mechanism used for logins.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional prosody configuration";
+ };
+
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ environment.etc."prosody/prosody.cfg.lua".text = ''
+
+ pidfile = "/run/prosody/prosody.pid"
+
+ log = "*syslog"
+
+ data_path = "${cfg.dataDir}"
+ plugin_paths = {
+ ${lib.concatStringsSep ", " (map (n: "\"${n}\"") cfg.extraPluginPaths) }
+ }
+
+ ${ optionalString (cfg.ssl != null) (createSSLOptsStr cfg.ssl) }
+
+ admins = ${toLua cfg.admins}
+
+ -- we already build with libevent, so we can just enable it for a more performant server
+ use_libevent = true
+
+ modules_enabled = {
+
+ ${ lib.concatStringsSep "\n " (lib.mapAttrsToList
+ (name: val: optionalString val "${toLua name};")
+ cfg.modules) }
+ ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.package.communityModules)}
+ ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)}
+ };
+
+ allow_registration = ${toLua cfg.allowRegistration}
+
+ c2s_require_encryption = ${toLua cfg.c2sRequireEncryption}
+
+ s2s_require_encryption = ${toLua cfg.s2sRequireEncryption}
+
+ s2s_secure_auth = ${toLua cfg.s2sSecureAuth}
+
+ s2s_insecure_domains = ${toLua cfg.s2sInsecureDomains}
+
+ s2s_secure_domains = ${toLua cfg.s2sSecureDomains}
+
+ authentication = ${toLua cfg.authentication}
+
+ ${ cfg.extraConfig }
+
+ ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
+ VirtualHost "${v.domain}"
+ enabled = ${boolToString v.enabled};
+ ${ optionalString (v.ssl != null) (createSSLOptsStr v.ssl) }
+ ${ v.extraConfig }
+ '') cfg.virtualHosts) }
+ '';
+
+ users.users.prosody = mkIf (cfg.user == "prosody") {
+ uid = config.ids.uids.prosody;
+ description = "Prosody user";
+ createHome = true;
+ inherit (cfg) group;
+ home = "${cfg.dataDir}";
+ };
+
+ users.groups.prosody = mkIf (cfg.group == "prosody") {
+ gid = config.ids.gids.prosody;
+ };
+
+ systemd.services.prosody = {
+ description = "Prosody XMPP server";
+ after = [ "network-online.target" ];
+ wants = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ config.environment.etc."prosody/prosody.cfg.lua".source ];
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ Type = "forking";
+ RuntimeDirectory = [ "prosody" ];
+ PIDFile = "/run/prosody/prosody.pid";
+ ExecStart = "${cfg.package}/bin/prosodyctl start";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/quagga.nix b/nixpkgs/nixos/modules/services/networking/quagga.nix
new file mode 100644
index 00000000000..5acdd5af8f8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/quagga.nix
@@ -0,0 +1,185 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.quagga;
+
+ services = [ "babel" "bgp" "isis" "ospf6" "ospf" "pim" "rip" "ripng" ];
+ allServices = services ++ [ "zebra" ];
+
+ isEnabled = service: cfg.${service}.enable;
+
+ daemonName = service: if service == "zebra" then service else "${service}d";
+
+ configFile = service:
+ let
+ scfg = cfg.${service};
+ in
+ if scfg.configFile != null then scfg.configFile
+ else pkgs.writeText "${daemonName service}.conf"
+ ''
+ ! Quagga ${daemonName service} configuration
+ !
+ hostname ${config.networking.hostName}
+ log syslog
+ service password-encryption
+ !
+ ${scfg.config}
+ !
+ end
+ '';
+
+ serviceOptions = service:
+ {
+ enable = mkEnableOption "the Quagga ${toUpper service} routing protocol";
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/etc/quagga/${daemonName service}.conf";
+ description = ''
+ Configuration file to use for Quagga ${daemonName service}.
+ By default the NixOS generated files are used.
+ '';
+ };
+
+ config = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ let
+ examples = {
+ rip = ''
+ router rip
+ network 10.0.0.0/8
+ '';
+
+ ospf = ''
+ router ospf
+ network 10.0.0.0/8 area 0
+ '';
+
+ bgp = ''
+ router bgp 65001
+ neighbor 10.0.0.1 remote-as 65001
+ '';
+ };
+ in
+ examples.${service} or "";
+ description = ''
+ ${daemonName service} configuration statements.
+ '';
+ };
+
+ vtyListenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = ''
+ Address to bind to for the VTY interface.
+ '';
+ };
+
+ vtyListenPort = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ TCP Port to bind to for the VTY interface.
+ '';
+ };
+ };
+
+in
+
+{
+
+ ###### interface
+ imports = [
+ {
+ options.services.quagga = {
+ zebra = (serviceOptions "zebra") // {
+ enable = mkOption {
+ type = types.bool;
+ default = any isEnabled services;
+ description = ''
+ Whether to enable the Zebra routing manager.
+
+ The Zebra routing manager is automatically enabled
+ if any routing protocols are configured.
+ '';
+ };
+ };
+ };
+ }
+ { options.services.quagga = (genAttrs services serviceOptions); }
+ ];
+
+ ###### implementation
+
+ config = mkIf (any isEnabled allServices) {
+
+ environment.systemPackages = [
+ pkgs.quagga # for the vtysh tool
+ ];
+
+ users.users.quagga = {
+ description = "Quagga daemon user";
+ isSystemUser = true;
+ group = "quagga";
+ };
+
+ users.groups = {
+ quagga = {};
+ # Members of the quaggavty group can use vtysh to inspect the Quagga daemons
+ quaggavty = { members = [ "quagga" ]; };
+ };
+
+ systemd.services =
+ let
+ quaggaService = service:
+ let
+ scfg = cfg.${service};
+ daemon = daemonName service;
+ in
+ nameValuePair daemon ({
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ (configFile service) ];
+
+ serviceConfig = {
+ Type = "forking";
+ PIDFile = "/run/quagga/${daemon}.pid";
+ ExecStart = "@${pkgs.quagga}/libexec/quagga/${daemon} ${daemon} -d -f ${configFile service}"
+ + optionalString (scfg.vtyListenAddress != "") " -A ${scfg.vtyListenAddress}"
+ + optionalString (scfg.vtyListenPort != null) " -P ${toString scfg.vtyListenPort}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ Restart = "on-abort";
+ };
+ } // (
+ if service == "zebra" then
+ {
+ description = "Quagga Zebra routing manager";
+ unitConfig.Documentation = "man:zebra(8)";
+ after = [ "network.target" ];
+ preStart = ''
+ install -m 0755 -o quagga -g quagga -d /run/quagga
+
+ ${pkgs.iproute}/bin/ip route flush proto zebra
+ '';
+ }
+ else
+ {
+ description = "Quagga ${toUpper service} routing daemon";
+ unitConfig.Documentation = "man:${daemon}(8) man:zebra(8)";
+ bindsTo = [ "zebra.service" ];
+ after = [ "network.target" "zebra.service" ];
+ }
+ ));
+ in
+ listToAttrs (map quaggaService (filter isEnabled allServices));
+
+ };
+
+ meta.maintainers = with lib.maintainers; [ tavyc ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/quassel.nix b/nixpkgs/nixos/modules/services/networking/quassel.nix
new file mode 100644
index 00000000000..b495b3948fb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/quassel.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.quassel;
+ quassel = cfg.package;
+ user = if cfg.user != null then cfg.user else "quassel";
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.quassel = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to run the Quassel IRC client daemon.
+ '';
+ };
+
+ certificateFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Path to the certificate used for SSL connections with clients.
+ '';
+ };
+
+ requireSSL = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Require SSL for connections from clients.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.quasselDaemon;
+ defaultText = "pkgs.quasselDaemon";
+ description = ''
+ The package of the quassel daemon.
+ '';
+ example = literalExample "pkgs.quasselDaemon";
+ };
+
+ interfaces = mkOption {
+ default = [ "127.0.0.1" ];
+ description = ''
+ The interfaces the Quassel daemon will be listening to. If `[ 127.0.0.1 ]',
+ only clients on the local host can connect to it; if `[ 0.0.0.0 ]', clients
+ can access it from any network interface.
+ '';
+ };
+
+ portNumber = mkOption {
+ default = 4242;
+ description = ''
+ The port number the Quassel daemon will be listening to.
+ '';
+ };
+
+ dataDir = mkOption {
+ default = ''/home/${user}/.config/quassel-irc.org'';
+ description = ''
+ The directory holding configuration files, the SQlite database and the SSL Cert.
+ '';
+ };
+
+ user = mkOption {
+ default = null;
+ description = ''
+ The existing user the Quassel daemon should run as. If left empty, a default "quassel" user will be created.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ assertions = [
+ { assertion = cfg.requireSSL -> cfg.certificateFile != null;
+ message = "Quassel needs a certificate file in order to require SSL";
+ }];
+
+ users.users = mkIf (cfg.user == null) [
+ { name = "quassel";
+ description = "Quassel IRC client daemon";
+ group = "quassel";
+ uid = config.ids.uids.quassel;
+ }];
+
+ users.groups = mkIf (cfg.user == null) [
+ { name = "quassel";
+ gid = config.ids.gids.quassel;
+ }];
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - ${user} - - -"
+ ];
+
+ systemd.services.quassel =
+ { description = "Quassel IRC client daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ] ++ optional config.services.postgresql.enable "postgresql.service"
+ ++ optional config.services.mysql.enable "mysql.service";
+
+ serviceConfig =
+ {
+ ExecStart = concatStringsSep " " ([
+ "${quassel}/bin/quasselcore"
+ "--listen=${concatStringsSep "," cfg.interfaces}"
+ "--port=${toString cfg.portNumber}"
+ "--configdir=${cfg.dataDir}"
+ ] ++ optional cfg.requireSSL "--require-ssl"
+ ++ optional (cfg.certificateFile != null) "--ssl-cert=${cfg.certificateFile}");
+ User = user;
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/quicktun.nix b/nixpkgs/nixos/modules/services/networking/quicktun.nix
new file mode 100644
index 00000000000..fb783c83646
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/quicktun.nix
@@ -0,0 +1,118 @@
+{ config, pkgs, lib, ... }:
+
+let
+
+ cfg = config.services.quicktun;
+
+in
+
+with lib;
+
+{
+ options = {
+
+ services.quicktun = mkOption {
+ default = { };
+ description = "QuickTun tunnels";
+ type = types.attrsOf (types.submodule {
+ options = {
+ tunMode = mkOption {
+ type = types.int;
+ default = 0;
+ example = 1;
+ description = "";
+ };
+
+ remoteAddress = mkOption {
+ type = types.str;
+ example = "tunnel.example.com";
+ description = "";
+ };
+
+ localAddress = mkOption {
+ type = types.str;
+ example = "0.0.0.0";
+ description = "";
+ };
+
+ localPort = mkOption {
+ type = types.int;
+ default = 2998;
+ description = "";
+ };
+
+ remotePort = mkOption {
+ type = types.int;
+ default = 2998;
+ description = "";
+ };
+
+ remoteFloat = mkOption {
+ type = types.int;
+ default = 0;
+ description = "";
+ };
+
+ protocol = mkOption {
+ type = types.str;
+ default = "nacltai";
+ description = "";
+ };
+
+ privateKey = mkOption {
+ type = types.str;
+ description = "";
+ };
+
+ publicKey = mkOption {
+ type = types.str;
+ description = "";
+ };
+
+ timeWindow = mkOption {
+ type = types.int;
+ default = 5;
+ description = "";
+ };
+
+ upScript = mkOption {
+ type = types.lines;
+ default = "";
+ description = "";
+ };
+ };
+ });
+ };
+
+ };
+
+ config = mkIf (cfg != []) {
+ systemd.services = fold (a: b: a // b) {} (
+ mapAttrsToList (name: qtcfg: {
+ "quicktun-${name}" = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment = {
+ INTERFACE = name;
+ TUN_MODE = toString qtcfg.tunMode;
+ REMOTE_ADDRESS = qtcfg.remoteAddress;
+ LOCAL_ADDRESS = qtcfg.localAddress;
+ LOCAL_PORT = toString qtcfg.localPort;
+ REMOTE_PORT = toString qtcfg.remotePort;
+ REMOTE_FLOAT = toString qtcfg.remoteFloat;
+ PRIVATE_KEY = qtcfg.privateKey;
+ PUBLIC_KEY = qtcfg.publicKey;
+ TIME_WINDOW = toString qtcfg.timeWindow;
+ TUN_UP_SCRIPT = pkgs.writeScript "quicktun-${name}-up.sh" qtcfg.upScript;
+ SUID = "nobody";
+ };
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${pkgs.quicktun}/bin/quicktun.${qtcfg.protocol}";
+ };
+ };
+ }) cfg
+ );
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/racoon.nix b/nixpkgs/nixos/modules/services/networking/racoon.nix
new file mode 100644
index 00000000000..328f4cb1497
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/racoon.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.racoon;
+in {
+ options.services.racoon = {
+ enable = mkEnableOption "racoon";
+
+ config = mkOption {
+ description = "Contents of racoon configuration file.";
+ default = "";
+ type = types.str;
+ };
+
+ configPath = mkOption {
+ description = "Location of racoon config if config is not provided.";
+ default = "/etc/racoon/racoon.conf";
+ type = types.path;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.racoon = {
+ description = "Racoon Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.ipsecTools}/bin/racoon -f ${
+ if (cfg.config != "") then pkgs.writeText "racoon.conf" cfg.config
+ else cfg.configPath
+ }";
+ ExecReload = "${pkgs.ipsecTools}/bin/racoonctl reload-config";
+ PIDFile = "/run/racoon.pid";
+ Type = "forking";
+ Restart = "always";
+ };
+ preStart = ''
+ rm /run/racoon.pid || true
+ mkdir -p /var/racoon
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/radicale.nix b/nixpkgs/nixos/modules/services/networking/radicale.nix
new file mode 100644
index 00000000000..1daced4a6c7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/radicale.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.radicale;
+
+ confFile = pkgs.writeText "radicale.conf" cfg.config;
+
+ # This enables us to default to version 2 while still not breaking configurations of people with version 1
+ defaultPackage = if versionAtLeast config.system.stateVersion "17.09" then {
+ pkg = pkgs.radicale2;
+ text = "pkgs.radicale2";
+ } else {
+ pkg = pkgs.radicale1;
+ text = "pkgs.radicale1";
+ };
+in
+
+{
+
+ options = {
+ services.radicale.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable Radicale CalDAV and CardDAV server.
+ '';
+ };
+
+ services.radicale.package = mkOption {
+ type = types.package;
+ default = defaultPackage.pkg;
+ defaultText = defaultPackage.text;
+ description = ''
+ Radicale package to use. This defaults to version 1.x if
+ <literal>system.stateVersion &lt; 17.09</literal> and version 2.x
+ otherwise.
+ '';
+ };
+
+ services.radicale.config = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Radicale configuration, this will set the service
+ configuration file.
+ '';
+ };
+
+ services.radicale.extraArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Extra arguments passed to the Radicale daemon.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ cfg.package ];
+
+ users.users = singleton
+ { name = "radicale";
+ uid = config.ids.uids.radicale;
+ description = "radicale user";
+ home = "/var/lib/radicale";
+ createHome = true;
+ };
+
+ users.groups = singleton
+ { name = "radicale";
+ gid = config.ids.gids.radicale;
+ };
+
+ systemd.services.radicale = {
+ description = "A Simple Calendar and Contact Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = concatStringsSep " " ([
+ "${cfg.package}/bin/radicale" "-C" confFile
+ ] ++ (
+ map escapeShellArg cfg.extraArgs
+ ));
+ User = "radicale";
+ Group = "radicale";
+ };
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ aneeshusa infinisil ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/radvd.nix b/nixpkgs/nixos/modules/services/networking/radvd.nix
new file mode 100644
index 00000000000..020faa34922
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/radvd.nix
@@ -0,0 +1,72 @@
+# Module for the IPv6 Router Advertisement Daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.radvd;
+
+ confFile = pkgs.writeText "radvd.conf" cfg.config;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.radvd.enable = mkOption {
+ default = false;
+ description =
+ ''
+ Whether to enable the Router Advertisement Daemon
+ (<command>radvd</command>), which provides link-local
+ advertisements of IPv6 router addresses and prefixes using
+ the Neighbor Discovery Protocol (NDP). This enables
+ stateless address autoconfiguration in IPv6 clients on the
+ network.
+ '';
+ };
+
+ services.radvd.config = mkOption {
+ example =
+ ''
+ interface eth0 {
+ AdvSendAdvert on;
+ prefix 2001:db8:1234:5678::/64 { };
+ };
+ '';
+ description =
+ ''
+ The contents of the radvd configuration file.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users.radvd =
+ { uid = config.ids.uids.radvd;
+ description = "Router Advertisement Daemon User";
+ };
+
+ systemd.services.radvd =
+ { description = "IPv6 Router Advertisement Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig =
+ { ExecStart = "@${pkgs.radvd}/bin/radvd radvd -n -u radvd -C ${confFile}";
+ Restart = "always";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/rdnssd.nix b/nixpkgs/nixos/modules/services/networking/rdnssd.nix
new file mode 100644
index 00000000000..bccab805bee
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/rdnssd.nix
@@ -0,0 +1,79 @@
+# Module for rdnssd, a daemon that configures DNS servers in
+# /etc/resolv/conf from IPv6 RDNSS advertisements.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ mergeHook = pkgs.writeScript "rdnssd-merge-hook" ''
+ #! ${pkgs.runtimeShell} -e
+ ${pkgs.openresolv}/bin/resolvconf -u
+ '';
+in
+{
+
+ ###### interface
+
+ options = {
+
+ services.rdnssd.enable = mkOption {
+ default = false;
+ #default = config.networking.enableIPv6;
+ description =
+ ''
+ Whether to enable the RDNSS daemon
+ (<command>rdnssd</command>), which configures DNS servers in
+ <filename>/etc/resolv.conf</filename> from RDNSS
+ advertisements sent by IPv6 routers.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.rdnssd.enable {
+
+ assertions = [{
+ assertion = config.networking.resolvconf.enable;
+ message = "rdnssd needs resolvconf to work (probably something sets up a static resolv.conf)";
+ }];
+
+ systemd.services.rdnssd = {
+ description = "RDNSS daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ # Create the proper run directory
+ mkdir -p /run/rdnssd
+ touch /run/rdnssd/resolv.conf
+ chown -R rdnssd /run/rdnssd
+
+ # Link the resolvconf interfaces to rdnssd
+ rm -f /run/resolvconf/interfaces/rdnssd
+ ln -s /run/rdnssd/resolv.conf /run/resolvconf/interfaces/rdnssd
+ ${mergeHook}
+ '';
+
+ postStop = ''
+ rm -f /run/resolvconf/interfaces/rdnssd
+ ${mergeHook}
+ '';
+
+ serviceConfig = {
+ ExecStart = "@${pkgs.ndisc6}/bin/rdnssd rdnssd -p /run/rdnssd/rdnssd.pid -r /run/rdnssd/resolv.conf -u rdnssd -H ${mergeHook}";
+ Type = "forking";
+ PIDFile = "/run/rdnssd/rdnssd.pid";
+ };
+ };
+
+ users.users.rdnssd = {
+ description = "RDNSSD Daemon User";
+ uid = config.ids.uids.rdnssd;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/redsocks.nix b/nixpkgs/nixos/modules/services/networking/redsocks.nix
new file mode 100644
index 00000000000..8481f9debf3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/redsocks.nix
@@ -0,0 +1,272 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.redsocks;
+in
+{
+ ##### interface
+ options = {
+ services.redsocks = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable redsocks.";
+ };
+
+ log_debug = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Log connection progress.";
+ };
+
+ log_info = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Log start and end of client sessions.";
+ };
+
+ log = mkOption {
+ type = types.str;
+ default = "stderr";
+ description =
+ ''
+ Where to send logs.
+
+ Possible values are:
+ - stderr
+ - file:/path/to/file
+ - syslog:FACILITY where FACILITY is any of "daemon", "local0",
+ etc.
+ '';
+ };
+
+ chroot = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description =
+ ''
+ Chroot under which to run redsocks. Log file is opened before
+ chroot, but if logging to syslog /etc/localtime may be required.
+ '';
+ };
+
+ redsocks = mkOption {
+ description =
+ ''
+ Local port to proxy associations to be performed.
+
+ The example shows how to configure a proxy to handle port 80 as HTTP
+ relay, and all other ports as HTTP connect.
+ '';
+ example = [
+ { port = 23456; proxy = "1.2.3.4:8080"; type = "http-relay";
+ redirectCondition = "--dport 80";
+ doNotRedirect = [ "-d 1.2.0.0/16" ];
+ }
+ { port = 23457; proxy = "1.2.3.4:8080"; type = "http-connect";
+ redirectCondition = true;
+ doNotRedirect = [ "-d 1.2.0.0/16" ];
+ }
+ ];
+ type = types.listOf (types.submodule { options = {
+ ip = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description =
+ ''
+ IP on which redsocks should listen. Defaults to 127.0.0.1 for
+ security reasons.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 12345;
+ description = "Port on which redsocks should listen.";
+ };
+
+ proxy = mkOption {
+ type = types.str;
+ description =
+ ''
+ Proxy through which redsocks should forward incoming traffic.
+ Example: "example.org:8080"
+ '';
+ };
+
+ type = mkOption {
+ type = types.enum [ "socks4" "socks5" "http-connect" "http-relay" ];
+ description = "Type of proxy.";
+ };
+
+ login = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "Login to send to proxy.";
+ };
+
+ password = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description =
+ ''
+ Password to send to proxy. WARNING, this will end up
+ world-readable in the store! Awaiting
+ https://github.com/NixOS/nix/issues/8 to be able to fix.
+ '';
+ };
+
+ disclose_src = mkOption {
+ type = types.enum [ "false" "X-Forwarded-For" "Forwarded_ip"
+ "Forwarded_ipport" ];
+ default = "false";
+ description =
+ ''
+ Way to disclose client IP to the proxy.
+ - "false": do not disclose
+ http-connect supports the following ways:
+ - "X-Forwarded-For": add header "X-Forwarded-For: IP"
+ - "Forwarded_ip": add header "Forwarded: for=IP" (see RFC7239)
+ - "Forwarded_ipport": add header 'Forwarded: for="IP:port"'
+ '';
+ };
+
+ redirectInternetOnly = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Exclude all non-globally-routable IPs from redsocks";
+ };
+
+ doNotRedirect = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description =
+ ''
+ Iptables filters that if matched will get the packet off of
+ redsocks.
+ '';
+ example = [ "-d 1.2.3.4" ];
+ };
+
+ redirectCondition = mkOption {
+ type = with types; either bool str;
+ default = false;
+ description =
+ ''
+ Conditions to make outbound packets go through this redsocks
+ instance.
+
+ If set to false, no packet will be forwarded. If set to true,
+ all packets will be forwarded (except packets excluded by
+ redirectInternetOnly).
+
+ If set to a string, this is an iptables filter that will be
+ matched against packets before getting them into redsocks. For
+ example, setting it to "--dport 80" will only send
+ packets to port 80 to redsocks. Note "-p tcp" is always
+ implicitly added, as udp can only be proxied through redudp or
+ the like.
+ '';
+ };
+ };});
+ };
+
+ # TODO: Add support for redudp and dnstc
+ };
+ };
+
+ ##### implementation
+ config = let
+ redsocks_blocks = concatMapStrings (block:
+ let proxy = splitString ":" block.proxy; in
+ ''
+ redsocks {
+ local_ip = ${block.ip};
+ local_port = ${toString block.port};
+
+ ip = ${elemAt proxy 0};
+ port = ${elemAt proxy 1};
+ type = ${block.type};
+
+ ${optionalString (block.login != null) "login = \"${block.login}\";"}
+ ${optionalString (block.password != null) "password = \"${block.password}\";"}
+
+ disclose_src = ${block.disclose_src};
+ }
+ '') cfg.redsocks;
+ configfile = pkgs.writeText "redsocks.conf"
+ ''
+ base {
+ log_debug = ${if cfg.log_debug then "on" else "off" };
+ log_info = ${if cfg.log_info then "on" else "off" };
+ log = ${cfg.log};
+
+ daemon = off;
+ redirector = iptables;
+
+ user = redsocks;
+ group = redsocks;
+ ${optionalString (cfg.chroot != null) "chroot = ${cfg.chroot};"}
+ }
+
+ ${redsocks_blocks}
+ '';
+ internetOnly = [ # TODO: add ipv6-equivalent
+ "-d 0.0.0.0/8"
+ "-d 10.0.0.0/8"
+ "-d 127.0.0.0/8"
+ "-d 169.254.0.0/16"
+ "-d 172.16.0.0/12"
+ "-d 192.168.0.0/16"
+ "-d 224.168.0.0/4"
+ "-d 240.168.0.0/4"
+ ];
+ redCond = block:
+ optionalString (isString block.redirectCondition) block.redirectCondition;
+ iptables = concatImapStrings (idx: block:
+ let chain = "REDSOCKS${toString idx}"; doNotRedirect =
+ concatMapStringsSep "\n"
+ (f: "ip46tables -t nat -A ${chain} ${f} -j RETURN 2>/dev/null || true")
+ (block.doNotRedirect ++ (optionals block.redirectInternetOnly internetOnly));
+ in
+ optionalString (block.redirectCondition != false)
+ ''
+ ip46tables -t nat -F ${chain} 2>/dev/null || true
+ ip46tables -t nat -N ${chain} 2>/dev/null || true
+ ${doNotRedirect}
+ ip46tables -t nat -A ${chain} -p tcp -j REDIRECT --to-ports ${toString block.port}
+
+ # TODO: show errors, when it will be easily possible by a switch to
+ # iptables-restore
+ ip46tables -t nat -A OUTPUT -p tcp ${redCond block} -j ${chain} 2>/dev/null || true
+ ''
+ ) cfg.redsocks;
+ in
+ mkIf cfg.enable {
+ users.groups.redsocks = {};
+ users.users.redsocks = {
+ description = "Redsocks daemon";
+ group = "redsocks";
+ isSystemUser = true;
+ };
+
+ systemd.services.redsocks = {
+ description = "Redsocks";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ script = "${pkgs.redsocks}/bin/redsocks -c ${configfile}";
+ };
+
+ networking.firewall.extraCommands = iptables;
+
+ networking.firewall.extraStopCommands =
+ concatImapStringsSep "\n" (idx: block:
+ let chain = "REDSOCKS${toString idx}"; in
+ optionalString (block.redirectCondition != false)
+ "ip46tables -t nat -D OUTPUT -p tcp ${redCond block} -j ${chain} 2>/dev/null || true"
+ ) cfg.redsocks;
+ };
+
+ meta.maintainers = with lib.maintainers; [ ekleog ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/resilio.nix b/nixpkgs/nixos/modules/services/networking/resilio.nix
new file mode 100644
index 00000000000..9b25aa57583
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/resilio.nix
@@ -0,0 +1,263 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.resilio;
+
+ resilioSync = pkgs.resilio-sync;
+
+ sharedFoldersRecord = map (entry: {
+ secret = entry.secret;
+ dir = entry.directory;
+
+ use_relay_server = entry.useRelayServer;
+ use_tracker = entry.useTracker;
+ use_dht = entry.useDHT;
+
+ search_lan = entry.searchLAN;
+ use_sync_trash = entry.useSyncTrash;
+ known_hosts = entry.knownHosts;
+ }) cfg.sharedFolders;
+
+ configFile = pkgs.writeText "config.json" (builtins.toJSON ({
+ device_name = cfg.deviceName;
+ storage_path = cfg.storagePath;
+ listening_port = cfg.listeningPort;
+ use_gui = false;
+ check_for_updates = cfg.checkForUpdates;
+ use_upnp = cfg.useUpnp;
+ download_limit = cfg.downloadLimit;
+ upload_limit = cfg.uploadLimit;
+ lan_encrypt_data = cfg.encryptLAN;
+ } // optionalAttrs cfg.enableWebUI {
+ webui = { listen = "${cfg.httpListenAddr}:${toString cfg.httpListenPort}"; } //
+ (optionalAttrs (cfg.httpLogin != "") { login = cfg.httpLogin; }) //
+ (optionalAttrs (cfg.httpPass != "") { password = cfg.httpPass; }) //
+ (optionalAttrs (cfg.apiKey != "") { api_key = cfg.apiKey; }) //
+ (optionalAttrs (cfg.directoryRoot != "") { directory_root = cfg.directoryRoot; });
+ } // optionalAttrs (sharedFoldersRecord != []) {
+ shared_folders = sharedFoldersRecord;
+ }));
+
+in
+{
+ options = {
+ services.resilio = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, start the Resilio Sync daemon. Once enabled, you can
+ interact with the service through the Web UI, or configure it in your
+ NixOS configuration.
+ '';
+ };
+
+ deviceName = mkOption {
+ type = types.str;
+ example = "Voltron";
+ default = config.networking.hostName;
+ description = ''
+ Name of the Resilio Sync device.
+ '';
+ };
+
+ listeningPort = mkOption {
+ type = types.int;
+ default = 0;
+ example = 44444;
+ description = ''
+ Listening port. Defaults to 0 which randomizes the port.
+ '';
+ };
+
+ checkForUpdates = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Determines whether to check for updates and alert the user
+ about them in the UI.
+ '';
+ };
+
+ useUpnp = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Use Universal Plug-n-Play (UPnP)
+ '';
+ };
+
+ downloadLimit = mkOption {
+ type = types.int;
+ default = 0;
+ example = 1024;
+ description = ''
+ Download speed limit. 0 is unlimited (default).
+ '';
+ };
+
+ uploadLimit = mkOption {
+ type = types.int;
+ default = 0;
+ example = 1024;
+ description = ''
+ Upload speed limit. 0 is unlimited (default).
+ '';
+ };
+
+ httpListenAddr = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ example = "1.2.3.4";
+ description = ''
+ HTTP address to bind to.
+ '';
+ };
+
+ httpListenPort = mkOption {
+ type = types.int;
+ default = 9000;
+ description = ''
+ HTTP port to bind on.
+ '';
+ };
+
+ httpLogin = mkOption {
+ type = types.str;
+ example = "allyourbase";
+ default = "";
+ description = ''
+ HTTP web login username.
+ '';
+ };
+
+ httpPass = mkOption {
+ type = types.str;
+ example = "arebelongtous";
+ default = "";
+ description = ''
+ HTTP web login password.
+ '';
+ };
+
+ encryptLAN = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Encrypt LAN data.";
+ };
+
+ enableWebUI = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable Web UI for administration. Bound to the specified
+ <literal>httpListenAddress</literal> and
+ <literal>httpListenPort</literal>.
+ '';
+ };
+
+ storagePath = mkOption {
+ type = types.path;
+ default = "/var/lib/resilio-sync/";
+ description = ''
+ Where BitTorrent Sync will store it's database files (containing
+ things like username info and licenses). Generally, you should not
+ need to ever change this.
+ '';
+ };
+
+ apiKey = mkOption {
+ type = types.str;
+ default = "";
+ description = "API key, which enables the developer API.";
+ };
+
+ directoryRoot = mkOption {
+ type = types.str;
+ default = "";
+ example = "/media";
+ description = "Default directory to add folders in the web UI.";
+ };
+
+ sharedFolders = mkOption {
+ default = [];
+ example =
+ [ { secret = "AHMYFPCQAHBM7LQPFXQ7WV6Y42IGUXJ5Y";
+ directory = "/home/user/sync_test";
+ useRelayServer = true;
+ useTracker = true;
+ useDHT = false;
+ searchLAN = true;
+ useSyncTrash = true;
+ knownHosts = [
+ "192.168.1.2:4444"
+ "192.168.1.3:4444"
+ ];
+ }
+ ];
+ description = ''
+ Shared folder list. If enabled, web UI must be
+ disabled. Secrets can be generated using <literal>rslsync
+ --generate-secret</literal>. Note that this secret will be
+ put inside the Nix store, so it is realistically not very
+ secret.
+
+ If you would like to be able to modify the contents of this
+ directories, it is recommended that you make your user a
+ member of the <literal>resilio</literal> group.
+
+ Directories in this list should be in the
+ <literal>resilio</literal> group, and that group must have
+ write access to the directory. It is also recommended that
+ <literal>chmod g+s</literal> is applied to the directory
+ so that any sub directories created will also belong to
+ the <literal>resilio</literal> group. Also,
+ <literal>setfacl -d -m group:resilio:rwx</literal> and
+ <literal>setfacl -m group:resilio:rwx</literal> should also
+ be applied so that the sub directories are writable by
+ the group.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions =
+ [ { assertion = cfg.deviceName != "";
+ message = "Device name cannot be empty.";
+ }
+ { assertion = cfg.enableWebUI -> cfg.sharedFolders == [];
+ message = "If using shared folders, the web UI cannot be enabled.";
+ }
+ { assertion = cfg.apiKey != "" -> cfg.enableWebUI;
+ message = "If you're using an API key, you must enable the web server.";
+ }
+ ];
+
+ users.users.rslsync = {
+ description = "Resilio Sync Service user";
+ home = cfg.storagePath;
+ createHome = true;
+ uid = config.ids.uids.rslsync;
+ group = "rslsync";
+ };
+
+ users.groups = [ { name = "rslsync"; } ];
+
+ systemd.services.resilio = with pkgs; {
+ description = "Resilio Sync Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ Restart = "on-abort";
+ UMask = "0002";
+ User = "rslsync";
+ ExecStart = ''
+ ${resilioSync}/bin/rslsync --nodaemon --config ${configFile}
+ '';
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/rpcbind.nix b/nixpkgs/nixos/modules/services/networking/rpcbind.nix
new file mode 100644
index 00000000000..0a5df698709
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/rpcbind.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.rpcbind = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable `rpcbind', an ONC RPC directory service
+ notably used by NFS and NIS, and which can be queried
+ using the rpcinfo(1) command. `rpcbind` is a replacement for
+ `portmap`.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.rpcbind.enable {
+ environment.systemPackages = [ pkgs.rpcbind ];
+
+ systemd.packages = [ pkgs.rpcbind ];
+
+ systemd.services.rpcbind = {
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ users.users.rpc = {
+ group = "nogroup";
+ uid = config.ids.uids.rpc;
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/rxe.nix b/nixpkgs/nixos/modules/services/networking/rxe.nix
new file mode 100644
index 00000000000..a6a069ec50c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/rxe.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.networking.rxe;
+
+ runRxeCmd = cmd: ifcs:
+ concatStrings ( map (x: "${pkgs.rdma-core}/bin/rxe_cfg -n ${cmd} ${x};") ifcs);
+
+ startScript = pkgs.writeShellScriptBin "rxe-start" ''
+ ${pkgs.rdma-core}/bin/rxe_cfg -n start
+ ${runRxeCmd "add" cfg.interfaces}
+ ${pkgs.rdma-core}/bin/rxe_cfg
+ '';
+
+ stopScript = pkgs.writeShellScriptBin "rxe-stop" ''
+ ${runRxeCmd "remove" cfg.interfaces }
+ ${pkgs.rdma-core}/bin/rxe_cfg -n stop
+ '';
+
+in {
+ ###### interface
+
+ options = {
+ networking.rxe = {
+ enable = mkEnableOption "RDMA over converged ethernet";
+ interfaces = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "eth0" ];
+ description = ''
+ Enable RDMA on the listed interfaces. The corresponding virtual
+ RDMA interfaces will be named rxe0 ... rxeN where the ordering
+ will be as they are named in the list. UDP port 4791 must be
+ open on the respective ethernet interfaces.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.rxe = {
+ path = with pkgs; [ kmod rdma-core ];
+ description = "RoCE interfaces";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "systemd-modules-load.service" "network-online.target" ];
+ wants = [ "network-pre.target" ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${startScript}/bin/rxe-start";
+ ExecStop = "${stopScript}/bin/rxe-stop";
+ };
+ };
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/sabnzbd.nix b/nixpkgs/nixos/modules/services/networking/sabnzbd.nix
new file mode 100644
index 00000000000..62b24d4377f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/sabnzbd.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.sabnzbd;
+ inherit (pkgs) sabnzbd;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.sabnzbd = {
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable the sabnzbd server.";
+ };
+ configFile = mkOption {
+ default = "/var/lib/sabnzbd/sabnzbd.ini";
+ description = "Path to config file.";
+ };
+
+ user = mkOption {
+ default = "sabnzbd";
+ description = "User to run the service as";
+ };
+
+ group = mkOption {
+ default = "sabnzbd";
+ description = "Group to run the service as";
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users.sabnzbd = {
+ uid = config.ids.uids.sabnzbd;
+ group = "sabnzbd";
+ description = "sabnzbd user";
+ home = "/var/lib/sabnzbd/";
+ createHome = true;
+ };
+
+ users.groups.sabnzbd = {
+ gid = config.ids.gids.sabnzbd;
+ };
+
+ systemd.services.sabnzbd = {
+ description = "sabnzbd server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ Type = "forking";
+ GuessMainPID = "no";
+ User = "${cfg.user}";
+ Group = "${cfg.group}";
+ ExecStart = "${sabnzbd}/bin/sabnzbd -d -f ${cfg.configFile}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/searx.nix b/nixpkgs/nixos/modules/services/networking/searx.nix
new file mode 100644
index 00000000000..9412d0ef8a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/searx.nix
@@ -0,0 +1,78 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.searx;
+
+ configFile = cfg.configFile;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.searx = {
+
+ enable = mkEnableOption
+ "the searx server. See https://github.com/asciimoo/searx";
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "
+ The path of the Searx server configuration file. If no file
+ is specified, a default file is used (default config file has
+ debug mode enabled).
+ ";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.searx;
+ defaultText = "pkgs.searx";
+ description = "searx package to use.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.searx.enable {
+
+ users.users.searx =
+ { uid = config.ids.uids.searx;
+ description = "Searx user";
+ createHome = true;
+ home = "/var/lib/searx";
+ };
+
+ users.groups.searx =
+ { gid = config.ids.gids.searx;
+ };
+
+ systemd.services.searx =
+ {
+ description = "Searx server, the meta search engine.";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "searx";
+ ExecStart = "${cfg.package}/bin/searx-run";
+ };
+ } // (optionalAttrs (configFile != null) {
+ environment.SEARX_SETTINGS_PATH = configFile;
+ });
+
+ environment.systemPackages = [ cfg.package ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/seeks.nix b/nixpkgs/nixos/modules/services/networking/seeks.nix
new file mode 100644
index 00000000000..40729225b6d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/seeks.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.seeks;
+
+ confDir = cfg.confDir;
+
+ seeks = pkgs.seeks.override { seeks_confDir = confDir; };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.seeks = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "
+ Whether to enable the Seeks server.
+ ";
+ };
+
+ confDir = mkOption {
+ default = "";
+ type = types.str;
+ description = "
+ The Seeks server configuration. If it is not specified,
+ a default configuration is used.
+ ";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.seeks.enable {
+
+ users.users.seeks =
+ { uid = config.ids.uids.seeks;
+ description = "Seeks user";
+ createHome = true;
+ home = "/var/lib/seeks";
+ };
+
+ users.groups.seeks =
+ { gid = config.ids.gids.seeks;
+ };
+
+ systemd.services.seeks =
+ {
+ description = "Seeks server, the p2p search engine.";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "seeks";
+ ExecStart = "${seeks}/bin/seeks";
+ };
+ };
+
+ environment.systemPackages = [ seeks ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/shadowsocks.nix b/nixpkgs/nixos/modules/services/networking/shadowsocks.nix
new file mode 100644
index 00000000000..af12db590f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/shadowsocks.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.shadowsocks;
+
+ opts = {
+ server = cfg.localAddress;
+ server_port = cfg.port;
+ method = cfg.encryptionMethod;
+ mode = cfg.mode;
+ user = "nobody";
+ fast_open = true;
+ } // optionalAttrs (cfg.password != null) { password = cfg.password; };
+
+ configFile = pkgs.writeText "shadowsocks.json" (builtins.toJSON opts);
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.shadowsocks = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run shadowsocks-libev shadowsocks server.
+ '';
+ };
+
+ localAddress = mkOption {
+ type = types.coercedTo types.str singleton (types.listOf types.str);
+ default = [ "[::0]" "0.0.0.0" ];
+ description = ''
+ Local addresses to which the server binds.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8388;
+ description = ''
+ Port which the server uses.
+ '';
+ };
+
+ password = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Password for connecting clients.
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Password file with a password for connecting clients.
+ '';
+ };
+
+ mode = mkOption {
+ type = types.enum [ "tcp_only" "tcp_and_udp" "udp_only" ];
+ default = "tcp_and_udp";
+ description = ''
+ Relay protocols.
+ '';
+ };
+
+ encryptionMethod = mkOption {
+ type = types.str;
+ default = "chacha20-ietf-poly1305";
+ description = ''
+ Encryption method. See <link xlink:href="https://github.com/shadowsocks/shadowsocks-org/wiki/AEAD-Ciphers"/>.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ assertions = singleton
+ { assertion = cfg.password == null || cfg.passwordFile == null;
+ message = "Cannot use both password and passwordFile for shadowsocks-libev";
+ };
+
+ systemd.services.shadowsocks-libev = {
+ description = "shadowsocks-libev Daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.shadowsocks-libev ] ++ optional (cfg.passwordFile != null) pkgs.jq;
+ serviceConfig.PrivateTmp = true;
+ script = ''
+ ${optionalString (cfg.passwordFile != null) ''
+ cat ${configFile} | jq --arg password "$(cat "${cfg.passwordFile}")" '. + { password: $password }' > /tmp/shadowsocks.json
+ ''}
+ exec ss-server -c ${if cfg.passwordFile != null then "/tmp/shadowsocks.json" else configFile}
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/shairport-sync.nix b/nixpkgs/nixos/modules/services/networking/shairport-sync.nix
new file mode 100644
index 00000000000..68e005ab81d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/shairport-sync.nix
@@ -0,0 +1,83 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.shairport-sync;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.shairport-sync = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Enable the shairport-sync daemon.
+
+ Running with a local system-wide or remote pulseaudio server
+ is recommended.
+ '';
+ };
+
+ arguments = mkOption {
+ default = "-v -o pa";
+ description = ''
+ Arguments to pass to the daemon. Defaults to a local pulseaudio
+ server.
+ '';
+ };
+
+ user = mkOption {
+ default = "shairport";
+ description = ''
+ User account name under which to run shairport-sync. The account
+ will be created.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.shairport-sync.enable {
+
+ services.avahi.enable = true;
+ services.avahi.publish.enable = true;
+ services.avahi.publish.userServices = true;
+
+ users.users = singleton
+ { name = cfg.user;
+ description = "Shairport user";
+ isSystemUser = true;
+ createHome = true;
+ home = "/var/lib/shairport-sync";
+ extraGroups = [ "audio" ] ++ optional config.hardware.pulseaudio.enable "pulse";
+ };
+
+ systemd.services.shairport-sync =
+ {
+ description = "shairport-sync";
+ after = [ "network.target" "avahi-daemon.service" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = cfg.user;
+ ExecStart = "${pkgs.shairport-sync}/bin/shairport-sync ${cfg.arguments}";
+ RuntimeDirectory = "shairport-sync";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.shairport-sync ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/shout.nix b/nixpkgs/nixos/modules/services/networking/shout.nix
new file mode 100644
index 00000000000..e548ec66962
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/shout.nix
@@ -0,0 +1,114 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+ cfg = config.services.shout;
+ shoutHome = "/var/lib/shout";
+
+ defaultConfig = pkgs.runCommand "config.js" { preferLocalBuild = true; } ''
+ EDITOR=true ${pkgs.shout}/bin/shout config --home $PWD
+ mv config.js $out
+ '';
+
+ finalConfigFile = if (cfg.configFile != null) then cfg.configFile else ''
+ var _ = require('${pkgs.shout}/lib/node_modules/shout/node_modules/lodash')
+
+ module.exports = _.merge(
+ {},
+ require('${defaultConfig}'),
+ ${builtins.toJSON cfg.config}
+ )
+ '';
+
+in {
+ options.services.shout = {
+ enable = mkEnableOption "Shout web IRC client";
+
+ private = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Make your shout instance private. You will need to configure user
+ accounts by adding entries in <filename>${shoutHome}/users</filename>.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = "IP interface to listen on for http connections.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 9000;
+ description = "TCP port to listen on for http connections.";
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Contents of Shout's <filename>config.js</filename> file.
+
+ Used for backward compatibility, recommended way is now to use
+ the <literal>config</literal> option.
+
+ Documentation: http://shout-irc.com/docs/server/configuration.html
+ '';
+ };
+
+ config = mkOption {
+ default = {};
+ type = types.attrs;
+ example = {
+ displayNetwork = false;
+ defaults = {
+ name = "Your Network";
+ host = "localhost";
+ port = 6697;
+ };
+ };
+ description = ''
+ Shout <filename>config.js</filename> contents as attribute set (will be
+ converted to JSON to generate the configuration file).
+
+ The options defined here will be merged to the default configuration file.
+
+ Documentation: http://shout-irc.com/docs/server/configuration.html
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users = singleton {
+ name = "shout";
+ uid = config.ids.uids.shout;
+ description = "Shout daemon user";
+ home = shoutHome;
+ createHome = true;
+ };
+
+ systemd.services.shout = {
+ description = "Shout web IRC client";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+ preStart = "ln -sf ${pkgs.writeText "config.js" finalConfigFile} ${shoutHome}/config.js";
+ script = concatStringsSep " " [
+ "${pkgs.shout}/bin/shout"
+ (if cfg.private then "--private" else "--public")
+ "--port" (toString cfg.port)
+ "--host" (toString cfg.listenAddress)
+ "--home" shoutHome
+ ];
+ serviceConfig = {
+ User = "shout";
+ ProtectHome = "true";
+ ProtectSystem = "full";
+ PrivateTmp = "true";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/skydns.nix b/nixpkgs/nixos/modules/services/networking/skydns.nix
new file mode 100644
index 00000000000..6ad18bb2240
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/skydns.nix
@@ -0,0 +1,92 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.skydns;
+
+in {
+ options.services.skydns = {
+ enable = mkEnableOption "skydns service";
+
+ etcd = {
+ machines = mkOption {
+ default = [ "http://127.0.0.1:2379" ];
+ type = types.listOf types.str;
+ description = "Skydns list of etcd endpoints to connect to.";
+ };
+
+ tlsKey = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Skydns path of TLS client certificate - private key.";
+ };
+
+ tlsPem = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Skydns path of TLS client certificate - public key.";
+ };
+
+ caCert = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Skydns path of TLS certificate authority public key.";
+ };
+ };
+
+ address = mkOption {
+ default = "0.0.0.0:53";
+ type = types.str;
+ description = "Skydns address to bind to.";
+ };
+
+ domain = mkOption {
+ default = "skydns.local.";
+ type = types.str;
+ description = "Skydns default domain if not specified by etcd config.";
+ };
+
+ nameservers = mkOption {
+ default = map (n: n + ":53") config.networking.nameservers;
+ type = types.listOf types.str;
+ description = "Skydns list of nameservers to forward DNS requests to when not authoritative for a domain.";
+ example = ["8.8.8.8:53" "8.8.4.4:53"];
+ };
+
+ package = mkOption {
+ default = pkgs.skydns;
+ defaultText = "pkgs.skydns";
+ type = types.package;
+ description = "Skydns package to use.";
+ };
+
+ extraConfig = mkOption {
+ default = {};
+ type = types.attrsOf types.str;
+ description = "Skydns attribute set of extra config options passed as environemnt variables.";
+ };
+ };
+
+ config = mkIf (cfg.enable) {
+ systemd.services.skydns = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "etcd.service" ];
+ description = "Skydns Service";
+ environment = {
+ ETCD_MACHINES = concatStringsSep "," cfg.etcd.machines;
+ ETCD_TLSKEY = cfg.etcd.tlsKey;
+ ETCD_TLSPEM = cfg.etcd.tlsPem;
+ ETCD_CACERT = cfg.etcd.caCert;
+ SKYDNS_ADDR = cfg.address;
+ SKYDNS_DOMAIN = cfg.domain;
+ SKYDNS_NAMESERVERS = concatStringsSep "," cfg.nameservers;
+ };
+ serviceConfig = {
+ ExecStart = "${cfg.package.bin}/bin/skydns";
+ };
+ };
+
+ environment.systemPackages = [ cfg.package ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/smokeping.nix b/nixpkgs/nixos/modules/services/networking/smokeping.nix
new file mode 100644
index 00000000000..d4d0594a9cd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/smokeping.nix
@@ -0,0 +1,318 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+ cfg = config.services.smokeping;
+ smokepingHome = "/var/lib/smokeping";
+ smokepingPidDir = "/run";
+ configFile =
+ if cfg.config == null
+ then
+ ''
+ *** General ***
+ cgiurl = ${cfg.cgiUrl}
+ contact = ${cfg.ownerEmail}
+ datadir = ${smokepingHome}/data
+ imgcache = ${smokepingHome}/cache
+ imgurl = ${cfg.imgUrl}
+ linkstyle = ${cfg.linkStyle}
+ ${lib.optionalString (cfg.mailHost != "") "mailhost = ${cfg.mailHost}"}
+ owner = ${cfg.owner}
+ pagedir = ${smokepingHome}/cache
+ piddir = ${smokepingPidDir}
+ ${lib.optionalString (cfg.sendmail != null) "sendmail = ${cfg.sendmail}"}
+ smokemail = ${cfg.smokeMailTemplate}
+ *** Presentation ***
+ template = ${cfg.presentationTemplate}
+ ${cfg.presentationConfig}
+ *** Alerts ***
+ ${cfg.alertConfig}
+ *** Database ***
+ ${cfg.databaseConfig}
+ *** Probes ***
+ ${cfg.probeConfig}
+ *** Targets ***
+ ${cfg.targetConfig}
+ ${cfg.extraConfig}
+ ''
+ else
+ cfg.config;
+
+ configPath = pkgs.writeText "smokeping.conf" configFile;
+ cgiHome = pkgs.writeScript "smokeping.fcgi" ''
+ #!${pkgs.bash}/bin/bash
+ ${cfg.package}/bin/smokeping_cgi ${configPath}
+ '';
+in
+
+{
+ options = {
+ services.smokeping = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the smokeping service";
+ };
+ alertConfig = mkOption {
+ type = types.lines;
+ default = ''
+ to = root@localhost
+ from = smokeping@localhost
+ '';
+ example = literalExample ''
+ to = alertee@address.somewhere
+ from = smokealert@company.xy
+
+ +someloss
+ type = loss
+ # in percent
+ pattern = >0%,*12*,>0%,*12*,>0%
+ comment = loss 3 times in a row;
+ '';
+ description = "Configuration for alerts.";
+ };
+ cgiUrl = mkOption {
+ type = types.str;
+ default = "http://${cfg.hostName}:${toString cfg.port}/smokeping.cgi";
+ defaultText = "http://\${hostName}:\${toString port}/smokeping.cgi";
+ example = "https://somewhere.example.com/smokeping.cgi";
+ description = "URL to the smokeping cgi.";
+ };
+ config = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = "Full smokeping config supplied by the user. Overrides " +
+ "and replaces any other configuration supplied.";
+ };
+ databaseConfig = mkOption {
+ type = types.lines;
+ default = ''
+ step = 300
+ pings = 20
+ # consfn mrhb steps total
+ AVERAGE 0.5 1 1008
+ AVERAGE 0.5 12 4320
+ MIN 0.5 12 4320
+ MAX 0.5 12 4320
+ AVERAGE 0.5 144 720
+ MAX 0.5 144 720
+ MIN 0.5 144 720
+
+ '';
+ example = literalExample ''
+ # near constant pings.
+ step = 30
+ pings = 20
+ # consfn mrhb steps total
+ AVERAGE 0.5 1 10080
+ AVERAGE 0.5 12 43200
+ MIN 0.5 12 43200
+ MAX 0.5 12 43200
+ AVERAGE 0.5 144 7200
+ MAX 0.5 144 7200
+ MIN 0.5 144 7200
+ '';
+ description = ''Configure the ping frequency and retention of the rrd files.
+ Once set, changing the interval will require deletion or migration of all
+ the collected data.'';
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Any additional customization not already included.";
+ };
+ hostName = mkOption {
+ type = types.str;
+ default = config.networking.hostName;
+ example = "somewhere.example.com";
+ description = "DNS name for the urls generated in the cgi.";
+ };
+ imgUrl = mkOption {
+ type = types.str;
+ default = "http://${cfg.hostName}:${toString cfg.port}/cache";
+ defaultText = "http://\${hostName}:\${toString port}/cache";
+ example = "https://somewhere.example.com/cache";
+ description = "Base url for images generated in the cgi.";
+ };
+ linkStyle = mkOption {
+ type = types.enum ["original" "absolute" "relative"];
+ default = "relative";
+ example = "absolute";
+ description = "DNS name for the urls generated in the cgi.";
+ };
+ mailHost = mkOption {
+ type = types.str;
+ default = "";
+ example = "localhost";
+ description = "Use this SMTP server to send alerts";
+ };
+ owner = mkOption {
+ type = types.str;
+ default = "nobody";
+ example = "Joe Admin";
+ description = "Real name of the owner of the instance";
+ };
+ ownerEmail = mkOption {
+ type = types.str;
+ default = "no-reply@${cfg.hostName}";
+ example = "no-reply@yourdomain.com";
+ description = "Email contact for owner";
+ };
+ package = mkOption {
+ type = types.package;
+ default = pkgs.smokeping;
+ defaultText = "pkgs.smokeping";
+ description = "Specify a custom smokeping package";
+ };
+ port = mkOption {
+ type = types.int;
+ default = 8081;
+ example = 8081;
+ description = "TCP port to use for the web server.";
+ };
+ presentationConfig = mkOption {
+ type = types.lines;
+ default = ''
+ + charts
+ menu = Charts
+ title = The most interesting destinations
+ ++ stddev
+ sorter = StdDev(entries=>4)
+ title = Top Standard Deviation
+ menu = Std Deviation
+ format = Standard Deviation %f
+ ++ max
+ sorter = Max(entries=>5)
+ title = Top Max Roundtrip Time
+ menu = by Max
+ format = Max Roundtrip Time %f seconds
+ ++ loss
+ sorter = Loss(entries=>5)
+ title = Top Packet Loss
+ menu = Loss
+ format = Packets Lost %f
+ ++ median
+ sorter = Median(entries=>5)
+ title = Top Median Roundtrip Time
+ menu = by Median
+ format = Median RTT %f seconds
+ + overview
+ width = 600
+ height = 50
+ range = 10h
+ + detail
+ width = 600
+ height = 200
+ unison_tolerance = 2
+ "Last 3 Hours" 3h
+ "Last 30 Hours" 30h
+ "Last 10 Days" 10d
+ "Last 360 Days" 360d
+ '';
+ description = "presentation graph style";
+ };
+ presentationTemplate = mkOption {
+ type = types.str;
+ default = "${pkgs.smokeping}/etc/basepage.html.dist";
+ description = "Default page layout for the web UI.";
+ };
+ probeConfig = mkOption {
+ type = types.lines;
+ default = ''
+ + FPing
+ binary = ${config.security.wrapperDir}/fping
+ '';
+ description = "Probe configuration";
+ };
+ sendmail = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/wrappers/bin/sendmail";
+ description = "Use this sendmail compatible script to deliver alerts";
+ };
+ smokeMailTemplate = mkOption {
+ type = types.str;
+ default = "${cfg.package}/etc/smokemail.dist";
+ description = "Specify the smokemail template for alerts.";
+ };
+ targetConfig = mkOption {
+ type = types.lines;
+ default = ''
+ probe = FPing
+ menu = Top
+ title = Network Latency Grapher
+ remark = Welcome to the SmokePing website of xxx Company. \
+ Here you will learn all about the latency of our network.
+ + Local
+ menu = Local
+ title = Local Network
+ ++ LocalMachine
+ menu = Local Machine
+ title = This host
+ host = localhost
+ '';
+ description = "Target configuration";
+ };
+ user = mkOption {
+ type = types.str;
+ default = "smokeping";
+ description = "User that runs smokeping and (optionally) thttpd";
+ };
+ webService = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Enable a smokeping web interface";
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = !(cfg.sendmail != null && cfg.mailHost != "");
+ message = "services.smokeping: sendmail and Mailhost cannot both be enabled.";
+ }
+ ];
+ security.wrappers = {
+ fping.source = "${pkgs.fping}/bin/fping";
+ fping6.source = "${pkgs.fping}/bin/fping6";
+ };
+ environment.systemPackages = [ pkgs.fping ];
+ users.users = singleton {
+ name = cfg.user;
+ isNormalUser = false;
+ isSystemUser = true;
+ uid = config.ids.uids.smokeping;
+ description = "smokeping daemon user";
+ home = smokepingHome;
+ createHome = true;
+ };
+ systemd.services.smokeping = {
+ wantedBy = [ "multi-user.target"];
+ serviceConfig = {
+ User = cfg.user;
+ Restart = "on-failure";
+ };
+ preStart = ''
+ mkdir -m 0755 -p ${smokepingHome}/cache ${smokepingHome}/data
+ rm -f ${smokepingHome}/cropper
+ ln -s ${cfg.package}/htdocs/cropper ${smokepingHome}/cropper
+ cp ${cgiHome} ${smokepingHome}/smokeping.fcgi
+ ${cfg.package}/bin/smokeping --check --config=${configPath}
+ ${cfg.package}/bin/smokeping --static --config=${configPath}
+ '';
+ script = ''${cfg.package}/bin/smokeping --config=${configPath} --nodaemon'';
+ };
+ systemd.services.thttpd = mkIf cfg.webService {
+ wantedBy = [ "multi-user.target"];
+ requires = [ "smokeping.service"];
+ partOf = [ "smokeping.service"];
+ path = with pkgs; [ bash rrdtool smokeping thttpd ];
+ script = ''thttpd -u ${cfg.user} -c "**.fcgi" -d ${smokepingHome} -p ${builtins.toString cfg.port} -D -nos'';
+ serviceConfig.Restart = "always";
+ };
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/sniproxy.nix b/nixpkgs/nixos/modules/services/networking/sniproxy.nix
new file mode 100644
index 00000000000..0345c12d3af
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/sniproxy.nix
@@ -0,0 +1,99 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.sniproxy;
+
+ configFile = pkgs.writeText "sniproxy.conf" ''
+ user ${cfg.user}
+ pidfile /run/sniproxy.pid
+ ${cfg.config}
+ '';
+
+in
+{
+ options = {
+ services.sniproxy = {
+ enable = mkEnableOption "sniproxy server";
+
+ user = mkOption {
+ type = types.str;
+ default = "sniproxy";
+ description = "User account under which sniproxy runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "sniproxy";
+ description = "Group under which sniproxy runs.";
+ };
+
+ config = mkOption {
+ type = types.lines;
+ default = "";
+ description = "sniproxy.conf configuration excluding the daemon username and pid file.";
+ example = literalExample ''
+ error_log {
+ filename /var/log/sniproxy/error.log
+ }
+ access_log {
+ filename /var/log/sniproxy/access.log
+ }
+ listen 443 {
+ proto tls
+ }
+ table {
+ example.com 192.0.2.10
+ example.net 192.0.2.20
+ }
+ '';
+ };
+
+ logDir = mkOption {
+ type = types.str;
+ default = "/var/log/sniproxy/";
+ description = "Location of the log directory for sniproxy.";
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.sniproxy = {
+ description = "sniproxy server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ test -d ${cfg.logDir} || {
+ echo "Creating initial log directory for sniproxy in ${cfg.logDir}"
+ mkdir -p ${cfg.logDir}
+ chmod 640 ${cfg.logDir}
+ }
+ chown -R ${cfg.user}:${cfg.group} ${cfg.logDir}
+ '';
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.sniproxy}/bin/sniproxy -c ${configFile}";
+ Restart = "always";
+ };
+ };
+
+ users.users = mkIf (cfg.user == "sniproxy") {
+ sniproxy = {
+ group = cfg.group;
+ uid = config.ids.uids.sniproxy;
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "sniproxy") {
+ sniproxy = {
+ gid = config.ids.gids.sniproxy;
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/softether.nix b/nixpkgs/nixos/modules/services/networking/softether.nix
new file mode 100644
index 00000000000..2dc73d81b25
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/softether.nix
@@ -0,0 +1,163 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.softether;
+
+ package = cfg.package.override { dataDir = cfg.dataDir; };
+
+in
+{
+
+ ###### interface
+
+ options = {
+
+ services.softether = {
+
+ enable = mkEnableOption "SoftEther VPN services";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.softether;
+ defaultText = "pkgs.softether";
+ description = ''
+ softether derivation to use.
+ '';
+ };
+
+ vpnserver.enable = mkEnableOption "SoftEther VPN Server";
+
+ vpnbridge.enable = mkEnableOption "SoftEther VPN Bridge";
+
+ vpnclient = {
+ enable = mkEnableOption "SoftEther VPN Client";
+ up = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed when the Virtual Network Adapter(s) is/are starting.
+ '';
+ };
+ down = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed when the Virtual Network Adapter(s) is/are shutting down.
+ '';
+ };
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/softether";
+ description = ''
+ Data directory for SoftEther VPN.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable (
+
+ mkMerge [{
+ environment.systemPackages = [ package ];
+
+ systemd.services.softether-init = {
+ description = "SoftEther VPN services initial task";
+ wantedBy = [ "network.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = false;
+ };
+ script = ''
+ for d in vpnserver vpnbridge vpnclient vpncmd; do
+ if ! test -e ${cfg.dataDir}/$d; then
+ ${pkgs.coreutils}/bin/mkdir -m0700 -p ${cfg.dataDir}/$d
+ install -m0600 ${package}${cfg.dataDir}/$d/hamcore.se2 ${cfg.dataDir}/$d/hamcore.se2
+ fi
+ done
+ rm -rf ${cfg.dataDir}/vpncmd/vpncmd
+ ln -s ${package}${cfg.dataDir}/vpncmd/vpncmd ${cfg.dataDir}/vpncmd/vpncmd
+ '';
+ };
+ }
+
+ (mkIf (cfg.vpnserver.enable) {
+ systemd.services.vpnserver = {
+ description = "SoftEther VPN Server";
+ after = [ "softether-init.service" ];
+ requires = [ "softether-init.service" ];
+ wantedBy = [ "network.target" ];
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${package}/bin/vpnserver start";
+ ExecStop = "${package}/bin/vpnserver stop";
+ };
+ preStart = ''
+ rm -rf ${cfg.dataDir}/vpnserver/vpnserver
+ ln -s ${package}${cfg.dataDir}/vpnserver/vpnserver ${cfg.dataDir}/vpnserver/vpnserver
+ '';
+ postStop = ''
+ rm -rf ${cfg.dataDir}/vpnserver/vpnserver
+ '';
+ };
+ })
+
+ (mkIf (cfg.vpnbridge.enable) {
+ systemd.services.vpnbridge = {
+ description = "SoftEther VPN Bridge";
+ after = [ "softether-init.service" ];
+ requires = [ "softether-init.service" ];
+ wantedBy = [ "network.target" ];
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${package}/bin/vpnbridge start";
+ ExecStop = "${package}/bin/vpnbridge stop";
+ };
+ preStart = ''
+ rm -rf ${cfg.dataDir}/vpnbridge/vpnbridge
+ ln -s ${package}${cfg.dataDir}/vpnbridge/vpnbridge ${cfg.dataDir}/vpnbridge/vpnbridge
+ '';
+ postStop = ''
+ rm -rf ${cfg.dataDir}/vpnbridge/vpnbridge
+ '';
+ };
+ })
+
+ (mkIf (cfg.vpnclient.enable) {
+ systemd.services.vpnclient = {
+ description = "SoftEther VPN Client";
+ after = [ "softether-init.service" ];
+ requires = [ "softether-init.service" ];
+ wantedBy = [ "network.target" ];
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${package}/bin/vpnclient start";
+ ExecStop = "${package}/bin/vpnclient stop";
+ };
+ preStart = ''
+ rm -rf ${cfg.dataDir}/vpnclient/vpnclient
+ ln -s ${package}${cfg.dataDir}/vpnclient/vpnclient ${cfg.dataDir}/vpnclient/vpnclient
+ '';
+ postStart = ''
+ sleep 1
+ ${cfg.vpnclient.up}
+ '';
+ postStop = ''
+ rm -rf ${cfg.dataDir}/vpnclient/vpnclient
+ sleep 1
+ ${cfg.vpnclient.down}
+ '';
+ };
+ boot.kernelModules = [ "tun" ];
+ })
+
+ ]);
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/spiped.nix b/nixpkgs/nixos/modules/services/networking/spiped.nix
new file mode 100644
index 00000000000..e60d9abf42a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/spiped.nix
@@ -0,0 +1,220 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.spiped;
+in
+{
+ options = {
+ services.spiped = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the spiped service module.";
+ };
+
+ config = mkOption {
+ type = types.attrsOf (types.submodule (
+ {
+ options = {
+ encrypt = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Take unencrypted connections from the
+ <literal>source</literal> socket and send encrypted
+ connections to the <literal>target</literal> socket.
+ '';
+ };
+
+ decrypt = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Take encrypted connections from the
+ <literal>source</literal> socket and send unencrypted
+ connections to the <literal>target</literal> socket.
+ '';
+ };
+
+ source = mkOption {
+ type = types.str;
+ description = ''
+ Address on which spiped should listen for incoming
+ connections. Must be in one of the following formats:
+ <literal>/absolute/path/to/unix/socket</literal>,
+ <literal>host.name:port</literal>,
+ <literal>[ip.v4.ad.dr]:port</literal> or
+ <literal>[ipv6::addr]:port</literal> - note that
+ hostnames are resolved when spiped is launched and are
+ not re-resolved later; thus if DNS entries change
+ spiped will continue to connect to the expired
+ address.
+ '';
+ };
+
+ target = mkOption {
+ type = types.str;
+ description = "Address to which spiped should connect.";
+ };
+
+ keyfile = mkOption {
+ type = types.path;
+ description = ''
+ Name of a file containing the spiped key. As the
+ daemon runs as the <literal>spiped</literal> user, the
+ key file must be somewhere owned by that user. By
+ default, we recommend putting the keys for any spipe
+ services in <literal>/var/lib/spiped</literal>.
+ '';
+ };
+
+ timeout = mkOption {
+ type = types.int;
+ default = 5;
+ description = ''
+ Timeout, in seconds, after which an attempt to connect to
+ the target or a protocol handshake will be aborted (and the
+ connection dropped) if not completed
+ '';
+ };
+
+ maxConns = mkOption {
+ type = types.int;
+ default = 100;
+ description = ''
+ Limit on the number of simultaneous connections allowed.
+ '';
+ };
+
+ waitForDNS = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Wait for DNS. Normally when <literal>spiped</literal> is
+ launched it resolves addresses and binds to its source
+ socket before the parent process returns; with this option
+ it will daemonize first and retry failed DNS lookups until
+ they succeed. This allows <literal>spiped</literal> to
+ launch even if DNS isn't set up yet, but at the expense of
+ losing the guarantee that once <literal>spiped</literal> has
+ finished launching it will be ready to create pipes.
+ '';
+ };
+
+ disableKeepalives = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Disable transport layer keep-alives.";
+ };
+
+ weakHandshake = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Use fast/weak handshaking: This reduces the CPU time spent
+ in the initial connection setup, at the expense of losing
+ perfect forward secrecy.
+ '';
+ };
+
+ resolveRefresh = mkOption {
+ type = types.int;
+ default = 60;
+ description = ''
+ Resolution refresh time for the target socket, in seconds.
+ '';
+ };
+
+ disableReresolution = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Disable target address re-resolution.";
+ };
+ };
+ }
+ ));
+
+ default = {};
+
+ example = literalExample ''
+ {
+ pipe1 =
+ { keyfile = "/var/lib/spiped/pipe1.key";
+ encrypt = true;
+ source = "localhost:6000";
+ target = "endpoint.example.com:7000";
+ };
+ pipe2 =
+ { keyfile = "/var/lib/spiped/pipe2.key";
+ decrypt = true;
+ source = "0.0.0.0:7000";
+ target = "localhost:3000";
+ };
+ }
+ '';
+
+ description = ''
+ Configuration for a secure pipe daemon. The daemon can be
+ started, stopped, or examined using
+ <literal>systemctl</literal>, under the name
+ <literal>spiped@foo</literal>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = mapAttrsToList (name: c: {
+ assertion = (c.encrypt -> !c.decrypt) || (c.decrypt -> c.encrypt);
+ message = "A pipe must either encrypt or decrypt";
+ }) cfg.config;
+
+ users.groups.spiped.gid = config.ids.gids.spiped;
+ users.users.spiped = {
+ description = "Secure Pipe Service user";
+ group = "spiped";
+ uid = config.ids.uids.spiped;
+ };
+
+ systemd.services."spiped@" = {
+ description = "Secure pipe '%i'";
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ Restart = "always";
+ User = "spiped";
+ PermissionsStartOnly = true;
+ };
+
+ preStart = ''
+ cd /var/lib/spiped
+ chmod -R 0660 *
+ chown -R spiped:spiped *
+ '';
+ scriptArgs = "%i";
+ script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/$1.spec`";
+ };
+
+ system.activationScripts.spiped = optionalString (cfg.config != {})
+ "mkdir -p /var/lib/spiped";
+
+ # Setup spiped config files
+ environment.etc = mapAttrs' (name: cfg: nameValuePair "spiped/${name}.spec"
+ { text = concatStringsSep " "
+ [ (if cfg.encrypt then "-e" else "-d") # Mode
+ "-s ${cfg.source}" # Source
+ "-t ${cfg.target}" # Target
+ "-k ${cfg.keyfile}" # Keyfile
+ "-n ${toString cfg.maxConns}" # Max number of conns
+ "-o ${toString cfg.timeout}" # Timeout
+ (optionalString cfg.waitForDNS "-D") # Wait for DNS
+ (optionalString cfg.weakHandshake "-f") # No PFS
+ (optionalString cfg.disableKeepalives "-j") # Keepalives
+ (if cfg.disableReresolution then "-R"
+ else "-r ${toString cfg.resolveRefresh}")
+ ];
+ }) cfg.config;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/squid.nix b/nixpkgs/nixos/modules/services/networking/squid.nix
new file mode 100644
index 00000000000..9d063b92aa1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/squid.nix
@@ -0,0 +1,168 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.squid;
+
+
+ squidConfig = pkgs.writeText "squid.conf"
+ (if cfg.configText != null then cfg.configText else
+ ''
+ #
+ # Recommended minimum configuration (3.5):
+ #
+
+ # Example rule allowing access from your local networks.
+ # Adapt to list your (internal) IP networks from where browsing
+ # should be allowed
+ acl localnet src 10.0.0.0/8 # RFC 1918 possible internal network
+ acl localnet src 172.16.0.0/12 # RFC 1918 possible internal network
+ acl localnet src 192.168.0.0/16 # RFC 1918 possible internal network
+ acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
+ acl localnet src fc00::/7 # RFC 4193 local private network range
+ acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines
+
+ acl SSL_ports port 443 # https
+ acl Safe_ports port 80 # http
+ acl Safe_ports port 21 # ftp
+ acl Safe_ports port 443 # https
+ acl Safe_ports port 70 # gopher
+ acl Safe_ports port 210 # wais
+ acl Safe_ports port 1025-65535 # unregistered ports
+ acl Safe_ports port 280 # http-mgmt
+ acl Safe_ports port 488 # gss-http
+ acl Safe_ports port 591 # filemaker
+ acl Safe_ports port 777 # multiling http
+ acl CONNECT method CONNECT
+
+ #
+ # Recommended minimum Access Permission configuration:
+ #
+ # Deny requests to certain unsafe ports
+ http_access deny !Safe_ports
+
+ # Deny CONNECT to other than secure SSL ports
+ http_access deny CONNECT !SSL_ports
+
+ # Only allow cachemgr access from localhost
+ http_access allow localhost manager
+ http_access deny manager
+
+ # We strongly recommend the following be uncommented to protect innocent
+ # web applications running on the proxy server who think the only
+ # one who can access services on "localhost" is a local user
+ http_access deny to_localhost
+
+ # Application logs to syslog, access and store logs have specific files
+ cache_log syslog
+ access_log stdio:/var/log/squid/access.log
+ cache_store_log stdio:/var/log/squid/store.log
+
+ # Required by systemd service
+ pid_filename /run/squid.pid
+
+ # Run as user and group squid
+ cache_effective_user squid squid
+
+ #
+ # INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
+ #
+ ${cfg.extraConfig}
+
+ # Example rule allowing access from your local networks.
+ # Adapt localnet in the ACL section to list your (internal) IP networks
+ # from where browsing should be allowed
+ http_access allow localnet
+ http_access allow localhost
+
+ # And finally deny all other access to this proxy
+ http_access deny all
+
+ # Squid normally listens to port 3128
+ http_port ${toString cfg.proxyPort}
+
+ # Leave coredumps in the first cache dir
+ coredump_dir /var/cache/squid
+
+ #
+ # Add any of your own refresh_pattern entries above these.
+ #
+ refresh_pattern ^ftp: 1440 20% 10080
+ refresh_pattern ^gopher: 1440 0% 1440
+ refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
+ refresh_pattern . 0 20% 4320
+ '');
+
+in
+
+{
+
+ options = {
+
+ services.squid = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to run squid web proxy.";
+ };
+
+ proxyPort = mkOption {
+ type = types.int;
+ default = 3128;
+ description = "TCP port on which squid will listen.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Squid configuration. Contents will be added
+ verbatim to the configuration file.
+ '';
+ };
+
+ configText = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Verbatim contents of squid.conf. If null (default), use the
+ autogenerated file from NixOS instead.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users.squid = {
+ isSystemUser = true;
+ group = "squid";
+ home = "/var/cache/squid";
+ createHome = true;
+ };
+
+ users.groups.squid = {};
+
+ systemd.services.squid = {
+ description = "Squid caching web proxy";
+ after = [ "network.target" "nss-lookup.target" ];
+ wantedBy = [ "multi-user.target"];
+ preStart = ''
+ mkdir -p "/var/log/squid"
+ chown squid:squid "/var/log/squid"
+ '';
+ serviceConfig = {
+ Type="forking";
+ PIDFile="/run/squid.pid";
+ ExecStart = "${pkgs.squid}/bin/squid -YCs -f ${squidConfig}";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ssh/lshd.nix b/nixpkgs/nixos/modules/services/networking/ssh/lshd.nix
new file mode 100644
index 00000000000..eca599afb33
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ssh/lshd.nix
@@ -0,0 +1,176 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs) lsh;
+
+ cfg = config.services.lshd;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.lshd = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable the GNU lshd SSH2 daemon, which allows
+ secure remote login.
+ '';
+ };
+
+ portNumber = mkOption {
+ default = 22;
+ description = ''
+ The port on which to listen for connections.
+ '';
+ };
+
+ interfaces = mkOption {
+ default = [];
+ description = ''
+ List of network interfaces where listening for connections.
+ When providing the empty list, `[]', lshd listens on all
+ network interfaces.
+ '';
+ example = [ "localhost" "1.2.3.4:443" ];
+ };
+
+ hostKey = mkOption {
+ default = "/etc/lsh/host-key";
+ description = ''
+ Path to the server's private key. Note that this key must
+ have been created, e.g., using "lsh-keygen --server |
+ lsh-writekey --server", so that you can run lshd.
+ '';
+ };
+
+ syslog = mkOption {
+ default = true;
+ description = ''Whether to enable syslog output.'';
+ };
+
+ passwordAuthentication = mkOption {
+ default = true;
+ description = ''Whether to enable password authentication.'';
+ };
+
+ publicKeyAuthentication = mkOption {
+ default = true;
+ description = ''Whether to enable public key authentication.'';
+ };
+
+ rootLogin = mkOption {
+ default = false;
+ description = ''Whether to enable remote root login.'';
+ };
+
+ loginShell = mkOption {
+ default = null;
+ description = ''
+ If non-null, override the default login shell with the
+ specified value.
+ '';
+ example = "/nix/store/xyz-bash-10.0/bin/bash10";
+ };
+
+ srpKeyExchange = mkOption {
+ default = false;
+ description = ''
+ Whether to enable SRP key exchange and user authentication.
+ '';
+ };
+
+ tcpForwarding = mkOption {
+ default = true;
+ description = ''Whether to enable TCP/IP forwarding.'';
+ };
+
+ x11Forwarding = mkOption {
+ default = true;
+ description = ''Whether to enable X11 forwarding.'';
+ };
+
+ subsystems = mkOption {
+ description = ''
+ List of subsystem-path pairs, where the head of the pair
+ denotes the subsystem name, and the tail denotes the path to
+ an executable implementing it.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.lshd.subsystems = [ ["sftp" "${pkgs.lsh}/sbin/sftp-server"] ];
+
+ systemd.services.lshd = {
+ description = "GNU lshd SSH2 daemon";
+
+ after = [ "network.target" ];
+
+ wantedBy = [ "multi-user.target" ];
+
+ environment = {
+ LD_LIBRARY_PATH = config.system.nssModules.path;
+ };
+
+ preStart = ''
+ test -d /etc/lsh || mkdir -m 0755 -p /etc/lsh
+ test -d /var/spool/lsh || mkdir -m 0755 -p /var/spool/lsh
+
+ if ! test -f /var/spool/lsh/yarrow-seed-file
+ then
+ # XXX: It would be nice to provide feedback to the
+ # user when this fails, so that they can retry it
+ # manually.
+ ${lsh}/bin/lsh-make-seed --sloppy \
+ -o /var/spool/lsh/yarrow-seed-file
+ fi
+
+ if ! test -f "${cfg.hostKey}"
+ then
+ ${lsh}/bin/lsh-keygen --server | \
+ ${lsh}/bin/lsh-writekey --server -o "${cfg.hostKey}"
+ fi
+ '';
+
+ script = with cfg; ''
+ ${lsh}/sbin/lshd --daemonic \
+ --password-helper="${lsh}/sbin/lsh-pam-checkpw" \
+ -p ${toString portNumber} \
+ ${if interfaces == [] then ""
+ else (concatStrings (map (i: "--interface=\"${i}\"")
+ interfaces))} \
+ -h "${hostKey}" \
+ ${if !syslog then "--no-syslog" else ""} \
+ ${if passwordAuthentication then "--password" else "--no-password" } \
+ ${if publicKeyAuthentication then "--publickey" else "--no-publickey" } \
+ ${if rootLogin then "--root-login" else "--no-root-login" } \
+ ${if loginShell != null then "--login-shell=\"${loginShell}\"" else "" } \
+ ${if srpKeyExchange then "--srp-keyexchange" else "--no-srp-keyexchange" } \
+ ${if !tcpForwarding then "--no-tcpip-forward" else "--tcpip-forward"} \
+ ${if x11Forwarding then "--x11-forward" else "--no-x11-forward" } \
+ --subsystems=${concatStringsSep ","
+ (map (pair: (head pair) + "=" +
+ (head (tail pair)))
+ subsystems)}
+ '';
+ };
+
+ security.pam.services.lshd = {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix b/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix
new file mode 100644
index 00000000000..91fc7d72bc6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix
@@ -0,0 +1,512 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ # The splicing information needed for nativeBuildInputs isn't available
+ # on the derivations likely to be used as `cfgc.package`.
+ # This middle-ground solution ensures *an* sshd can do their basic validation
+ # on the configuration.
+ validationPackage = if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform
+ then [ cfgc.package ]
+ else [ pkgs.buildPackages.openssh ];
+
+ sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ validationPackage ]; } ''
+ cat >$out <<EOL
+ ${cfg.extraConfig}
+ EOL
+
+ ssh-keygen -f mock-hostkey -N ""
+ sshd -t -f $out -h mock-hostkey
+ '';
+
+ cfg = config.services.openssh;
+ cfgc = config.programs.ssh;
+
+ nssModulesPath = config.system.nssModules.path;
+
+ userOptions = {
+
+ options.openssh.authorizedKeys = {
+ keys = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ A list of verbatim OpenSSH public keys that should be added to the
+ user's authorized keys. The keys are added to a file that the SSH
+ daemon reads in addition to the the user's authorized_keys file.
+ You can combine the <literal>keys</literal> and
+ <literal>keyFiles</literal> options.
+ Warning: If you are using <literal>NixOps</literal> then don't use this
+ option since it will replace the key required for deployment via ssh.
+ '';
+ };
+
+ keyFiles = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ A list of files each containing one OpenSSH public key that should be
+ added to the user's authorized keys. The contents of the files are
+ read at build time and added to a file that the SSH daemon reads in
+ addition to the the user's authorized_keys file. You can combine the
+ <literal>keyFiles</literal> and <literal>keys</literal> options.
+ '';
+ };
+ };
+
+ };
+
+ authKeysFiles = let
+ mkAuthKeyFile = u: nameValuePair "ssh/authorized_keys.d/${u.name}" {
+ mode = "0444";
+ source = pkgs.writeText "${u.name}-authorized_keys" ''
+ ${concatStringsSep "\n" u.openssh.authorizedKeys.keys}
+ ${concatMapStrings (f: readFile f + "\n") u.openssh.authorizedKeys.keyFiles}
+ '';
+ };
+ usersWithKeys = attrValues (flip filterAttrs config.users.users (n: u:
+ length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0
+ ));
+ in listToAttrs (map mkAuthKeyFile usersWithKeys);
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.openssh = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the OpenSSH secure shell daemon, which
+ allows secure remote logins.
+ '';
+ };
+
+ startWhenNeeded = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If set, <command>sshd</command> is socket-activated; that
+ is, instead of having it permanently running as a daemon,
+ systemd will start an instance for each incoming connection.
+ '';
+ };
+
+ forwardX11 = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to allow X11 connections to be forwarded.
+ '';
+ };
+
+ allowSFTP = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable the SFTP subsystem in the SSH daemon. This
+ enables the use of commands such as <command>sftp</command> and
+ <command>sshfs</command>.
+ '';
+ };
+
+ sftpFlags = mkOption {
+ type = with types; listOf str;
+ default = [];
+ example = [ "-f AUTHPRIV" "-l INFO" ];
+ description = ''
+ Commandline flags to add to sftp-server.
+ '';
+ };
+
+ permitRootLogin = mkOption {
+ default = "prohibit-password";
+ type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"];
+ description = ''
+ Whether the root user can login using ssh.
+ '';
+ };
+
+ gatewayPorts = mkOption {
+ type = types.str;
+ default = "no";
+ description = ''
+ Specifies whether remote hosts are allowed to connect to
+ ports forwarded for the client. See
+ <citerefentry><refentrytitle>sshd_config</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry>.
+ '';
+ };
+
+ ports = mkOption {
+ type = types.listOf types.port;
+ default = [22];
+ description = ''
+ Specifies on which ports the SSH daemon listens.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to automatically open the specified ports in the firewall.
+ '';
+ };
+
+ listenAddresses = mkOption {
+ type = with types; listOf (submodule {
+ options = {
+ addr = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Host, IPv4 or IPv6 address to listen to.
+ '';
+ };
+ port = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Port to listen to.
+ '';
+ };
+ };
+ });
+ default = [];
+ example = [ { addr = "192.168.3.1"; port = 22; } { addr = "0.0.0.0"; port = 64022; } ];
+ description = ''
+ List of addresses and ports to listen on (ListenAddress directive
+ in config). If port is not specified for address sshd will listen
+ on all ports specified by <literal>ports</literal> option.
+ NOTE: this will override default listening on all local addresses and port 22.
+ NOTE: setting this option won't automatically enable given ports
+ in firewall configuration.
+ '';
+ };
+
+ passwordAuthentication = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Specifies whether password authentication is allowed.
+ '';
+ };
+
+ challengeResponseAuthentication = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Specifies whether challenge/response authentication is allowed.
+ '';
+ };
+
+ hostKeys = mkOption {
+ type = types.listOf types.attrs;
+ default =
+ [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; }
+ { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; }
+ ];
+ example =
+ [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; rounds = 100; openSSHFormat = true; }
+ { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; rounds = 100; comment = "key comment"; }
+ ];
+ description = ''
+ NixOS can automatically generate SSH host keys. This option
+ specifies the path, type and size of each key. See
+ <citerefentry><refentrytitle>ssh-keygen</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> for supported types
+ and sizes.
+ '';
+ };
+
+ authorizedKeysFiles = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Files from which authorized keys are read.";
+ };
+
+ kexAlgorithms = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "curve25519-sha256@libssh.org"
+ "diffie-hellman-group-exchange-sha256"
+ ];
+ description = ''
+ Allowed key exchange algorithms
+ </para>
+ <para>
+ Defaults to recommended settings from both
+ <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" />
+ and
+ <link xlink:href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH#Modern_.28OpenSSH_6.7.2B.29" />
+ '';
+ };
+
+ ciphers = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "chacha20-poly1305@openssh.com"
+ "aes256-gcm@openssh.com"
+ "aes128-gcm@openssh.com"
+ "aes256-ctr"
+ "aes192-ctr"
+ "aes128-ctr"
+ ];
+ description = ''
+ Allowed ciphers
+ </para>
+ <para>
+ Defaults to recommended settings from both
+ <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" />
+ and
+ <link xlink:href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH#Modern_.28OpenSSH_6.7.2B.29" />
+ '';
+ };
+
+ macs = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "hmac-sha2-512-etm@openssh.com"
+ "hmac-sha2-256-etm@openssh.com"
+ "umac-128-etm@openssh.com"
+ "hmac-sha2-512"
+ "hmac-sha2-256"
+ "umac-128@openssh.com"
+ ];
+ description = ''
+ Allowed MACs
+ </para>
+ <para>
+ Defaults to recommended settings from both
+ <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" />
+ and
+ <link xlink:href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH#Modern_.28OpenSSH_6.7.2B.29" />
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ];
+ default = "VERBOSE";
+ description = ''
+ Gives the verbosity level that is used when logging messages from sshd(8). The possible values are:
+ QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, and DEBUG3. The default is VERBOSE. DEBUG and DEBUG1
+ are equivalent. DEBUG2 and DEBUG3 each specify higher levels of debugging output. Logging with a DEBUG level
+ violates the privacy of users and is not recommended.
+
+ LogLevel VERBOSE logs user's key fingerprint on login.
+ Needed to have a clear audit track of which key was used to log in.
+ '';
+ };
+
+ useDns = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Specifies whether sshd(8) should look up the remote host name, and to check that the resolved host name for
+ the remote IP address maps back to the very same IP address.
+ If this option is set to no (the default) then only addresses and not host names may be used in
+ ~/.ssh/authorized_keys from and sshd_config Match Host directives.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Verbatim contents of <filename>sshd_config</filename>.";
+ };
+
+ moduliFile = mkOption {
+ example = "/etc/my-local-ssh-moduli;";
+ type = types.path;
+ description = ''
+ Path to <literal>moduli</literal> file to install in
+ <literal>/etc/ssh/moduli</literal>. If this option is unset, then
+ the <literal>moduli</literal> file shipped with OpenSSH will be used.
+ '';
+ };
+
+ };
+
+ users.users = mkOption {
+ type = with types; loaOf (submodule userOptions);
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users.sshd =
+ { isSystemUser = true;
+ description = "SSH privilege separation user";
+ };
+
+ services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli";
+
+ environment.etc = authKeysFiles //
+ { "ssh/moduli".source = cfg.moduliFile;
+ "ssh/sshd_config".source = sshconf;
+ };
+
+ systemd =
+ let
+ service =
+ { description = "SSH Daemon";
+ wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
+ after = [ "network.target" ];
+ stopIfChanged = false;
+ path = [ cfgc.package pkgs.gawk ];
+ environment.LD_LIBRARY_PATH = nssModulesPath;
+
+ restartTriggers = optionals (!cfg.startWhenNeeded) [
+ config.environment.etc."ssh/sshd_config".source
+ ];
+
+ preStart =
+ ''
+ # Make sure we don't write to stdout, since in case of
+ # socket activation, it goes to the remote side (#19589).
+ exec >&2
+
+ mkdir -m 0755 -p /etc/ssh
+
+ ${flip concatMapStrings cfg.hostKeys (k: ''
+ if ! [ -f "${k.path}" ]; then
+ ssh-keygen \
+ -t "${k.type}" \
+ ${if k ? bits then "-b ${toString k.bits}" else ""} \
+ ${if k ? rounds then "-a ${toString k.rounds}" else ""} \
+ ${if k ? comment then "-C '${k.comment}'" else ""} \
+ ${if k ? openSSHFormat && k.openSSHFormat then "-o" else ""} \
+ -f "${k.path}" \
+ -N ""
+ fi
+ '')}
+ '';
+
+ serviceConfig =
+ { ExecStart =
+ (optionalString cfg.startWhenNeeded "-") +
+ "${cfgc.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") +
+ "-f /etc/ssh/sshd_config";
+ KillMode = "process";
+ } // (if cfg.startWhenNeeded then {
+ StandardInput = "socket";
+ StandardError = "journal";
+ } else {
+ Restart = "always";
+ Type = "simple";
+ });
+
+ };
+ in
+
+ if cfg.startWhenNeeded then {
+
+ sockets.sshd =
+ { description = "SSH Socket";
+ wantedBy = [ "sockets.target" ];
+ socketConfig.ListenStream = if cfg.listenAddresses != [] then
+ map (l: "${l.addr}:${toString (if l.port != null then l.port else 22)}") cfg.listenAddresses
+ else
+ cfg.ports;
+ socketConfig.Accept = true;
+ };
+
+ services."sshd@" = service;
+
+ } else {
+
+ services.sshd = service;
+
+ };
+
+ networking.firewall.allowedTCPPorts = if cfg.openFirewall then cfg.ports else [];
+
+ security.pam.services.sshd =
+ { startSession = true;
+ showMotd = true;
+ unixAuth = cfg.passwordAuthentication;
+ };
+
+ # These values are merged with the ones defined externally, see:
+ # https://github.com/NixOS/nixpkgs/pull/10155
+ # https://github.com/NixOS/nixpkgs/pull/41745
+ services.openssh.authorizedKeysFiles =
+ [ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ];
+
+ services.openssh.extraConfig = mkOrder 0
+ ''
+ UsePAM yes
+
+ AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
+ ${concatMapStrings (port: ''
+ Port ${toString port}
+ '') cfg.ports}
+
+ ${concatMapStrings ({ port, addr, ... }: ''
+ ListenAddress ${addr}${if port != null then ":" + toString port else ""}
+ '') cfg.listenAddresses}
+
+ ${optionalString cfgc.setXAuthLocation ''
+ XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
+ ''}
+
+ ${if cfg.forwardX11 then ''
+ X11Forwarding yes
+ '' else ''
+ X11Forwarding no
+ ''}
+
+ ${optionalString cfg.allowSFTP ''
+ Subsystem sftp ${cfgc.package}/libexec/sftp-server ${concatStringsSep " " cfg.sftpFlags}
+ ''}
+
+ PermitRootLogin ${cfg.permitRootLogin}
+ GatewayPorts ${cfg.gatewayPorts}
+ PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"}
+ ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"}
+
+ PrintMotd no # handled by pam_motd
+
+ AuthorizedKeysFile ${toString cfg.authorizedKeysFiles}
+
+ ${flip concatMapStrings cfg.hostKeys (k: ''
+ HostKey ${k.path}
+ '')}
+
+ KexAlgorithms ${concatStringsSep "," cfg.kexAlgorithms}
+ Ciphers ${concatStringsSep "," cfg.ciphers}
+ MACs ${concatStringsSep "," cfg.macs}
+
+ LogLevel ${cfg.logLevel}
+
+ ${if cfg.useDns then ''
+ UseDNS yes
+ '' else ''
+ UseDNS no
+ ''}
+
+ '';
+
+ assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
+ message = "cannot enable X11 forwarding without setting xauth location";}]
+ ++ forEach cfg.listenAddresses ({ addr, ... }: {
+ assertion = addr != null;
+ message = "addr must be specified in each listenAddresses entry";
+ });
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/sslh.nix b/nixpkgs/nixos/modules/services/networking/sslh.nix
new file mode 100644
index 00000000000..0222e8ce8b5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/sslh.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.sslh;
+ user = "sslh";
+ configFile = pkgs.writeText "sslh.conf" ''
+ verbose: ${boolToString cfg.verbose};
+ foreground: true;
+ inetd: false;
+ numeric: false;
+ transparent: ${boolToString cfg.transparent};
+ timeout: "${toString cfg.timeout}";
+
+ listen:
+ (
+ { host: "${cfg.listenAddress}"; port: "${toString cfg.port}"; }
+ );
+
+ ${cfg.appendConfig}
+ '';
+ defaultAppendConfig = ''
+ protocols:
+ (
+ { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
+ { name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; },
+ { name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; },
+ { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
+ { name: "ssl"; host: "localhost"; port: "443"; probe: "builtin"; },
+ { name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; }
+ );
+ '';
+in
+{
+ options = {
+ services.sslh = {
+ enable = mkEnableOption "sslh";
+
+ verbose = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Verbose logs.";
+ };
+
+ timeout = mkOption {
+ type = types.int;
+ default = 2;
+ description = "Timeout in seconds.";
+ };
+
+ transparent = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Will the services behind sslh (Apache, sshd and so on) see the external IP and ports as if the external world connected directly to them";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = "Listening address or hostname.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 443;
+ description = "Listening port.";
+ };
+
+ appendConfig = mkOption {
+ type = types.str;
+ default = defaultAppendConfig;
+ description = "Verbatim configuration file.";
+ };
+ };
+ };
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ users.users.${user} = {
+ description = "sslh daemon user";
+ isSystemUser = true;
+ };
+
+ systemd.services.sslh = {
+ description = "Applicative Protocol Multiplexer (e.g. share SSH and HTTPS on the same port)";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = user;
+ Group = "nogroup";
+ PermissionsStartOnly = true;
+ Restart = "always";
+ RestartSec = "1s";
+ ExecStart = "${pkgs.sslh}/bin/sslh -F${configFile}";
+ KillMode = "process";
+ AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID";
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectSystem = "full";
+ ProtectHome = true;
+ };
+ };
+ })
+
+ # code from https://github.com/yrutschle/sslh#transparent-proxy-support
+ # the only difference is using iptables mark 0x2 instead of 0x1 to avoid conflicts with nixos/nat module
+ (mkIf (cfg.enable && cfg.transparent) {
+ # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
+ boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1;
+ boot.kernel.sysctl."net.ipv4.conf.all.route_localnet" = 1;
+
+ systemd.services.sslh = let
+ iptablesCommands = [
+ # DROP martian packets as they would have been if route_localnet was zero
+ # Note: packets not leaving the server aren't affected by this, thus sslh will still work
+ { table = "raw"; command = "PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP"; }
+ { table = "mangle"; command = "POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP"; }
+ # Mark all connections made by ssl for special treatment (here sslh is run as user ${user})
+ { table = "nat"; command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f"; }
+ # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
+ { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; }
+ ];
+ ip6tablesCommands = [
+ { table = "raw"; command = "PREROUTING ! -i lo -d ::1/128 -j DROP"; }
+ { table = "mangle"; command = "POSTROUTING ! -o lo -s ::1/128 -j DROP"; }
+ { table = "nat"; command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f"; }
+ { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; }
+ ];
+ in {
+ path = [ pkgs.iptables pkgs.iproute pkgs.procps ];
+
+ preStart = ''
+ # Cleanup old iptables entries which might be still there
+ ${concatMapStringsSep "\n" ({table, command}: "while iptables -w -t ${table} -D ${command} 2>/dev/null; do echo; done") iptablesCommands}
+ ${concatMapStringsSep "\n" ({table, command}: "iptables -w -t ${table} -A ${command}" ) iptablesCommands}
+
+ # Configure routing for those marked packets
+ ip rule add fwmark 0x2 lookup 100
+ ip route add local 0.0.0.0/0 dev lo table 100
+
+ '' + optionalString config.networking.enableIPv6 ''
+ ${concatMapStringsSep "\n" ({table, command}: "while ip6tables -w -t ${table} -D ${command} 2>/dev/null; do echo; done") ip6tablesCommands}
+ ${concatMapStringsSep "\n" ({table, command}: "ip6tables -w -t ${table} -A ${command}" ) ip6tablesCommands}
+
+ ip -6 rule add fwmark 0x2 lookup 100
+ ip -6 route add local ::/0 dev lo table 100
+ '';
+
+ postStop = ''
+ ${concatMapStringsSep "\n" ({table, command}: "iptables -w -t ${table} -D ${command}") iptablesCommands}
+
+ ip rule del fwmark 0x2 lookup 100
+ ip route del local 0.0.0.0/0 dev lo table 100
+ '' + optionalString config.networking.enableIPv6 ''
+ ${concatMapStringsSep "\n" ({table, command}: "ip6tables -w -t ${table} -D ${command}") ip6tablesCommands}
+
+ ip -6 rule del fwmark 0x2 lookup 100
+ ip -6 route del local ::/0 dev lo table 100
+ '';
+ };
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix
new file mode 100644
index 00000000000..0fec3ef00ad
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix
@@ -0,0 +1,84 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with (import ./param-lib.nix lib);
+
+let
+ cfg = config.services.strongswan-swanctl;
+ swanctlParams = import ./swanctl-params.nix lib;
+in {
+ options.services.strongswan-swanctl = {
+ enable = mkEnableOption "strongswan-swanctl service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.strongswan;
+ defaultText = "pkgs.strongswan";
+ description = ''
+ The strongswan derivation to use.
+ '';
+ };
+
+ strongswan.extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Contents of the <literal>strongswan.conf</literal> file.
+ '';
+ };
+
+ swanctl = paramsToOptions swanctlParams;
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = !config.services.strongswan.enable;
+ message = "cannot enable both services.strongswan and services.strongswan-swanctl. Choose either one.";
+ }
+ ];
+
+ environment.etc."swanctl/swanctl.conf".text =
+ paramsToConf cfg.swanctl swanctlParams;
+
+ # The swanctl command complains when the following directories don't exist:
+ # See: https://wiki.strongswan.org/projects/strongswan/wiki/Swanctldirectory
+ system.activationScripts.strongswan-swanctl-etc = stringAfter ["etc"] ''
+ mkdir -p '/etc/swanctl/x509' # Trusted X.509 end entity certificates
+ mkdir -p '/etc/swanctl/x509ca' # Trusted X.509 Certificate Authority certificates
+ mkdir -p '/etc/swanctl/x509ocsp'
+ mkdir -p '/etc/swanctl/x509aa' # Trusted X.509 Attribute Authority certificates
+ mkdir -p '/etc/swanctl/x509ac' # Attribute Certificates
+ mkdir -p '/etc/swanctl/x509crl' # Certificate Revocation Lists
+ mkdir -p '/etc/swanctl/pubkey' # Raw public keys
+ mkdir -p '/etc/swanctl/private' # Private keys in any format
+ mkdir -p '/etc/swanctl/rsa' # PKCS#1 encoded RSA private keys
+ mkdir -p '/etc/swanctl/ecdsa' # Plain ECDSA private keys
+ mkdir -p '/etc/swanctl/bliss'
+ mkdir -p '/etc/swanctl/pkcs8' # PKCS#8 encoded private keys of any type
+ mkdir -p '/etc/swanctl/pkcs12' # PKCS#12 containers
+ '';
+
+ systemd.services.strongswan-swanctl = {
+ description = "strongSwan IPsec IKEv1/IKEv2 daemon using swanctl";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target" ];
+ path = with pkgs; [ kmod iproute iptables utillinux ];
+ environment = {
+ STRONGSWAN_CONF = pkgs.writeTextFile {
+ name = "strongswan.conf";
+ text = cfg.strongswan.extraConfig;
+ };
+ SWANCTL_DIR = "/etc/swanctl";
+ };
+ restartTriggers = [ config.environment.etc."swanctl/swanctl.conf".source ];
+ serviceConfig = {
+ ExecStart = "${cfg.package}/sbin/charon-systemd";
+ Type = "notify";
+ ExecStartPost = "${cfg.package}/sbin/swanctl --load-all --noprompt";
+ ExecReload = "${cfg.package}/sbin/swanctl --reload";
+ Restart = "on-abnormal";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
new file mode 100644
index 00000000000..dfdfc50d8ae
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
@@ -0,0 +1,162 @@
+# In the following context a parameter is an attribute set that
+# contains a NixOS option and a render function. It also contains the
+# attribute: '_type = "param"' so we can distinguish it from other
+# sets.
+#
+# The render function is used to convert the value of the option to a
+# snippet of strongswan.conf. Most parameters simply render their
+# value to a string. For example, take the following parameter:
+#
+# threads = mkIntParam 10 "Threads to use for request handling.";
+#
+# When a users defines the corresponding option as for example:
+#
+# services.strongswan-swanctl.strongswan.threads = 32;
+#
+# It will get rendered to the following snippet in strongswan.conf:
+#
+# threads = 32
+#
+# Some parameters however need to be able to change the attribute
+# name. For example, take the following parameter:
+#
+# id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") "...";
+#
+# A user can define the corresponding option as for example:
+#
+# id = {
+# "foo" = "bar";
+# "baz" = "qux";
+# };
+#
+# This will get rendered to the following snippet:
+#
+# foo-id = bar
+# baz-id = qux
+#
+# For this reason the render function is not simply a function from
+# value -> string but a function from a value to an attribute set:
+# { "${name}" = string }. This allows parameters to change the attribute
+# name like in the previous example.
+
+lib :
+
+with lib;
+with (import ./param-lib.nix lib);
+
+rec {
+ mkParamOfType = type : strongswanDefault : description : {
+ _type = "param";
+ option = mkOption {
+ type = types.nullOr type;
+ default = null;
+ description = documentDefault description strongswanDefault;
+ };
+ render = single toString;
+ };
+
+ documentDefault = description : strongswanDefault :
+ if strongswanDefault == null
+ then description
+ else description + ''
+ </para><para>
+ StrongSwan default: <literal><![CDATA[${builtins.toJSON strongswanDefault}]]></literal>
+ '';
+
+ single = f: name: value: { ${name} = f value; };
+
+ mkStrParam = mkParamOfType types.str;
+ mkOptionalStrParam = mkStrParam null;
+
+ mkEnumParam = values : mkParamOfType (types.enum values);
+
+ mkIntParam = mkParamOfType types.int;
+ mkOptionalIntParam = mkIntParam null;
+
+ # We should have floats in Nix...
+ mkFloatParam = mkStrParam;
+
+ # TODO: Check for hex format:
+ mkHexParam = mkStrParam;
+ mkOptionalHexParam = mkOptionalStrParam;
+
+ # TODO: Check for duration format:
+ mkDurationParam = mkStrParam;
+ mkOptionalDurationParam = mkOptionalStrParam;
+
+ mkYesNoParam = strongswanDefault : description : {
+ _type = "param";
+ option = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = documentDefault description strongswanDefault;
+ };
+ render = single (b: if b then "yes" else "no");
+ };
+ yes = true;
+ no = false;
+
+ mkSpaceSepListParam = mkSepListParam " ";
+ mkCommaSepListParam = mkSepListParam ",";
+
+ mkSepListParam = sep : strongswanDefault : description : {
+ _type = "param";
+ option = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = documentDefault description strongswanDefault;
+ };
+ render = single (value: concatStringsSep sep value);
+ };
+
+ mkAttrsOfParams = params :
+ mkAttrsOf params (types.submodule {options = paramsToOptions params;});
+
+ mkAttrsOfParam = param :
+ mkAttrsOf param param.option.type;
+
+ mkAttrsOf = param : option : description : {
+ _type = "param";
+ option = mkOption {
+ type = types.attrsOf option;
+ default = {};
+ inherit description;
+ };
+ render = single (attrs:
+ (paramsToRenderedStrings attrs
+ (mapAttrs (_n: _v: param) attrs)));
+ };
+
+ mkPrefixedAttrsOfParams = params :
+ mkPrefixedAttrsOf params (types.submodule {options = paramsToOptions params;});
+
+ mkPrefixedAttrsOfParam = param :
+ mkPrefixedAttrsOf param param.option.type;
+
+ mkPrefixedAttrsOf = p : option : description : {
+ _type = "param";
+ option = mkOption {
+ type = types.attrsOf option;
+ default = {};
+ inherit description;
+ };
+ render = prefix: attrs:
+ let prefixedAttrs = mapAttrs' (name: nameValuePair "${prefix}-${name}") attrs;
+ in paramsToRenderedStrings prefixedAttrs
+ (mapAttrs (_n: _v: p) prefixedAttrs);
+ };
+
+ mkPostfixedAttrsOfParams = params : description : {
+ _type = "param";
+ option = mkOption {
+ type = types.attrsOf (types.submodule {options = paramsToOptions params;});
+ default = {};
+ inherit description;
+ };
+ render = postfix: attrs:
+ let postfixedAttrs = mapAttrs' (name: nameValuePair "${name}-${postfix}") attrs;
+ in paramsToRenderedStrings postfixedAttrs
+ (mapAttrs (_n: _v: params) postfixedAttrs);
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix
new file mode 100644
index 00000000000..2bbb39a7604
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix
@@ -0,0 +1,82 @@
+lib :
+
+with lib;
+
+rec {
+ paramsToConf = cfg : ps : mkConf 0 (paramsToRenderedStrings cfg ps);
+
+ # mkConf takes an indentation level (which usually starts at 0) and a nested
+ # attribute set of strings and will render that set to a strongswan.conf style
+ # configuration format. For example:
+ #
+ # mkConf 0 {a = "1"; b = { c = { "foo" = "2"; "bar" = "3"; }; d = "4";};} => ''
+ # a = 1
+ # b {
+ # c {
+ # foo = 2
+ # bar = 3
+ # }
+ # d = 4
+ # }''
+ mkConf = indent : ps :
+ concatMapStringsSep "\n"
+ (name:
+ let value = ps.${name};
+ indentation = replicate indent " ";
+ in
+ indentation + (
+ if isAttrs value
+ then "${name} {\n" +
+ mkConf (indent + 2) value + "\n" +
+ indentation + "}"
+ else "${name} = ${value}"
+ )
+ )
+ (attrNames ps);
+
+ replicate = n : c : concatStrings (builtins.genList (_x : c) n);
+
+ # `paramsToRenderedStrings cfg ps` converts the NixOS configuration `cfg`
+ # (typically the "config" argument of a NixOS module) and the set of
+ # parameters `ps` (an attribute set where the values are constructed using the
+ # parameter constructors in ./param-constructors.nix) to a nested attribute
+ # set of strings (rendered parameters).
+ paramsToRenderedStrings = cfg : ps :
+ filterEmptySets (
+ (mapParamsRecursive (path: name: param:
+ let value = attrByPath path null cfg;
+ in optionalAttrs (value != null) (param.render name value)
+ ) ps));
+
+ filterEmptySets = set : filterAttrs (n: v: (v != null)) (mapAttrs (name: value:
+ if isAttrs value
+ then let value' = filterEmptySets value;
+ in if value' == {}
+ then null
+ else value'
+ else value
+ ) set);
+
+ # Recursively map over every parameter in the given attribute set.
+ mapParamsRecursive = mapAttrsRecursiveCond' (as: (!(as ? _type && as._type == "param")));
+
+ mapAttrsRecursiveCond' = cond: f: set:
+ let
+ recurse = path: set:
+ let
+ g =
+ name: value:
+ if isAttrs value && cond value
+ then { ${name} = recurse (path ++ [name]) value; }
+ else f (path ++ [name]) name value;
+ in mapAttrs'' g set;
+ in recurse [] set;
+
+ mapAttrs'' = f: set:
+ foldl' (a: b: a // b) {} (map (attr: f attr set.${attr}) (attrNames set));
+
+ # Extract the options from the given set of parameters.
+ paramsToOptions = ps :
+ mapParamsRecursive (_path: name: param: { ${name} = param.option; }) ps;
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
new file mode 100644
index 00000000000..808cb863a9c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
@@ -0,0 +1,1300 @@
+# See: https://wiki.strongswan.org/projects/strongswan/wiki/Swanctlconf
+#
+# When strongSwan is upgraded please update the parameters in this file. You can
+# see which parameters should be deleted, changed or added by diffing
+# swanctl.opt:
+#
+# git clone https://github.com/strongswan/strongswan.git
+# cd strongswan
+# git diff 5.7.2..5.8.0 src/swanctl/swanctl.opt
+
+lib: with (import ./param-constructors.nix lib);
+
+let
+ certParams = {
+ file = mkOptionalStrParam ''
+ Absolute path to the certificate to load. Passed as-is to the daemon, so
+ it must be readable by it.
+ </para><para>
+ Configure either this or <option>handle</option>, but not both, in one section.
+ '';
+
+ handle = mkOptionalHexParam ''
+ Hex-encoded CKA_ID or handle of the certificate on a token or TPM,
+ respectively.
+ </para><para>
+ Configure either this or <option>file</option>, but not both, in one section.
+ '';
+
+ slot = mkOptionalIntParam ''
+ Optional slot number of the token that stores the certificate.
+ '';
+
+ module = mkOptionalStrParam ''
+ Optional PKCS#11 module name.
+ '';
+ };
+in {
+ authorities = mkAttrsOfParams ({
+
+ cacert = mkOptionalStrParam ''
+ The certificates may use a relative path from the swanctl
+ <literal>x509ca</literal> directory or an absolute path.
+ </para><para>
+ Configure one of <option>cacert</option>,
+ <option>file</option>, or
+ <option>handle</option> per section.
+ '';
+
+ cert_uri_base = mkOptionalStrParam ''
+ Defines the base URI for the Hash and URL feature supported by
+ IKEv2. Instead of exchanging complete certificates, IKEv2 allows one to
+ send an URI that resolves to the DER encoded certificate. The certificate
+ URIs are built by appending the SHA1 hash of the DER encoded certificates
+ to this base URI.
+ '';
+
+ crl_uris = mkCommaSepListParam [] ''
+ List of CRL distribution points (ldap, http, or file URI).
+ '';
+
+ ocsp_uris = mkCommaSepListParam [] ''
+ List of OCSP URIs.
+ '';
+
+ } // certParams) ''
+ Section defining complementary attributes of certification authorities, each
+ in its own subsection with an arbitrary yet unique name
+ '';
+
+ connections = mkAttrsOfParams {
+
+ version = mkIntParam 0 ''
+ IKE major version to use for connection.
+ <itemizedlist>
+ <listitem><para>1 uses IKEv1 aka ISAKMP,</para></listitem>
+ <listitem><para>2 uses IKEv2.</para></listitem>
+ <listitem><para>A connection using the default of 0 accepts both IKEv1 and IKEv2 as
+ responder, and initiates the connection actively with IKEv2.</para></listitem>
+ </itemizedlist>
+ '';
+
+ local_addrs = mkCommaSepListParam [] ''
+ Local address(es) to use for IKE communication. Takes
+ single IPv4/IPv6 addresses, DNS names, CIDR subnets or IP address ranges.
+ </para><para>
+ As initiator, the first non-range/non-subnet is used to initiate the
+ connection from. As responder, the local destination address must match at
+ least to one of the specified addresses, subnets or ranges.
+ </para><para>
+ If FQDNs are assigned they are resolved every time a configuration lookup
+ is done. If DNS resolution times out, the lookup is delayed for that time.
+ '';
+
+ remote_addrs = mkCommaSepListParam [] ''
+ Remote address(es) to use for IKE communication. Takes
+ single IPv4/IPv6 addresses, DNS names, CIDR subnets or IP address ranges.
+ </para><para>
+ As initiator, the first non-range/non-subnet is used to initiate the
+ connection to. As responder, the initiator source address must match at
+ least to one of the specified addresses, subnets or ranges.
+ </para><para>
+ If FQDNs are assigned they are resolved every time a configuration lookup
+ is done. If DNS resolution times out, the lookup is delayed for that time.
+ To initiate a connection, at least one specific address or DNS name must
+ be specified.
+ '';
+
+ local_port = mkIntParam 500 ''
+ Local UDP port for IKE communication. By default the port of the socket
+ backend is used, which is usually <literal>500</literal>. If port
+ <literal>500</literal> is used, automatic IKE port floating to port
+ <literal>4500</literal> is used to work around NAT issues.
+ </para><para>
+ Using a non-default local IKE port requires support from the socket
+ backend in use (socket-dynamic).
+ '';
+
+ remote_port = mkIntParam 500 ''
+ Remote UDP port for IKE communication. If the default of port
+ <literal>500</literal> is used, automatic IKE port floating to port
+ <literal>4500</literal> is used to work around NAT issues.
+ '';
+
+ proposals = mkCommaSepListParam ["default"] ''
+ A proposal is a set of algorithms. For non-AEAD algorithms, this includes
+ for IKE an encryption algorithm, an integrity algorithm, a pseudo random
+ function and a Diffie-Hellman group. For AEAD algorithms, instead of
+ encryption and integrity algorithms, a combined algorithm is used.
+ </para><para>
+ In IKEv2, multiple algorithms of the same kind can be specified in a
+ single proposal, from which one gets selected. In IKEv1, only one
+ algorithm per kind is allowed per proposal, more algorithms get implicitly
+ stripped. Use multiple proposals to offer different algorithms
+ combinations in IKEv1.
+ </para><para>
+ Algorithm keywords get separated using dashes. Multiple proposals may be
+ specified in a list. The special value <literal>default</literal> forms a
+ default proposal of supported algorithms considered safe, and is usually a
+ good choice for interoperability.
+ '';
+
+ vips = mkCommaSepListParam [] ''
+ List of virtual IPs to request in IKEv2 configuration payloads or IKEv1
+ Mode Config. The wildcard addresses <literal>0.0.0.0</literal> and
+ <literal>::</literal> request an arbitrary address, specific addresses may
+ be defined. The responder may return a different address, though, or none
+ at all.
+ '';
+
+ aggressive = mkYesNoParam no ''
+ Enables Aggressive Mode instead of Main Mode with Identity
+ Protection. Aggressive Mode is considered less secure, because the ID and
+ HASH payloads are exchanged unprotected. This allows a passive attacker to
+ snoop peer identities, and even worse, start dictionary attacks on the
+ Preshared Key.
+ '';
+
+ pull = mkYesNoParam yes ''
+ If the default of yes is used, Mode Config works in pull mode, where the
+ initiator actively requests a virtual IP. With no, push mode is used,
+ where the responder pushes down a virtual IP to the initiating peer.
+ </para><para>
+ Push mode is currently supported for IKEv1, but not in IKEv2. It is used
+ by a few implementations only, pull mode is recommended.
+ '';
+
+ dscp = mkStrParam "000000" ''
+ Differentiated Services Field Codepoint to set on outgoing IKE packets for
+ this connection. The value is a six digit binary encoded string specifying
+ the Codepoint to set, as defined in RFC 2474.
+ '';
+
+ encap = mkYesNoParam no ''
+ To enforce UDP encapsulation of ESP packets, the IKE daemon can fake the
+ NAT detection payloads. This makes the peer believe that NAT takes place
+ on the path, forcing it to encapsulate ESP packets in UDP.
+ </para><para>
+ Usually this is not required, but it can help to work around connectivity
+ issues with too restrictive intermediary firewalls.
+ '';
+
+ mobike = mkYesNoParam yes ''
+ Enables MOBIKE on IKEv2 connections. MOBIKE is enabled by default on IKEv2
+ connections, and allows mobility of clients and multi-homing on servers by
+ migrating active IPsec tunnels.
+ </para><para>
+ Usually keeping MOBIKE enabled is unproblematic, as it is not used if the
+ peer does not indicate support for it. However, due to the design of
+ MOBIKE, IKEv2 always floats to port 4500 starting from the second
+ exchange. Some implementations don't like this behavior, hence it can be
+ disabled.
+ '';
+
+ dpd_delay = mkDurationParam "0s" ''
+ Interval to check the liveness of a peer actively using IKEv2
+ INFORMATIONAL exchanges or IKEv1 R_U_THERE messages. Active DPD checking
+ is only enforced if no IKE or ESP/AH packet has been received for the
+ configured DPD delay.
+ '';
+
+ dpd_timeout = mkDurationParam "0s" ''
+ Charon by default uses the normal retransmission mechanism and timeouts to
+ check the liveness of a peer, as all messages are used for liveness
+ checking. For compatibility reasons, with IKEv1 a custom interval may be
+ specified; this option has no effect on connections using IKEv2.
+ '';
+
+ fragmentation = mkEnumParam ["yes" "accept" "force" "no"] "yes" ''
+ Use IKE fragmentation (proprietary IKEv1 extension or RFC 7383 IKEv2
+ fragmentation). Acceptable values are <literal>yes</literal> (the default
+ since 5.5.1), <literal>accept</literal> (since versions:5.5.3),
+ <literal>force</literal> and <literal>no</literal>.
+ <itemizedlist>
+ <listitem><para>If set to <literal>yes</literal>, and the peer
+ supports it, oversized IKE messages will be sent in fragments.</para></listitem>
+ <listitem><para>If set to
+ <literal>accept</literal>, support for fragmentation is announced to the peer but the daemon
+ does not send its own messages in fragments.</para></listitem>
+ <listitem><para>If set to <literal>force</literal> (only
+ supported for IKEv1) the initial IKE message will already be fragmented if
+ required.</para></listitem>
+ <listitem><para>Finally, setting the option to <literal>no</literal> will disable announcing
+ support for this feature.</para></listitem>
+ </itemizedlist>
+ </para><para>
+ Note that fragmented IKE messages sent by a peer are always processed
+ irrespective of the value of this option (even when set to no).
+ '';
+
+ childless = mkEnumParam [ "allow" "force" "never" ] "allow" ''
+ Use childless IKE_SA initiation (RFC 6023) for IKEv2. Acceptable values
+ are <literal>allow</literal> (the default), <literal>force</literal> and
+ <literal>never</literal>. If set to <literal>allow</literal>, responders
+ will accept childless IKE_SAs (as indicated via notify in the IKE_SA_INIT
+ response) while initiators continue to create regular IKE_SAs with the
+ first CHILD_SA created during IKE_AUTH, unless the IKE_SA is initiated
+ explicitly without any children (which will fail if the responder does not
+ support or has disabled this extension). If set to
+ <literal>force</literal>, only childless initiation is accepted and the
+ first CHILD_SA is created with a separate CREATE_CHILD_SA exchange
+ (e.g. to use an independent DH exchange for all CHILD_SAs). Finally,
+ setting the option to <literal>never</literal> disables support for
+ childless IKE_SAs as responder.
+ '';
+
+ send_certreq = mkYesNoParam yes ''
+ Send certificate request payloads to offer trusted root CA certificates to
+ the peer. Certificate requests help the peer to choose an appropriate
+ certificate/private key for authentication and are enabled by default.
+ Disabling certificate requests can be useful if too many trusted root CA
+ certificates are installed, as each certificate request increases the size
+ of the initial IKE packets.
+ '';
+
+ send_cert = mkEnumParam ["always" "never" "ifasked" ] "ifasked" ''
+ Send certificate payloads when using certificate authentication.
+ <itemizedlist>
+ <listitem><para>With the default of <literal>ifasked</literal> the daemon sends
+ certificate payloads only if certificate requests have been received.</para></listitem>
+ <listitem><para><literal>never</literal> disables sending of certificate payloads
+ altogether,</para></listitem>
+ <listitem><para><literal>always</literal> causes certificate payloads to be sent
+ unconditionally whenever certificate authentication is used.</para></listitem>
+ </itemizedlist>
+ '';
+
+ ppk_id = mkOptionalStrParam ''
+ String identifying the Postquantum Preshared Key (PPK) to be used.
+ '';
+
+ ppk_required = mkYesNoParam no ''
+ Whether a Postquantum Preshared Key (PPK) is required for this connection.
+ '';
+
+ keyingtries = mkIntParam 1 ''
+ Number of retransmission sequences to perform during initial
+ connect. Instead of giving up initiation after the first retransmission
+ sequence with the default value of <literal>1</literal>, additional
+ sequences may be started according to the configured value. A value of
+ <literal>0</literal> initiates a new sequence until the connection
+ establishes or fails with a permanent error.
+ '';
+
+ unique = mkEnumParam ["no" "never" "keep" "replace"] "no" ''
+ Connection uniqueness policy to enforce. To avoid multiple connections
+ from the same user, a uniqueness policy can be enforced.
+ </para><para>
+ <itemizedlist>
+ <listitem><para>
+ The value <literal>never</literal> does never enforce such a policy, even
+ if a peer included INITIAL_CONTACT notification messages,
+ </para></listitem>
+ <listitem><para>
+ whereas <literal>no</literal> replaces existing connections for the same
+ identity if a new one has the INITIAL_CONTACT notify.
+ </para></listitem>
+ <listitem><para>
+ <literal>keep</literal> rejects new connection attempts if the same user
+ already has an active connection,
+ </para></listitem>
+ <listitem><para>
+ <literal>replace</literal> deletes any existing connection if a new one
+ for the same user gets established.
+ </para></listitem>
+ </itemizedlist>
+ To compare connections for uniqueness, the remote IKE identity is used. If
+ EAP or XAuth authentication is involved, the EAP-Identity or XAuth
+ username is used to enforce the uniqueness policy instead.
+ </para><para>
+ On initiators this setting specifies whether an INITIAL_CONTACT notify is
+ sent during IKE_AUTH if no existing connection is found with the remote
+ peer (determined by the identities of the first authentication
+ round). Unless set to <literal>never</literal> the client will send a notify.
+ '';
+
+ reauth_time = mkDurationParam "0s" ''
+ Time to schedule IKE reauthentication. IKE reauthentication recreates the
+ IKE/ISAKMP SA from scratch and re-evaluates the credentials. In asymmetric
+ configurations (with EAP or configuration payloads) it might not be
+ possible to actively reauthenticate as responder. The IKEv2
+ reauthentication lifetime negotiation can instruct the client to perform
+ reauthentication.
+ </para><para>
+ Reauthentication is disabled by default. Enabling it usually may lead to
+ small connection interruptions, as strongSwan uses a break-before-make
+ policy with IKEv2 to avoid any conflicts with associated tunnel resources.
+ '';
+
+ rekey_time = mkDurationParam "4h" ''
+ IKE rekeying refreshes key material using a Diffie-Hellman exchange, but
+ does not re-check associated credentials. It is supported in IKEv2 only,
+ IKEv1 performs a reauthentication procedure instead.
+ </para><para>
+ With the default value IKE rekeying is scheduled every 4 hours, minus the
+ configured rand_time. If a reauth_time is configured, rekey_time defaults
+ to zero, disabling rekeying; explicitly set both to enforce rekeying and
+ reauthentication.
+ '';
+
+ over_time = mkOptionalDurationParam ''
+ Hard IKE_SA lifetime if rekey/reauth does not complete, as time. To avoid
+ having an IKE/ISAKMP kept alive if IKE reauthentication or rekeying fails
+ perpetually, a maximum hard lifetime may be specified. If the IKE_SA fails
+ to rekey or reauthenticate within the specified time, the IKE_SA gets
+ closed.
+ </para><para>
+ In contrast to CHILD_SA rekeying, over_time is relative in time to the
+ rekey_time and reauth_time values, as it applies to both.
+ </para><para>
+ The default is 10% of the longer of <option>rekey_time</option> and
+ <option>reauth_time</option>.
+ '';
+
+ rand_time = mkOptionalDurationParam ''
+ Time range from which to choose a random value to subtract from
+ rekey/reauth times. To avoid having both peers initiating the rekey/reauth
+ procedure simultaneously, a random time gets subtracted from the
+ rekey/reauth times.
+ </para><para>
+ The default is equal to the configured <option>over_time</option>.
+ '';
+
+ pools = mkCommaSepListParam [] ''
+ List of named IP pools to allocate virtual IP addresses
+ and other configuration attributes from. Each name references a pool by
+ name from either the pools section or an external pool.
+ '';
+
+ if_id_in = mkStrParam "0" ''
+ XFRM interface ID set on inbound policies/SA, can be overridden by child
+ config, see there for details.
+ '';
+
+ if_id_out = mkStrParam "0" ''
+ XFRM interface ID set on outbound policies/SA, can be overridden by child
+ config, see there for details.
+ '';
+
+ mediation = mkYesNoParam no ''
+ Whether this connection is a mediation connection, that is, whether this
+ connection is used to mediate other connections using the IKEv2 Mediation
+ Extension. Mediation connections create no CHILD_SA.
+ '';
+
+ mediated_by = mkOptionalStrParam ''
+ The name of the connection to mediate this connection through. If given,
+ the connection will be mediated through the named mediation
+ connection. The mediation connection must have mediation enabled.
+ '';
+
+ mediation_peer = mkOptionalStrParam ''
+ Identity under which the peer is registered at the mediation server, that
+ is, the IKE identity the other end of this connection uses as its local
+ identity on its connection to the mediation server. This is the identity
+ we request the mediation server to mediate us with. Only relevant on
+ connections that set mediated_by. If it is not given, the remote IKE
+ identity of the first authentication round of this connection will be
+ used.
+ '';
+
+ local = mkPrefixedAttrsOfParams {
+
+ round = mkIntParam 0 ''
+ Optional numeric identifier by which authentication rounds are
+ sorted. If not specified rounds are ordered by their position in the
+ config file/vici message.
+ '';
+
+ certs = mkCommaSepListParam [] ''
+ List of certificate candidates to use for
+ authentication. The certificates may use a relative path from the
+ swanctl <literal>x509</literal> directory or an absolute path.
+ </para><para>
+ The certificate used for authentication is selected based on the
+ received certificate request payloads. If no appropriate CA can be
+ located, the first certificate is used.
+ '';
+
+ cert = mkPostfixedAttrsOfParams certParams ''
+ Section for a certificate candidate to use for
+ authentication. Certificates in certs are transmitted as binary blobs,
+ these sections offer more flexibility.
+ '';
+
+ pubkeys = mkCommaSepListParam [] ''
+ List of raw public key candidates to use for
+ authentication. The public keys may use a relative path from the swanctl
+ <literal>pubkey</literal> directory or an absolute path.
+ </para><para>
+ Even though multiple local public keys could be defined in principle,
+ only the first public key in the list is used for authentication.
+ '';
+
+ auth = mkStrParam "pubkey" ''
+ Authentication to perform locally.
+ <itemizedlist>
+ <listitem><para>
+ The default <literal>pubkey</literal> uses public key authentication
+ using a private key associated to a usable certificate.
+ </para></listitem>
+ <listitem><para>
+ <literal>psk</literal> uses pre-shared key authentication.
+ </para></listitem>
+ <listitem><para>
+ The IKEv1 specific <literal>xauth</literal> is used for XAuth or Hybrid
+ authentication,
+ </para></listitem>
+ <listitem><para>
+ while the IKEv2 specific <literal>eap</literal> keyword defines EAP
+ authentication.
+ </para></listitem>
+ <listitem><para>
+ For <literal>xauth</literal>, a specific backend name may be appended,
+ separated by a dash. The appropriate <literal>xauth</literal> backend is
+ selected to perform the XAuth exchange. For traditional XAuth, the
+ <literal>xauth</literal> method is usually defined in the second
+ authentication round following an initial <literal>pubkey</literal> (or
+ <literal>psk</literal>) round. Using <literal>xauth</literal> in the
+ first round performs Hybrid Mode client authentication.
+ </para></listitem>
+ <listitem><para>
+ For <literal>eap</literal>, a specific EAP method name may be appended, separated by a
+ dash. An EAP module implementing the appropriate method is selected to
+ perform the EAP conversation.
+ </para></listitem>
+ <listitem><para>
+ Since 5.4.0, if both peers support RFC 7427 ("Signature Authentication
+ in IKEv2") specific hash algorithms to be used during IKEv2
+ authentication may be configured. To do so use <literal>ike:</literal>
+ followed by a trust chain signature scheme constraint (see description
+ of the <option>remote</option> section's <option>auth</option>
+ keyword). For example, with <literal>ike:pubkey-sha384-sha256</literal>
+ a public key signature scheme with either SHA-384 or SHA-256 would get
+ used for authentication, in that order and depending on the hash
+ algorithms supported by the peer. If no specific hash algorithms are
+ configured, the default is to prefer an algorithm that matches or
+ exceeds the strength of the signature key. If no constraints with
+ <literal>ike:</literal> prefix are configured any signature scheme
+ constraint (without <literal>ike:</literal> prefix) will also apply to
+ IKEv2 authentication, unless this is disabled in
+ <literal>strongswan.conf</literal>. To use RSASSA-PSS signatures use
+ <literal>rsa/pss</literal> instead of <literal>pubkey</literal> or
+ <literal>rsa</literal> as in e.g.
+ <literal>ike:rsa/pss-sha256</literal>. If <literal>pubkey</literal> or
+ <literal>rsa</literal> constraints are configured RSASSA-PSS signatures
+ will only be used if enabled in <literal>strongswan.conf</literal>(5).
+ </para></listitem>
+ </itemizedlist>
+ '';
+
+ id = mkOptionalStrParam ''
+ IKE identity to use for authentication round. When using certificate
+ authentication, the IKE identity must be contained in the certificate,
+ either as subject or as subjectAltName.
+ '';
+
+ eap_id = mkOptionalStrParam ''
+ Client EAP-Identity to use in EAP-Identity exchange and the EAP method.
+ '';
+
+ aaa_id = mkOptionalStrParam ''
+ Server side EAP-Identity to expect in the EAP method. Some EAP methods,
+ such as EAP-TLS, use an identity for the server to perform mutual
+ authentication. This identity may differ from the IKE identity,
+ especially when EAP authentication is delegated from the IKE responder
+ to an AAA backend.
+ </para><para>
+ For EAP-(T)TLS, this defines the identity for which the server must
+ provide a certificate in the TLS exchange.
+ '';
+
+ xauth_id = mkOptionalStrParam ''
+ Client XAuth username used in the XAuth exchange.
+ '';
+
+ } ''
+ Section for a local authentication round. A local authentication round
+ defines the rules how authentication is performed for the local
+ peer. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple
+ Authentication or IKEv1 XAuth.
+ </para><para>
+ Each round is defined in a section having <literal>local</literal> as
+ prefix, and an optional unique suffix. To define a single authentication
+ round, the suffix may be omitted.
+ '';
+
+ remote = mkPrefixedAttrsOfParams {
+
+ round = mkIntParam 0 ''
+ Optional numeric identifier by which authentication rounds are
+ sorted. If not specified rounds are ordered by their position in the
+ config file/vici message.
+ '';
+
+ id = mkStrParam "%any" ''
+ IKE identity to expect for authentication round. When using certificate
+ authentication, the IKE identity must be contained in the certificate,
+ either as subject or as subjectAltName.
+ '';
+
+ eap_id = mkOptionalStrParam ''
+ Identity to use as peer identity during EAP authentication. If set to
+ <literal>%any</literal> the EAP-Identity method will be used to ask the
+ client for an EAP identity.
+ '';
+
+ groups = mkCommaSepListParam [] ''
+ Authorization group memberships to require. The peer
+ must prove membership to at least one of the specified groups. Group
+ membership can be certified by different means, for example by
+ appropriate Attribute Certificates or by an AAA backend involved in the
+ authentication.
+ '';
+
+ cert_policy = mkCommaSepListParam [] ''
+ List of certificate policy OIDs the peer's certificate
+ must have. OIDs are specified using the numerical dotted representation.
+ '';
+
+ certs = mkCommaSepListParam [] ''
+ List of certificates to accept for authentication. The certificates may
+ use a relative path from the swanctl <literal>x509</literal> directory
+ or an absolute path.
+ '';
+
+ cert = mkPostfixedAttrsOfParams certParams ''
+ Section for a certificate candidate to use for
+ authentication. Certificates in certs are transmitted as binary blobs,
+ these sections offer more flexibility.
+ '';
+
+ cacerts = mkCommaSepListParam [] ''
+ List of CA certificates to accept for
+ authentication. The certificates may use a relative path from the
+ swanctl <literal>x509ca</literal> directory or an absolute path.
+ '';
+
+ cacert = mkPostfixedAttrsOfParams certParams ''
+ Section for a CA certificate to accept for authentication. Certificates
+ in cacerts are transmitted as binary blobs, these sections offer more
+ flexibility.
+ '';
+
+ pubkeys = mkCommaSepListParam [] ''
+ List of raw public keys to accept for
+ authentication. The public keys may use a relative path from the swanctl
+ <literal>pubkey</literal> directory or an absolute path.
+ '';
+
+ revocation = mkEnumParam ["strict" "ifuri" "relaxed"] "relaxed" ''
+ Certificate revocation policy for CRL or OCSP revocation.
+ <itemizedlist>
+ <listitem><para>
+ A <literal>strict</literal> revocation policy fails if no revocation information is
+ available, i.e. the certificate is not known to be unrevoked.
+ </para></listitem>
+ <listitem><para>
+ <literal>ifuri</literal> fails only if a CRL/OCSP URI is available, but certificate
+ revocation checking fails, i.e. there should be revocation information
+ available, but it could not be obtained.
+ </para></listitem>
+ <listitem><para>
+ The default revocation policy <literal>relaxed</literal> fails only if a certificate is
+ revoked, i.e. it is explicitly known that it is bad.
+ </para></listitem>
+ </itemizedlist>
+ '';
+
+ auth = mkStrParam "pubkey" ''
+ Authentication to expect from remote. See the <option>local</option>
+ section's <option>auth</option> keyword description about the details of
+ supported mechanisms.
+ </para><para>
+ Since 5.4.0, to require a trustchain public key strength for the remote
+ side, specify the key type followed by the minimum strength in bits (for
+ example <literal>ecdsa-384</literal> or
+ <literal>rsa-2048-ecdsa-256</literal>). To limit the acceptable set of
+ hashing algorithms for trustchain validation, append hash algorithms to
+ pubkey or a key strength definition (for example
+ <literal>pubkey-sha256-sha512</literal>,
+ <literal>rsa-2048-sha256-sha384-sha512</literal> or
+ <literal>rsa-2048-sha256-ecdsa-256-sha256-sha384</literal>).
+ Unless disabled in <literal>strongswan.conf</literal>, or explicit IKEv2
+ signature constraints are configured (refer to the description of the
+ <option>local</option> section's <option>auth</option> keyword for
+ details), such key types and hash algorithms are also applied as
+ constraints against IKEv2 signature authentication schemes used by the
+ remote side. To require RSASSA-PSS signatures use
+ <literal>rsa/pss</literal> instead of <literal>pubkey</literal> or
+ <literal>rsa</literal> as in e.g. <literal>rsa/pss-sha256</literal>. If
+ <literal>pubkey</literal> or <literal>rsa</literal> constraints are
+ configured RSASSA-PSS signatures will only be accepted if enabled in
+ <literal>strongswan.conf</literal>(5).
+ </para><para>
+ To specify trust chain constraints for EAP-(T)TLS, append a colon to the
+ EAP method, followed by the key type/size and hash algorithm as
+ discussed above (e.g. <literal>eap-tls:ecdsa-384-sha384</literal>).
+ '';
+
+ } ''
+ Section for a remote authentication round. A remote authentication round
+ defines the constraints how the peers must authenticate to use this
+ connection. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple
+ Authentication or IKEv1 XAuth.
+ </para><para>
+ Each round is defined in a section having <literal>remote</literal> as
+ prefix, and an optional unique suffix. To define a single authentication
+ round, the suffix may be omitted.
+ '';
+
+ children = mkAttrsOfParams {
+ ah_proposals = mkCommaSepListParam [] ''
+ AH proposals to offer for the CHILD_SA. A proposal is a set of
+ algorithms. For AH, this includes an integrity algorithm and an optional
+ Diffie-Hellman group. If a DH group is specified, CHILD_SA/Quick Mode
+ rekeying and initial negotiation uses a separate Diffie-Hellman exchange
+ using the specified group (refer to esp_proposals for details).
+ </para><para>
+ In IKEv2, multiple algorithms of the same kind can be specified in a
+ single proposal, from which one gets selected. In IKEv1, only one
+ algorithm per kind is allowed per proposal, more algorithms get
+ implicitly stripped. Use multiple proposals to offer different algorithms
+ combinations in IKEv1.
+ </para><para>
+ Algorithm keywords get separated using dashes. Multiple proposals may be
+ specified in a list. The special value <literal>default</literal> forms
+ a default proposal of supported algorithms considered safe, and is
+ usually a good choice for interoperability. By default no AH proposals
+ are included, instead ESP is proposed.
+ '';
+
+ esp_proposals = mkCommaSepListParam ["default"] ''
+ ESP proposals to offer for the CHILD_SA. A proposal is a set of
+ algorithms. For ESP non-AEAD proposals, this includes an integrity
+ algorithm, an encryption algorithm, an optional Diffie-Hellman group and
+ an optional Extended Sequence Number Mode indicator. For AEAD proposals,
+ a combined mode algorithm is used instead of the separate
+ encryption/integrity algorithms.
+ </para><para>
+ If a DH group is specified, CHILD_SA/Quick Mode rekeying and initial
+ negotiation use a separate Diffie-Hellman exchange using the specified
+ group. However, for IKEv2, the keys of the CHILD_SA created implicitly
+ with the IKE_SA will always be derived from the IKE_SA's key material. So
+ any DH group specified here will only apply when the CHILD_SA is later
+ rekeyed or is created with a separate CREATE_CHILD_SA exchange. A
+ proposal mismatch might, therefore, not immediately be noticed when the
+ SA is established, but may later cause rekeying to fail.
+ </para><para>
+ Extended Sequence Number support may be indicated with the
+ <literal>esn</literal> and <literal>noesn</literal> values, both may be
+ included to indicate support for both modes. If omitted,
+ <literal>noesn</literal> is assumed.
+ </para><para>
+ In IKEv2, multiple algorithms of the same kind can be specified in a
+ single proposal, from which one gets selected. In IKEv1, only one
+ algorithm per kind is allowed per proposal, more algorithms get
+ implicitly stripped. Use multiple proposals to offer different algorithms
+ combinations in IKEv1.
+ </para><para>
+ Algorithm keywords get separated using dashes. Multiple proposals may be
+ specified as a list. The special value <literal>default</literal> forms
+ a default proposal of supported algorithms considered safe, and is
+ usually a good choice for interoperability. If no algorithms are
+ specified for AH nor ESP, the default set of algorithms for ESP is
+ included.
+ '';
+
+ sha256_96 = mkYesNoParam no ''
+ HMAC-SHA-256 is used with 128-bit truncation with IPsec. For
+ compatibility with implementations that incorrectly use 96-bit truncation
+ this option may be enabled to configure the shorter truncation length in
+ the kernel. This is not negotiated, so this only works with peers that
+ use the incorrect truncation length (or have this option enabled).
+ '';
+
+ local_ts = mkCommaSepListParam ["dynamic"] ''
+ List of local traffic selectors to include in CHILD_SA. Each selector is
+ a CIDR subnet definition, followed by an optional proto/port
+ selector. The special value <literal>dynamic</literal> may be used
+ instead of a subnet definition, which gets replaced by the tunnel outer
+ address or the virtual IP, if negotiated. This is the default.
+ </para><para>
+ A protocol/port selector is surrounded by opening and closing square
+ brackets. Between these brackets, a numeric or getservent(3) protocol
+ name may be specified. After the optional protocol restriction, an
+ optional port restriction may be specified, separated by a slash. The
+ port restriction may be numeric, a getservent(3) service name, or the
+ special value <literal>opaque</literal> for RFC 4301 OPAQUE
+ selectors. Port ranges may be specified as well, none of the kernel
+ backends currently support port ranges, though.
+ </para><para>
+ When IKEv1 is used only the first selector is interpreted, except if the
+ Cisco Unity extension plugin is used. This is due to a limitation of the
+ IKEv1 protocol, which only allows a single pair of selectors per
+ CHILD_SA. So to tunnel traffic matched by several pairs of selectors when
+ using IKEv1 several children (CHILD_SAs) have to be defined that cover
+ the selectors. The IKE daemon uses traffic selector narrowing for IKEv1,
+ the same way it is standardized and implemented for IKEv2. However, this
+ may lead to problems with other implementations. To avoid that, configure
+ identical selectors in such scenarios.
+ '';
+
+ remote_ts = mkCommaSepListParam ["dynamic"] ''
+ List of remote selectors to include in CHILD_SA. See
+ <option>local_ts</option> for a description of the selector syntax.
+ '';
+
+ rekey_time = mkDurationParam "1h" ''
+ Time to schedule CHILD_SA rekeying. CHILD_SA rekeying refreshes key
+ material, optionally using a Diffie-Hellman exchange if a group is
+ specified in the proposal. To avoid rekey collisions initiated by both
+ ends simultaneously, a value in the range of <option>rand_time</option>
+ gets subtracted to form the effective soft lifetime.
+ </para><para>
+ By default CHILD_SA rekeying is scheduled every hour, minus
+ <option>rand_time</option>.
+ '';
+
+ life_time = mkOptionalDurationParam ''
+ Maximum lifetime before CHILD_SA gets closed. Usually this hard lifetime
+ is never reached, because the CHILD_SA gets rekeyed before. If that fails
+ for whatever reason, this limit closes the CHILD_SA. The default is 10%
+ more than the <option>rekey_time</option>.
+ '';
+
+ rand_time = mkOptionalDurationParam ''
+ Time range from which to choose a random value to subtract from
+ <option>rekey_time</option>. The default is the difference between
+ <option>life_time</option> and <option>rekey_time</option>.
+ '';
+
+ rekey_bytes = mkIntParam 0 ''
+ Number of bytes processed before initiating CHILD_SA rekeying. CHILD_SA
+ rekeying refreshes key material, optionally using a Diffie-Hellman
+ exchange if a group is specified in the proposal.
+ </para><para>
+ To avoid rekey collisions initiated by both ends simultaneously, a value
+ in the range of <option>rand_bytes</option> gets subtracted to form the
+ effective soft volume limit.
+ </para><para>
+ Volume based CHILD_SA rekeying is disabled by default.
+ '';
+
+ life_bytes = mkOptionalIntParam ''
+ Maximum bytes processed before CHILD_SA gets closed. Usually this hard
+ volume limit is never reached, because the CHILD_SA gets rekeyed
+ before. If that fails for whatever reason, this limit closes the
+ CHILD_SA. The default is 10% more than <option>rekey_bytes</option>.
+ '';
+
+ rand_bytes = mkOptionalIntParam ''
+ Byte range from which to choose a random value to subtract from
+ <option>rekey_bytes</option>. The default is the difference between
+ <option>life_bytes</option> and <option>rekey_bytes</option>.
+ '';
+
+ rekey_packets = mkIntParam 0 ''
+ Number of packets processed before initiating CHILD_SA rekeying. CHILD_SA
+ rekeying refreshes key material, optionally using a Diffie-Hellman
+ exchange if a group is specified in the proposal.
+ </para><para>
+ To avoid rekey collisions initiated by both ends simultaneously, a value
+ in the range of <option>rand_packets</option> gets subtracted to form
+ the effective soft packet count limit.
+ </para><para>
+ Packet count based CHILD_SA rekeying is disabled by default.
+ '';
+
+ life_packets = mkOptionalIntParam ''
+ Maximum number of packets processed before CHILD_SA gets closed. Usually
+ this hard packets limit is never reached, because the CHILD_SA gets
+ rekeyed before. If that fails for whatever reason, this limit closes the
+ CHILD_SA.
+ </para><para>
+ The default is 10% more than <option>rekey_bytes</option>.
+ '';
+
+ rand_packets = mkOptionalIntParam ''
+ Packet range from which to choose a random value to subtract from
+ <option>rekey_packets</option>. The default is the difference between
+ <option>life_packets</option> and <option>rekey_packets</option>.
+ '';
+
+ updown = mkOptionalStrParam ''
+ Updown script to invoke on CHILD_SA up and down events.
+ '';
+
+ hostaccess = mkYesNoParam no ''
+ Hostaccess variable to pass to <literal>updown</literal> script.
+ '';
+
+ mode = mkEnumParam [ "tunnel"
+ "transport"
+ "transport_proxy"
+ "beet"
+ "pass"
+ "drop"
+ ] "tunnel" ''
+ IPsec Mode to establish CHILD_SA with.
+ <itemizedlist>
+ <listitem><para>
+ <literal>tunnel</literal> negotiates the CHILD_SA in IPsec Tunnel Mode,
+ </para></listitem>
+ <listitem><para>
+ whereas <literal>transport</literal> uses IPsec Transport Mode.
+ </para></listitem>
+ <listitem><para>
+ <literal>transport_proxy</literal> signifying the special Mobile IPv6
+ Transport Proxy Mode.
+ </para></listitem>
+ <listitem><para>
+ <literal>beet</literal> is the Bound End to End Tunnel mixture mode,
+ working with fixed inner addresses without the need to include them in
+ each packet.
+ </para></listitem>
+ <listitem><para>
+ Both <literal>transport</literal> and <literal>beet</literal> modes are
+ subject to mode negotiation; <literal>tunnel</literal> mode is
+ negotiated if the preferred mode is not available.
+ </para></listitem>
+ <listitem><para>
+ <literal>pass</literal> and <literal>drop</literal> are used to install
+ shunt policies which explicitly bypass the defined traffic from IPsec
+ processing or drop it, respectively.
+ </para></listitem>
+ </itemizedlist>
+ '';
+
+ policies = mkYesNoParam yes ''
+ Whether to install IPsec policies or not. Disabling this can be useful in
+ some scenarios e.g. MIPv6, where policies are not managed by the IKE
+ daemon. Since 5.3.3.
+ '';
+
+ policies_fwd_out = mkYesNoParam no ''
+ Whether to install outbound FWD IPsec policies or not. Enabling this is
+ required in case there is a drop policy that would match and block
+ forwarded traffic for this CHILD_SA. Since 5.5.1.
+ '';
+
+ dpd_action = mkEnumParam ["clear" "trap" "restart"] "clear" ''
+ Action to perform for this CHILD_SA on DPD timeout. The default clear
+ closes the CHILD_SA and does not take further action. trap installs a
+ trap policy, which will catch matching traffic and tries to re-negotiate
+ the tunnel on-demand. restart immediately tries to re-negotiate the
+ CHILD_SA under a fresh IKE_SA.
+ '';
+
+ ipcomp = mkYesNoParam no ''
+ Enable IPComp compression before encryption. If enabled, IKE tries to
+ negotiate IPComp compression to compress ESP payload data prior to
+ encryption.
+ '';
+
+ inactivity = mkDurationParam "0s" ''
+ Timeout before closing CHILD_SA after inactivity. If no traffic has been
+ processed in either direction for the configured timeout, the CHILD_SA
+ gets closed due to inactivity. The default value of 0 disables inactivity
+ checks.
+ '';
+
+ reqid = mkIntParam 0 ''
+ Fixed reqid to use for this CHILD_SA. This might be helpful in some
+ scenarios, but works only if each CHILD_SA configuration is instantiated
+ not more than once. The default of 0 uses dynamic reqids, allocated
+ incrementally.
+ '';
+
+ priority = mkIntParam 0 ''
+ Optional fixed priority for IPsec policies. This could be useful to
+ install high-priority drop policies. The default of 0 uses dynamically
+ calculated priorities based on the size of the traffic selectors.
+ '';
+
+ interface = mkOptionalStrParam ''
+ Optional interface name to restrict outbound IPsec policies.
+ '';
+
+ mark_in = mkStrParam "0/0x00000000" ''
+ Netfilter mark and mask for input traffic. On Linux, Netfilter may
+ require marks on each packet to match an SA/policy having that option
+ set. This allows installing duplicate policies and enables Netfilter
+ rules to select specific SAs/policies for incoming traffic. Note that
+ inbound marks are only set on policies, by default, unless
+ <option>mark_in_sa</option> is enabled. The special value
+ <literal>%unique</literal> sets a unique mark on each CHILD_SA instance,
+ beyond that the value <literal>%unique-dir</literal> assigns a different
+ unique mark for each
+ </para><para>
+ An additional mask may be appended to the mark, separated by
+ <literal>/</literal>. The default mask if omitted is
+ <literal>0xffffffff</literal>.
+ '';
+
+ mark_in_sa = mkYesNoParam no ''
+ Whether to set <option>mark_in</option> on the inbound SA. By default,
+ the inbound mark is only set on the inbound policy. The tuple destination
+ address, protocol and SPI is unique and the mark is not required to find
+ the correct SA, allowing to mark traffic after decryption instead (where
+ more specific selectors may be used) to match different policies. Marking
+ packets before decryption is still possible, even if no mark is set on
+ the SA.
+ '';
+
+ mark_out = mkStrParam "0/0x00000000" ''
+ Netfilter mark and mask for output traffic. On Linux, Netfilter may
+ require marks on each packet to match a policy/SA having that option
+ set. This allows installing duplicate policies and enables Netfilter
+ rules to select specific policies/SAs for outgoing traffic. The special
+ value <literal>%unique</literal> sets a unique mark on each CHILD_SA
+ instance, beyond that the value <literal>%unique-dir</literal> assigns a
+ different unique mark for each CHILD_SA direction (in/out).
+ </para><para>
+ An additional mask may be appended to the mark, separated by
+ <literal>/</literal>. The default mask if omitted is
+ <literal>0xffffffff</literal>.
+ '';
+
+ set_mark_in = mkStrParam "0/0x00000000" ''
+ Netfilter mark applied to packets after the inbound IPsec SA processed
+ them. This way it's not necessary to mark packets via Netfilter before
+ decryption or right afterwards to match policies or process them
+ differently (e.g. via policy routing).
+
+ An additional mask may be appended to the mark, separated by
+ <literal>/</literal>. The default mask if omitted is 0xffffffff. The
+ special value <literal>%same</literal> uses the value (but not the mask)
+ from <option>mark_in</option> as mark value, which can be fixed,
+ <literal>%unique</literal> or <literal>%unique-dir</literal>.
+
+ Setting marks in XFRM input requires Linux 4.19 or higher.
+ '';
+
+ set_mark_out = mkStrParam "0/0x00000000" ''
+ Netfilter mark applied to packets after the outbound IPsec SA processed
+ them. This allows processing ESP packets differently than the original
+ traffic (e.g. via policy routing).
+
+ An additional mask may be appended to the mark, separated by
+ <literal>/</literal>. The default mask if omitted is 0xffffffff. The
+ special value <literal>%same</literal> uses the value (but not the mask)
+ from <option>mark_out</option> as mark value, which can be fixed,
+ <literal>%unique_</literal> or <literal>%unique-dir</literal>.
+
+ Setting marks in XFRM output is supported since Linux 4.14. Setting a
+ mask requires at least Linux 4.19.
+ '';
+
+ if_id_in = mkStrParam "0" ''
+ XFRM interface ID set on inbound policies/SA. This allows installing
+ duplicate policies/SAs and associates them with an interface with the
+ same ID. The special value <literal>%unique</literal> sets a unique
+ interface ID on each CHILD_SA instance, beyond that the value
+ <literal>%unique-dir</literal> assigns a different unique interface ID
+ for each CHILD_SA direction (in/out).
+ '';
+
+ if_id_out = mkStrParam "0" ''
+ XFRM interface ID set on outbound policies/SA. This allows installing
+ duplicate policies/SAs and associates them with an interface with the
+ same ID. The special value <literal>%unique</literal> sets a unique
+ interface ID on each CHILD_SA instance, beyond that the value
+ <literal>%unique-dir</literal> assigns a different unique interface ID
+ for each CHILD_SA direction (in/out).
+
+ The daemon will not install routes for CHILD_SAs that have this option set.
+ '';
+
+ tfc_padding = mkParamOfType (with lib.types; either int (enum ["mtu"])) 0 ''
+ Pads ESP packets with additional data to have a consistent ESP packet
+ size for improved Traffic Flow Confidentiality. The padding defines the
+ minimum size of all ESP packets sent. The default value of
+ <literal>0</literal> disables TFC padding, the special value
+ <literal>mtu</literal> adds TFC padding to create a packet size equal to
+ the Path Maximum Transfer Unit.
+ '';
+
+ replay_window = mkIntParam 32 ''
+ IPsec replay window to configure for this CHILD_SA. Larger values than
+ the default of <literal>32</literal> are supported using the Netlink
+ backend only, a value of <literal>0</literal> disables IPsec replay
+ protection.
+ '';
+
+ hw_offload = mkEnumParam ["yes" "no" "auto"] "no" ''
+ Enable hardware offload for this CHILD_SA, if supported by the IPsec
+ implementation. The value <literal>yes</literal> enforces offloading
+ and the installation will fail if it's not supported by either kernel or
+ device. The value <literal>auto</literal> enables offloading, if it's
+ supported, but the installation does not fail otherwise.
+ '';
+
+ copy_df = mkYesNoParam yes ''
+ Whether to copy the DF bit to the outer IPv4 header in tunnel mode. This
+ effectively disables Path MTU discovery (PMTUD). Controlling this
+ behavior is not supported by all kernel interfaces.
+ '';
+
+ copy_ecn = mkYesNoParam yes ''
+ Whether to copy the ECN (Explicit Congestion Notification) header field
+ to/from the outer IP header in tunnel mode. Controlling this behavior is
+ not supported by all kernel interfaces.
+ '';
+
+ copy_dscp = mkEnumParam [ "out" "in" "yes" "no" ] "out" ''
+ Whether to copy the DSCP (Differentiated Services Field Codepoint)
+ header field to/from the outer IP header in tunnel mode. The value
+ <literal>out</literal> only copies the field from the inner to the outer
+ header, the value <literal>in</literal> does the opposite and only
+ copies the field from the outer to the inner header when decapsulating,
+ the value <literal>yes</literal> copies the field in both directions,
+ and the value <literal>no</literal> disables copying the field
+ altogether. Setting this to <literal>yes</literal> or
+ <literal>in</literal> could allow an attacker to adversely affect other
+ traffic at the receiver, which is why the default is
+ <literal>out</literal>. Controlling this behavior is not supported by
+ all kernel interfaces.
+ '';
+
+ start_action = mkEnumParam ["none" "trap" "start"] "none" ''
+ Action to perform after loading the configuration.
+ <itemizedlist>
+ <listitem><para>
+ The default of <literal>none</literal> loads the connection only, which
+ then can be manually initiated or used as a responder configuration.
+ </para></listitem>
+ <listitem><para>
+ The value <literal>trap</literal> installs a trap policy, which triggers
+ the tunnel as soon as matching traffic has been detected.
+ </para></listitem>
+ <listitem><para>
+ The value <literal>start</literal> initiates the connection actively.
+ </para></listitem>
+ </itemizedlist>
+ When unloading or replacing a CHILD_SA configuration having a
+ <option>start_action</option> different from <literal>none</literal>,
+ the inverse action is performed. Configurations with
+ <literal>start</literal> get closed, while such with
+ <literal>trap</literal> get uninstalled.
+ '';
+
+ close_action = mkEnumParam ["none" "trap" "start"] "none" ''
+ Action to perform after a CHILD_SA gets closed by the peer.
+ <itemizedlist>
+ <listitem><para>
+ The default of <literal>none</literal> does not take any action,
+ </para></listitem>
+ <listitem><para>
+ <literal>trap</literal> installs a trap policy for the CHILD_SA.
+ </para></listitem>
+ <listitem><para>
+ <literal>start</literal> tries to re-create the CHILD_SA.
+ </para></listitem>
+ </itemizedlist>
+ </para><para>
+ <option>close_action</option> does not provide any guarantee that the
+ CHILD_SA is kept alive. It acts on explicit close messages only, but not
+ on negotiation failures. Use trap policies to reliably re-create failed
+ CHILD_SAs.
+ '';
+
+ } ''
+ CHILD_SA configuration sub-section. Each connection definition may have
+ one or more sections in its <option>children</option> subsection. The
+ section name defines the name of the CHILD_SA configuration, which must be
+ unique within the connection (denoted &#60;child&#62; below).
+ '';
+ } ''
+ Section defining IKE connection configurations, each in its own subsection
+ with an arbitrary yet unique name
+ '';
+
+ secrets = let
+ mkEapXauthParams = mkPrefixedAttrsOfParams {
+ secret = mkOptionalStrParam ''
+ Value of the EAP/XAuth secret. It may either be an ASCII string, a hex
+ encoded string if it has a 0x prefix or a Base64 encoded string if it
+ has a 0s prefix in its value.
+ '';
+
+ id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
+ Identity the EAP/XAuth secret belongs to. Multiple unique identities may
+ be specified, each having an <literal>id</literal> prefix, if a secret
+ is shared between multiple users.
+ '';
+
+ } ''
+ EAP secret section for a specific secret. Each EAP secret is defined in a
+ unique section having the <literal>eap</literal> prefix. EAP secrets are
+ used for XAuth authentication as well.
+ '';
+
+ in {
+
+ eap = mkEapXauthParams;
+ xauth = mkEapXauthParams;
+
+ ntlm = mkPrefixedAttrsOfParams {
+ secret = mkOptionalStrParam ''
+ Value of the NTLM secret, which is the NT Hash of the actual secret,
+ that is, MD4(UTF-16LE(secret)). The resulting 16-byte value may either
+ be given as a hex encoded string with a 0x prefix or as a Base64 encoded
+ string with a 0s prefix.
+ '';
+
+ id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
+ Identity the NTLM secret belongs to. Multiple unique identities may be
+ specified, each having an id prefix, if a secret is shared between
+ multiple users.
+ '';
+ } ''
+ NTLM secret section for a specific secret. Each NTLM secret is defined in
+ a unique section having the <literal>ntlm</literal> prefix. NTLM secrets
+ may only be used for EAP-MSCHAPv2 authentication.
+ '';
+
+ ike = mkPrefixedAttrsOfParams {
+ secret = mkOptionalStrParam ''
+ Value of the IKE preshared secret. It may either be an ASCII string, a
+ hex encoded string if it has a 0x prefix or a Base64 encoded string if
+ it has a 0s prefix in its value.
+ '';
+
+ id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
+ IKE identity the IKE preshared secret belongs to. Multiple unique
+ identities may be specified, each having an <literal>id</literal>
+ prefix, if a secret is shared between multiple peers.
+ '';
+ } ''
+ IKE preshared secret section for a specific secret. Each IKE PSK is
+ defined in a unique section having the <literal>ike</literal> prefix.
+ '';
+
+ ppk = mkPrefixedAttrsOfParams {
+ secret = mkOptionalStrParam ''
+ Value of the PPK. It may either be an ASCII string, a hex encoded string
+ if it has a <literal>0x</literal> prefix or a Base64 encoded string if
+ it has a <literal>0s</literal> prefix in its value. Should have at least
+ 256 bits of entropy for 128-bit security.
+ '';
+
+ id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
+ PPK identity the PPK belongs to. Multiple unique identities may be
+ specified, each having an <literal>id</literal> prefix, if a secret is
+ shared between multiple peers.
+ '';
+ } ''
+ Postquantum Preshared Key (PPK) section for a specific secret. Each PPK is
+ defined in a unique section having the <literal>ppk</literal> prefix.
+ '';
+
+ private = mkPrefixedAttrsOfParams {
+ file = mkOptionalStrParam ''
+ File name in the private folder for which this passphrase should be used.
+ '';
+
+ secret = mkOptionalStrParam ''
+ Value of decryption passphrase for private key.
+ '';
+ } ''
+ Private key decryption passphrase for a key in the
+ <literal>private</literal> folder.
+ '';
+
+ rsa = mkPrefixedAttrsOfParams {
+ file = mkOptionalStrParam ''
+ File name in the <literal>rsa</literal> folder for which this passphrase
+ should be used.
+ '';
+ secret = mkOptionalStrParam ''
+ Value of decryption passphrase for RSA key.
+ '';
+ } ''
+ Private key decryption passphrase for a key in the <literal>rsa</literal>
+ folder.
+ '';
+
+ ecdsa = mkPrefixedAttrsOfParams {
+ file = mkOptionalStrParam ''
+ File name in the <literal>ecdsa</literal> folder for which this
+ passphrase should be used.
+ '';
+ secret = mkOptionalStrParam ''
+ Value of decryption passphrase for ECDSA key.
+ '';
+ } ''
+ Private key decryption passphrase for a key in the
+ <literal>ecdsa</literal> folder.
+ '';
+
+ pkcs8 = mkPrefixedAttrsOfParams {
+ file = mkOptionalStrParam ''
+ File name in the <literal>pkcs8</literal> folder for which this
+ passphrase should be used.
+ '';
+ secret = mkOptionalStrParam ''
+ Value of decryption passphrase for PKCS#8 key.
+ '';
+ } ''
+ Private key decryption passphrase for a key in the
+ <literal>pkcs8</literal> folder.
+ '';
+
+ pkcs12 = mkPrefixedAttrsOfParams {
+ file = mkOptionalStrParam ''
+ File name in the <literal>pkcs12</literal> folder for which this
+ passphrase should be used.
+ '';
+ secret = mkOptionalStrParam ''
+ Value of decryption passphrase for PKCS#12 container.
+ '';
+ } ''
+ PKCS#12 decryption passphrase for a container in the
+ <literal>pkcs12</literal> folder.
+ '';
+
+ token = mkPrefixedAttrsOfParams {
+ handle = mkOptionalHexParam ''
+ Hex-encoded CKA_ID or handle of the private key on the token or TPM,
+ respectively.
+ '';
+
+ slot = mkOptionalIntParam ''
+ Optional slot number to access the token.
+ '';
+
+ module = mkOptionalStrParam ''
+ Optional PKCS#11 module name to access the token.
+ '';
+
+ pin = mkOptionalStrParam ''
+ Optional PIN required to access the key on the token. If none is
+ provided the user is prompted during an interactive
+ <literal>--load-creds</literal> call.
+ '';
+ } ''Definition for a private key that's stored on a token/smartcard/TPM.'';
+
+ };
+
+ pools = mkAttrsOfParams {
+ addrs = mkOptionalStrParam ''
+ Subnet or range defining addresses allocated in pool. Accepts a single
+ CIDR subnet defining the pool to allocate addresses from or an address
+ range (&#60;from&#62;-&#60;to&#62;). Pools must be unique and non-overlapping.
+ '';
+
+ dns = mkCommaSepListParam [] "Address or CIDR subnets";
+ nbns = mkCommaSepListParam [] "Address or CIDR subnets";
+ dhcp = mkCommaSepListParam [] "Address or CIDR subnets";
+ netmask = mkCommaSepListParam [] "Address or CIDR subnets";
+ server = mkCommaSepListParam [] "Address or CIDR subnets";
+ subnet = mkCommaSepListParam [] "Address or CIDR subnets";
+ split_include = mkCommaSepListParam [] "Address or CIDR subnets";
+ split_exclude = mkCommaSepListParam [] "Address or CIDR subnets";
+ } ''
+ Section defining named pools. Named pools may be referenced by connections
+ with the pools option to assign virtual IPs and other configuration
+ attributes. Each pool must have a unique name (denoted &#60;name&#62; below).
+ '';
+}
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan.nix b/nixpkgs/nixos/modules/services/networking/strongswan.nix
new file mode 100644
index 00000000000..4ff9c486059
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/strongswan.nix
@@ -0,0 +1,168 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+ inherit (builtins) toFile;
+ inherit (lib) concatMapStringsSep concatStringsSep mapAttrsToList
+ mkIf mkEnableOption mkOption types;
+
+ cfg = config.services.strongswan;
+
+ ipsecSecrets = secrets: toFile "ipsec.secrets" (
+ concatMapStringsSep "\n" (f: "include ${f}") secrets
+ );
+
+ ipsecConf = {setup, connections, ca}:
+ let
+ # https://wiki.strongswan.org/projects/strongswan/wiki/IpsecConf
+ makeSections = type: sections: concatStringsSep "\n\n" (
+ mapAttrsToList (sec: attrs:
+ "${type} ${sec}\n" +
+ (concatStringsSep "\n" ( mapAttrsToList (k: v: " ${k}=${v}") attrs ))
+ ) sections
+ );
+ setupConf = makeSections "config" { inherit setup; };
+ connectionsConf = makeSections "conn" connections;
+ caConf = makeSections "ca" ca;
+
+ in
+ builtins.toFile "ipsec.conf" ''
+ ${setupConf}
+ ${connectionsConf}
+ ${caConf}
+ '';
+
+ strongswanConf = {setup, connections, ca, secretsFile, managePlugins, enabledPlugins}: toFile "strongswan.conf" ''
+ charon {
+ ${if managePlugins then "load_modular = no" else ""}
+ ${if managePlugins then ("load = " + (concatStringsSep " " enabledPlugins)) else ""}
+ plugins {
+ stroke {
+ secrets_file = ${secretsFile}
+ }
+ }
+ }
+
+ starter {
+ config_file = ${ipsecConf { inherit setup connections ca; }}
+ }
+ '';
+
+in
+{
+ options.services.strongswan = {
+ enable = mkEnableOption "strongSwan";
+
+ secrets = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "/run/keys/ipsec-foo.secret" ];
+ description = ''
+ A list of paths to IPSec secret files. These
+ files will be included into the main ipsec.secrets file with
+ the <literal>include</literal> directive. It is safer if these
+ paths are absolute.
+ '';
+ };
+
+ setup = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = { cachecrls = "yes"; strictcrlpolicy = "yes"; };
+ description = ''
+ A set of options for the ‘config setup’ section of the
+ <filename>ipsec.conf</filename> file. Defines general
+ configuration parameters.
+ '';
+ };
+
+ connections = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = {};
+ example = {
+ "%default" = {
+ keyexchange = "ikev2";
+ keyingtries = "1";
+ };
+ roadwarrior = {
+ auto = "add";
+ leftcert = "/run/keys/moonCert.pem";
+ leftid = "@moon.strongswan.org";
+ leftsubnet = "10.1.0.0/16";
+ right = "%any";
+ };
+ };
+ description = ''
+ A set of connections and their options for the ‘conn xxx’
+ sections of the <filename>ipsec.conf</filename> file.
+ '';
+ };
+
+ ca = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = {};
+ example = {
+ strongswan = {
+ auto = "add";
+ cacert = "/run/keys/strongswanCert.pem";
+ crluri = "http://crl2.strongswan.org/strongswan.crl";
+ };
+ };
+ description = ''
+ A set of CAs (certification authorities) and their options for
+ the ‘ca xxx’ sections of the <filename>ipsec.conf</filename>
+ file.
+ '';
+ };
+
+ managePlugins = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If set to true, this option will disable automatic plugin loading and
+ then tell strongSwan to enable the plugins specified in the
+ <option>enabledPlugins</option> option.
+ '';
+ };
+
+ enabledPlugins = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ A list of additional plugins to enable if
+ <option>managePlugins</option> is true.
+ '';
+ };
+ };
+
+
+ config = with cfg;
+ let
+ secretsFile = ipsecSecrets cfg.secrets;
+ in
+ mkIf enable
+ {
+
+ # here we should use the default strongswan ipsec.secrets and
+ # append to it (default one is empty so not a pb for now)
+ environment.etc."ipsec.secrets".source = secretsFile;
+
+ systemd.services.strongswan = {
+ description = "strongSwan IPSec Service";
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [ kmod iproute iptables utillinux ]; # XXX Linux
+ after = [ "network-online.target" ];
+ environment = {
+ STRONGSWAN_CONF = strongswanConf { inherit setup connections ca secretsFile managePlugins enabledPlugins; };
+ };
+ serviceConfig = {
+ ExecStart = "${pkgs.strongswan}/sbin/ipsec start --nofork";
+ };
+ preStart = ''
+ # with 'nopeerdns' setting, ppp writes into this folder
+ mkdir -m 700 -p /etc/ppp
+ '';
+ };
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/stubby.nix b/nixpkgs/nixos/modules/services/networking/stubby.nix
new file mode 100644
index 00000000000..b38bcd4cec0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/stubby.nix
@@ -0,0 +1,214 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+ cfg = config.services.stubby;
+
+ fallbacks = concatMapStringsSep "\n " (x: "- ${x}") cfg.fallbackProtocols;
+ listeners = concatMapStringsSep "\n " (x: "- ${x}") cfg.listenAddresses;
+
+ # By default, the recursive resolvers maintained by the getdns
+ # project itself are enabled. More information about both getdns's servers,
+ # as well as third party options for upstream resolvers, can be found here:
+ # https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Test+Servers
+ #
+ # You can override these values by supplying a yaml-formatted array of your
+ # preferred upstream resolvers in the following format:
+ #
+ # 106 # - address_data: IPv4 or IPv6 address of the upstream
+ # port: Port for UDP/TCP (default is 53)
+ # tls_auth_name: Authentication domain name checked against the server
+ # certificate
+ # tls_pubkey_pinset: An SPKI pinset verified against the keys in the server
+ # certificate
+ # - digest: Only "sha256" is currently supported
+ # value: Base64 encoded value of the sha256 fingerprint of the public
+ # key
+ # tls_port: Port for TLS (default is 853)
+
+ defaultUpstream = ''
+ - address_data: 145.100.185.15
+ tls_auth_name: "dnsovertls.sinodun.com"
+ tls_pubkey_pinset:
+ - digest: "sha256"
+ value: 62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4=
+ - address_data: 145.100.185.16
+ tls_auth_name: "dnsovertls1.sinodun.com"
+ tls_pubkey_pinset:
+ - digest: "sha256"
+ value: cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA=
+ - address_data: 185.49.141.37
+ tls_auth_name: "getdnsapi.net"
+ tls_pubkey_pinset:
+ - digest: "sha256"
+ value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q=
+ - address_data: 2001:610:1:40ba:145:100:185:15
+ tls_auth_name: "dnsovertls.sinodun.com"
+ tls_pubkey_pinset:
+ - digest: "sha256"
+ value: 62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4=
+ - address_data: 2001:610:1:40ba:145:100:185:16
+ tls_auth_name: "dnsovertls1.sinodun.com"
+ tls_pubkey_pinset:
+ - digest: "sha256"
+ value: cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA=
+ - address_data: 2a04:b900:0:100::38
+ tls_auth_name: "getdnsapi.net"
+ tls_pubkey_pinset:
+ - digest: "sha256"
+ value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q=
+ '';
+
+ # Resolution type is not changeable here because it is required per the
+ # stubby documentation:
+ #
+ # "resolution_type: Work in stub mode only (not recursive mode) - required for Stubby
+ # operation."
+ #
+ # https://dnsprivacy.org/wiki/display/DP/Configuring+Stubby
+
+ confFile = pkgs.writeText "stubby.yml" ''
+ resolution_type: GETDNS_RESOLUTION_STUB
+ dns_transport_list:
+ ${fallbacks}
+ tls_authentication: ${cfg.authenticationMode}
+ tls_query_padding_blocksize: ${toString cfg.queryPaddingBlocksize}
+ edns_client_subnet_private: ${if cfg.subnetPrivate then "1" else "0"}
+ idle_timeout: ${toString cfg.idleTimeout}
+ listen_addresses:
+ ${listeners}
+ round_robin_upstreams: ${if cfg.roundRobinUpstreams then "1" else "0"}
+ ${cfg.extraConfig}
+ upstream_recursive_servers:
+ ${cfg.upstreamServers}
+ '';
+in
+
+{
+ options = {
+ services.stubby = {
+
+ enable = mkEnableOption "Stubby DNS resolver";
+
+ fallbackProtocols = mkOption {
+ default = [ "GETDNS_TRANSPORT_TLS" ];
+ type = with types; listOf (enum [
+ "GETDNS_TRANSPORT_TLS"
+ "GETDNS_TRANSPORT_TCP"
+ "GETDNS_TRANSPORT_UDP"
+ ]);
+ description = ''
+ Ordered list composed of one or more transport protocols.
+ Strict mode should only use <literal>GETDNS_TRANSPORT_TLS</literal>.
+ Other options are <literal>GETDNS_TRANSPORT_UDP</literal> and
+ <literal>GETDNS_TRANSPORT_TCP</literal>.
+ '';
+ };
+
+ authenticationMode = mkOption {
+ default = "GETDNS_AUTHENTICATION_REQUIRED";
+ type = types.enum [
+ "GETDNS_AUTHENTICATION_REQUIRED"
+ "GETDNS_AUTHENTICATION_NONE"
+ ];
+ description = ''
+ Selects the Strict or Opportunistic usage profile.
+ For strict, set to <literal>GETDNS_AUTHENTICATION_REQUIRED</literal>.
+ for opportunistic, use <literal>GETDNS_AUTHENTICATION_NONE</literal>.
+ '';
+ };
+
+ queryPaddingBlocksize = mkOption {
+ default = 128;
+ type = types.int;
+ description = ''
+ EDNS0 option to pad the size of the DNS query to the given blocksize.
+ '';
+ };
+
+ subnetPrivate = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ EDNS0 option for ECS client privacy. Default is
+ <literal>true</literal>. If set, this option prevents the client
+ subnet from being sent to authoritative nameservers.
+ '';
+ };
+
+ idleTimeout = mkOption {
+ default = 10000;
+ type = types.int;
+ description = "EDNS0 option for keepalive idle timeout expressed in
+ milliseconds.";
+ };
+
+ listenAddresses = mkOption {
+ default = [ "127.0.0.1" "0::1" ];
+ type = with types; listOf str;
+ description = ''
+ Sets the listen address for the stubby daemon.
+ Uses port 53 by default.
+ Ise IP@port to specify a different port.
+ '';
+ };
+
+ roundRobinUpstreams = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Instructs stubby to distribute queries across all available name
+ servers. Default is <literal>true</literal>. Set to
+ <literal>false</literal> in order to use the first available.
+ '';
+ };
+
+ upstreamServers = mkOption {
+ default = defaultUpstream;
+ type = types.lines;
+ description = ''
+ Replace default upstreams. See <citerefentry><refentrytitle>stubby
+ </refentrytitle><manvolnum>1</manvolnum></citerefentry> for an
+ example of the entry formatting. In Strict mode, at least one of the
+ following settings must be supplied for each nameserver:
+ <literal>tls_auth_name</literal> or
+ <literal>tls_pubkey_pinset</literal>.
+ '';
+ };
+
+ debugLogging = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Enable or disable debug level logging.";
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Add additional configuration options. see <citerefentry>
+ <refentrytitle>stubby</refentrytitle><manvolnum>1</manvolnum>
+ </citerefentry>for more options.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.stubby ];
+ systemd.services.stubby = {
+ description = "Stubby local DNS resolver";
+ after = [ "network.target" ];
+ before = [ "nss-lookup.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+ CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
+ ExecStart = "${pkgs.stubby}/bin/stubby -C ${confFile} ${optionalString cfg.debugLogging "-l"}";
+ DynamicUser = true;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/stunnel.nix b/nixpkgs/nixos/modules/services/networking/stunnel.nix
new file mode 100644
index 00000000000..cbc899f2b4d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/stunnel.nix
@@ -0,0 +1,221 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.stunnel;
+ yesNo = val: if val then "yes" else "no";
+
+ verifyChainPathAssert = n: c: {
+ assertion = c.verifyHostname == null || (c.verifyChain || c.verifyPeer);
+ message = "stunnel: \"${n}\" client configuration - hostname verification " +
+ "is not possible without either verifyChain or verifyPeer enabled";
+ };
+
+ serverConfig = {
+ options = {
+ accept = mkOption {
+ type = types.int;
+ description = "On which port stunnel should listen for incoming TLS connections.";
+ };
+
+ connect = mkOption {
+ type = types.int;
+ description = "To which port the decrypted connection should be forwarded.";
+ };
+
+ cert = mkOption {
+ type = types.path;
+ description = "File containing both the private and public keys.";
+ };
+ };
+ };
+
+ clientConfig = {
+ options = {
+ accept = mkOption {
+ type = types.str;
+ description = "IP:Port on which connections should be accepted.";
+ };
+
+ connect = mkOption {
+ type = types.str;
+ description = "IP:Port destination to connect to.";
+ };
+
+ verifyChain = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Check if the provided certificate has a valid certificate chain (against CAPath).";
+ };
+
+ verifyPeer = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Check if the provided certificate is contained in CAPath.";
+ };
+
+ CAPath = mkOption {
+ type = types.path;
+ default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+ description = "Path to a file containing certificates to validate against.";
+ };
+
+ verifyHostname = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "If set, stunnel checks if the provided certificate is valid for the given hostname.";
+ };
+ };
+ };
+
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.stunnel = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the stunnel TLS tunneling service.";
+ };
+
+ user = mkOption {
+ type = with types; nullOr str;
+ default = "nobody";
+ description = "The user under which stunnel runs.";
+ };
+
+ group = mkOption {
+ type = with types; nullOr str;
+ default = "nogroup";
+ description = "The group under which stunnel runs.";
+ };
+
+ logLevel = mkOption {
+ type = types.enum [ "emerg" "alert" "crit" "err" "warning" "notice" "info" "debug" ];
+ default = "info";
+ description = "Verbosity of stunnel output.";
+ };
+
+ fipsMode = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable FIPS 140-2 mode required for compliance.";
+ };
+
+ enableInsecureSSLv3 = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable support for the insecure SSLv3 protocol.";
+ };
+
+
+ servers = mkOption {
+ description = "Define the server configuations.";
+ type = with types; attrsOf (submodule serverConfig);
+ example = {
+ fancyWebserver = {
+ enable = true;
+ accept = 443;
+ connect = 8080;
+ cert = "/path/to/pem/file";
+ };
+ };
+ default = { };
+ };
+
+ clients = mkOption {
+ description = "Define the client configurations.";
+ type = with types; attrsOf (submodule clientConfig);
+ example = {
+ foobar = {
+ accept = "0.0.0.0:8080";
+ connect = "nixos.org:443";
+ verifyChain = false;
+ };
+ };
+ default = { };
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = concatLists [
+ (singleton {
+ assertion = (length (attrValues cfg.servers) != 0) || ((length (attrValues cfg.clients)) != 0);
+ message = "stunnel: At least one server- or client-configuration has to be present.";
+ })
+
+ (mapAttrsToList verifyChainPathAssert cfg.clients)
+ ];
+
+ environment.systemPackages = [ pkgs.stunnel ];
+
+ environment.etc."stunnel.cfg".text = ''
+ ${ if cfg.user != null then "setuid = ${cfg.user}" else "" }
+ ${ if cfg.group != null then "setgid = ${cfg.group}" else "" }
+
+ debug = ${cfg.logLevel}
+
+ ${ optionalString cfg.fipsMode "fips = yes" }
+ ${ optionalString cfg.enableInsecureSSLv3 "options = -NO_SSLv3" }
+
+ ; ----- SERVER CONFIGURATIONS -----
+ ${ lib.concatStringsSep "\n"
+ (lib.mapAttrsToList
+ (n: v: ''
+ [${n}]
+ accept = ${toString v.accept}
+ connect = ${toString v.connect}
+ cert = ${v.cert}
+
+ '')
+ cfg.servers)
+ }
+
+ ; ----- CLIENT CONFIGURATIONS -----
+ ${ lib.concatStringsSep "\n"
+ (lib.mapAttrsToList
+ (n: v: ''
+ [${n}]
+ client = yes
+ accept = ${v.accept}
+ connect = ${v.connect}
+ verifyChain = ${yesNo v.verifyChain}
+ verifyPeer = ${yesNo v.verifyPeer}
+ ${optionalString (v.CAPath != null) "CApath = ${v.CAPath}"}
+ ${optionalString (v.verifyHostname != null) "checkHost = ${v.verifyHostname}"}
+ OCSPaia = yes
+
+ '')
+ cfg.clients)
+ }
+ '';
+
+ systemd.services.stunnel = {
+ description = "stunnel TLS tunneling service";
+ after = [ "network.target" ];
+ wants = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ config.environment.etc."stunnel.cfg".source ];
+ serviceConfig = {
+ ExecStart = "${pkgs.stunnel}/bin/stunnel ${config.environment.etc."stunnel.cfg".source}";
+ Type = "forking";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/supplicant.nix b/nixpkgs/nixos/modules/services/networking/supplicant.nix
new file mode 100644
index 00000000000..35c1e649e2e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/supplicant.nix
@@ -0,0 +1,251 @@
+{ config, lib, utils, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.networking.supplicant;
+
+ # We must escape interfaces due to the systemd interpretation
+ subsystemDevice = interface:
+ "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device";
+
+ serviceName = iface: "supplicant-${if (iface=="WLAN") then "wlan@" else (
+ if (iface=="LAN") then "lan@" else (
+ if (iface=="DBUS") then "dbus"
+ else (replaceChars [" "] ["-"] iface)))}";
+
+ # TODO: Use proper privilege separation for wpa_supplicant
+ supplicantService = iface: suppl:
+ let
+ deps = (if (iface=="WLAN"||iface=="LAN") then ["sys-subsystem-net-devices-%i.device"] else (
+ if (iface=="DBUS") then ["dbus.service"]
+ else (map subsystemDevice (splitString " " iface))))
+ ++ optional (suppl.bridge!="") (subsystemDevice suppl.bridge);
+
+ ifaceArg = concatStringsSep " -N " (map (i: "-i${i}") (splitString " " iface));
+ driverArg = optionalString (suppl.driver != null) "-D${suppl.driver}";
+ bridgeArg = optionalString (suppl.bridge!="") "-b${suppl.bridge}";
+ confFileArg = optionalString (suppl.configFile.path!=null) "-c${suppl.configFile.path}";
+ extraConfFile = pkgs.writeText "supplicant-extra-conf-${replaceChars [" "] ["-"] iface}" ''
+ ${optionalString suppl.userControlled.enable "ctrl_interface=DIR=${suppl.userControlled.socketDir} GROUP=${suppl.userControlled.group}"}
+ ${optionalString suppl.configFile.writable "update_config=1"}
+ ${suppl.extraConf}
+ '';
+ in
+ { description = "Supplicant ${iface}${optionalString (iface=="WLAN"||iface=="LAN") " %I"}";
+ wantedBy = [ "multi-user.target" ] ++ deps;
+ wants = [ "network.target" ];
+ bindsTo = deps;
+ after = deps;
+ before = [ "network.target" ];
+ # Receive restart event after resume
+ partOf = [ "post-resume.target" ];
+
+ path = [ pkgs.coreutils ];
+
+ preStart = ''
+ ${optionalString (suppl.configFile.path!=null) ''
+ touch -a ${suppl.configFile.path}
+ chmod 600 ${suppl.configFile.path}
+ ''}
+ ${optionalString suppl.userControlled.enable ''
+ if ! test -e ${suppl.userControlled.socketDir}; then
+ mkdir -m 0770 -p ${suppl.userControlled.socketDir}
+ chgrp ${suppl.userControlled.group} ${suppl.userControlled.socketDir}
+ fi
+
+ if test "$(stat --printf '%G' ${suppl.userControlled.socketDir})" != "${suppl.userControlled.group}"; then
+ echo "ERROR: bad ownership on ${suppl.userControlled.socketDir}" >&2
+ exit 1
+ fi
+ ''}
+ '';
+
+ serviceConfig.ExecStart = "${pkgs.wpa_supplicant}/bin/wpa_supplicant -s ${driverArg} ${confFileArg} -I${extraConfFile} ${bridgeArg} ${suppl.extraCmdArgs} ${if (iface=="WLAN"||iface=="LAN") then "-i%I" else (if (iface=="DBUS") then "-u" else ifaceArg)}";
+
+ };
+
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ networking.supplicant = mkOption {
+ type = with types; attrsOf (submodule {
+ options = {
+
+ configFile = {
+
+ path = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = literalExample "/etc/wpa_supplicant.conf";
+ description = ''
+ External <literal>wpa_supplicant.conf</literal> configuration file.
+ The configuration options defined declaratively within <literal>networking.supplicant</literal> have
+ precedence over options defined in <literal>configFile</literal>.
+ '';
+ };
+
+ writable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether the configuration file at <literal>configFile.path</literal> should be written to by
+ <literal>wpa_supplicant</literal>.
+ '';
+ };
+
+ };
+
+ extraConf = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ ap_scan=1
+ device_name=My-NixOS-Device
+ device_type=1-0050F204-1
+ driver_param=use_p2p_group_interface=1
+ disable_scan_offload=1
+ p2p_listen_reg_class=81
+ p2p_listen_channel=1
+ p2p_oper_reg_class=81
+ p2p_oper_channel=1
+ manufacturer=NixOS
+ model_name=NixOS_Unstable
+ model_number=2015
+ '';
+ description = ''
+ Configuration options for <literal>wpa_supplicant.conf</literal>.
+ Options defined here have precedence over options in <literal>configFile</literal>.
+ NOTE: Do not write sensitive data into <literal>extraConf</literal> as it will
+ be world-readable in the <literal>nix-store</literal>. For sensitive information
+ use the <literal>configFile</literal> instead.
+ '';
+ };
+
+ extraCmdArgs = mkOption {
+ type = types.str;
+ default = "";
+ example = "-e/run/wpa_supplicant/entropy.bin";
+ description =
+ "Command line arguments to add when executing <literal>wpa_supplicant</literal>.";
+ };
+
+ driver = mkOption {
+ type = types.nullOr types.str;
+ default = "nl80211,wext";
+ description = "Force a specific wpa_supplicant driver.";
+ };
+
+ bridge = mkOption {
+ type = types.str;
+ default = "";
+ description = "Name of the bridge interface that wpa_supplicant should listen at.";
+ };
+
+ userControlled = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli.
+ This is useful for laptop users that switch networks a lot and don't want
+ to depend on a large package such as NetworkManager just to pick nearby
+ access points.
+ '';
+ };
+
+ socketDir = mkOption {
+ type = types.str;
+ default = "/run/wpa_supplicant";
+ description = "Directory of sockets for controlling wpa_supplicant.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "wheel";
+ example = "network";
+ description = "Members of this group can control wpa_supplicant.";
+ };
+
+ };
+ };
+ });
+
+ default = { };
+
+ example = literalExample ''
+ { "wlan0 wlan1" = {
+ configFile.path = "/etc/wpa_supplicant.conf";
+ userControlled.group = "network";
+ extraConf = '''
+ ap_scan=1
+ p2p_disabled=1
+ ''';
+ extraCmdArgs = "-u -W";
+ bridge = "br0";
+ };
+ }
+ '';
+
+ description = ''
+ Interfaces for which to start <command>wpa_supplicant</command>.
+ The supplicant is used to scan for and associate with wireless networks,
+ or to authenticate with 802.1x capable network switches.
+
+ The value of this option is an attribute set. Each attribute configures a
+ <command>wpa_supplicant</command> service, where the attribute name specifies
+ the name of the interface that <command>wpa_supplicant</command> operates on.
+ The attribute name can be a space separated list of interfaces.
+ The attribute names <literal>WLAN</literal>, <literal>LAN</literal> and <literal>DBUS</literal>
+ have a special meaning. <literal>WLAN</literal> and <literal>LAN</literal> are
+ configurations for universal <command>wpa_supplicant</command> service that is
+ started for each WLAN interface or for each LAN interface, respectively.
+ <literal>DBUS</literal> defines a device-unrelated <command>wpa_supplicant</command>
+ service that can be accessed through <literal>D-Bus</literal>.
+ '';
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf (cfg != {}) {
+
+ environment.systemPackages = [ pkgs.wpa_supplicant ];
+
+ services.dbus.packages = [ pkgs.wpa_supplicant ];
+
+ systemd.services = mapAttrs' (n: v: nameValuePair (serviceName n) (supplicantService n v)) cfg;
+
+ services.udev.packages = [
+ (pkgs.writeTextFile {
+ name = "99-zzz-60-supplicant.rules";
+ destination = "/etc/udev/rules.d/99-zzz-60-supplicant.rules";
+ text = ''
+ ${flip (concatMapStringsSep "\n") (filter (n: n!="WLAN" && n!="LAN" && n!="DBUS") (attrNames cfg)) (iface:
+ flip (concatMapStringsSep "\n") (splitString " " iface) (i: ''
+ ACTION=="add", SUBSYSTEM=="net", ENV{INTERFACE}=="${i}", TAG+="systemd", ENV{SYSTEMD_WANTS}+="supplicant-${replaceChars [" "] ["-"] iface}.service", TAG+="SUPPLICANT_ASSIGNED"''))}
+
+ ${optionalString (hasAttr "WLAN" cfg) ''
+ ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", TAG!="SUPPLICANT_ASSIGNED", TAG+="systemd", PROGRAM="${pkgs.systemd}/bin/systemd-escape -p %E{INTERFACE}", ENV{SYSTEMD_WANTS}+="supplicant-wlan@$result.service"
+ ''}
+ ${optionalString (hasAttr "LAN" cfg) ''
+ ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="lan", TAG!="SUPPLICANT_ASSIGNED", TAG+="systemd", PROGRAM="${pkgs.systemd}/bin/systemd-escape -p %E{INTERFACE}", ENV{SYSTEMD_WANTS}+="supplicant-lan@$result.service"
+ ''}
+ '';
+ })];
+
+ };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/supybot.nix b/nixpkgs/nixos/modules/services/networking/supybot.nix
new file mode 100644
index 00000000000..64eb1106832
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/supybot.nix
@@ -0,0 +1,88 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.supybot;
+
+in
+
+{
+
+ options = {
+
+ services.supybot = {
+
+ enable = mkOption {
+ default = false;
+ description = "Enable Supybot, an IRC bot";
+ };
+
+ stateDir = mkOption {
+ # Setting this to /var/lib/supybot caused useradd to fail
+ default = "/home/supybot";
+ description = "The root directory, logs and plugins are stored here";
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ description = ''
+ Path to a supybot config file. This can be generated by
+ running supybot-wizard.
+
+ Note: all paths should include the full path to the stateDir
+ directory (backup conf data logs logs/plugins plugins tmp web).
+ '';
+ };
+
+ };
+
+ };
+
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.pythonPackages.limnoria ];
+
+ users.users = singleton {
+ name = "supybot";
+ uid = config.ids.uids.supybot;
+ group = "supybot";
+ description = "Supybot IRC bot user";
+ home = cfg.stateDir;
+ createHome = true;
+ };
+
+ users.groups.supybot = {
+ name = "supybot";
+ gid = config.ids.gids.supybot;
+ };
+
+ systemd.services.supybot = {
+ description = "Supybot, an IRC bot";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.pythonPackages.limnoria ];
+ preStart = ''
+ cd ${cfg.stateDir}
+ mkdir -p backup conf data plugins logs/plugins tmp web
+ ln -sf ${cfg.configFile} supybot.cfg
+ # This needs to be created afresh every time
+ rm -f supybot.cfg.bak
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkgs.pythonPackages.limnoria}/bin/supybot ${cfg.stateDir}/supybot.cfg";
+ PIDFile = "/run/supybot.pid";
+ User = "supybot";
+ Group = "supybot";
+ UMask = "0007";
+ Restart = "on-abort";
+ StartLimitInterval = "5m";
+ StartLimitBurst = "1";
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/syncplay.nix b/nixpkgs/nixos/modules/services/networking/syncplay.nix
new file mode 100644
index 00000000000..e3147c10502
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/syncplay.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.syncplay;
+
+ cmdArgs =
+ [ "--port" cfg.port ]
+ ++ optionals (cfg.salt != null) [ "--salt" cfg.salt ]
+ ++ optionals (cfg.certDir != null) [ "--tls" cfg.certDir ];
+
+in
+{
+ options = {
+ services.syncplay = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "If enabled, start the Syncplay server.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8999;
+ description = ''
+ TCP port to bind to.
+ '';
+ };
+
+ salt = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Salt to allow room operator passwords generated by this server
+ instance to still work when the server is restarted.
+ '';
+ };
+
+ certDir = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ TLS certificates directory to use for encryption. See
+ <link xlink:href="https://github.com/Syncplay/syncplay/wiki/TLS-support"/>.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "nobody";
+ description = ''
+ User to use when running Syncplay.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "nogroup";
+ description = ''
+ Group to use when running Syncplay.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.syncplay = {
+ description = "Syncplay Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target "];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.syncplay}/bin/syncplay-server ${escapeShellArgs cmdArgs}";
+ User = cfg.user;
+ Group = cfg.group;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/syncthing-relay.nix b/nixpkgs/nixos/modules/services/networking/syncthing-relay.nix
new file mode 100644
index 00000000000..f5ca63e7893
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/syncthing-relay.nix
@@ -0,0 +1,121 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.syncthing.relay;
+
+ dataDirectory = "/var/lib/syncthing-relay";
+
+ relayOptions =
+ [
+ "--keys=${dataDirectory}"
+ "--listen=${cfg.listenAddress}:${toString cfg.port}"
+ "--status-srv=${cfg.statusListenAddress}:${toString cfg.statusPort}"
+ "--provided-by=${escapeShellArg cfg.providedBy}"
+ ]
+ ++ optional (cfg.pools != null) "--pools=${escapeShellArg (concatStringsSep "," cfg.pools)}"
+ ++ optional (cfg.globalRateBps != null) "--global-rate=${toString cfg.globalRateBps}"
+ ++ optional (cfg.perSessionRateBps != null) "--per-session-rate=${toString cfg.perSessionRateBps}"
+ ++ cfg.extraOptions;
+in {
+ ###### interface
+
+ options.services.syncthing.relay = {
+ enable = mkEnableOption "Syncthing relay service";
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "";
+ example = "1.2.3.4";
+ description = ''
+ Address to listen on for relay traffic.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 22067;
+ description = ''
+ Port to listen on for relay traffic. This port should be added to
+ <literal>networking.firewall.allowedTCPPorts</literal>.
+ '';
+ };
+
+ statusListenAddress = mkOption {
+ type = types.str;
+ default = "";
+ example = "1.2.3.4";
+ description = ''
+ Address to listen on for serving the relay status API.
+ '';
+ };
+
+ statusPort = mkOption {
+ type = types.port;
+ default = 22070;
+ description = ''
+ Port to listen on for serving the relay status API. This port should be
+ added to <literal>networking.firewall.allowedTCPPorts</literal>.
+ '';
+ };
+
+ pools = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = ''
+ Relay pools to join. If null, uses the default global pool.
+ '';
+ };
+
+ providedBy = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Human-readable description of the provider of the relay (you).
+ '';
+ };
+
+ globalRateBps = mkOption {
+ type = types.nullOr types.ints.positive;
+ default = null;
+ description = ''
+ Global bandwidth rate limit in bytes per second.
+ '';
+ };
+
+ perSessionRateBps = mkOption {
+ type = types.nullOr types.ints.positive;
+ default = null;
+ description = ''
+ Per session bandwidth rate limit in bytes per second.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Extra command line arguments to pass to strelaysrv.
+ '';
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.syncthing-relay = {
+ description = "Syncthing relay service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = baseNameOf dataDirectory;
+
+ Restart = "on-failure";
+ ExecStart = "${pkgs.syncthing-relay}/bin/strelaysrv ${concatStringsSep " " relayOptions}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/syncthing.nix b/nixpkgs/nixos/modules/services/networking/syncthing.nix
new file mode 100644
index 00000000000..165fd5970cf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/syncthing.nix
@@ -0,0 +1,444 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.syncthing;
+ defaultUser = "syncthing";
+
+ devices = mapAttrsToList (name: device: {
+ deviceID = device.id;
+ inherit (device) name addresses introducer;
+ }) cfg.declarative.devices;
+
+ folders = mapAttrsToList ( _: folder: {
+ inherit (folder) path id label type;
+ devices = map (device: { deviceId = cfg.declarative.devices.${device}.id; }) folder.devices;
+ rescanIntervalS = folder.rescanInterval;
+ fsWatcherEnabled = folder.watch;
+ fsWatcherDelayS = folder.watchDelay;
+ ignorePerms = folder.ignorePerms;
+ }) (filterAttrs (
+ _: folder:
+ folder.enable
+ ) cfg.declarative.folders);
+
+ # get the api key by parsing the config.xml
+ getApiKey = pkgs.writers.writeDash "getAPIKey" ''
+ ${pkgs.libxml2}/bin/xmllint \
+ --xpath 'string(configuration/gui/apikey)'\
+ ${cfg.configDir}/config.xml
+ '';
+
+ updateConfig = pkgs.writers.writeDash "merge-syncthing-config" ''
+ set -efu
+ # wait for syncthing port to open
+ until ${pkgs.curl}/bin/curl -Ss ${cfg.guiAddress} -o /dev/null; do
+ sleep 1
+ done
+
+ API_KEY=$(${getApiKey})
+ OLD_CFG=$(${pkgs.curl}/bin/curl -Ss \
+ -H "X-API-Key: $API_KEY" \
+ ${cfg.guiAddress}/rest/system/config)
+
+ # generate the new config by merging with the nixos config options
+ NEW_CFG=$(echo "$OLD_CFG" | ${pkgs.jq}/bin/jq -s '.[] as $in | $in * {
+ "devices": (${builtins.toJSON devices}${optionalString (! cfg.declarative.overrideDevices) " + $in.devices"}),
+ "folders": (${builtins.toJSON folders}${optionalString (! cfg.declarative.overrideFolders) " + $in.folders"})
+ }')
+
+ # POST the new config to syncthing
+ echo "$NEW_CFG" | ${pkgs.curl}/bin/curl -Ss \
+ -H "X-API-Key: $API_KEY" \
+ ${cfg.guiAddress}/rest/system/config -d @-
+
+ # restart syncthing after sending the new config
+ ${pkgs.curl}/bin/curl -Ss \
+ -H "X-API-Key: $API_KEY" \
+ -X POST \
+ ${cfg.guiAddress}/rest/system/restart
+ '';
+in {
+ ###### interface
+ options = {
+ services.syncthing = {
+
+ enable = mkEnableOption ''
+ Syncthing - the self-hosted open-source alternative
+ to Dropbox and Bittorrent Sync. Initial interface will be
+ available on http://127.0.0.1:8384/.
+ '';
+
+ declarative = {
+ cert = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Path to users cert.pem file, will be copied into the syncthing's
+ <literal>configDir</literal>
+ '';
+ };
+
+ key = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Path to users key.pem file, will be copied into the syncthing's
+ <literal>configDir</literal>
+ '';
+ };
+
+ overrideDevices = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to delete the devices which are not configured via the
+ <literal>declarative.devices</literal> option.
+ If set to false, devices added via the webinterface will
+ persist but will have to be deleted manually.
+ '';
+ };
+
+ devices = mkOption {
+ default = {};
+ description = ''
+ Peers/devices which syncthing should communicate with.
+ '';
+ example = {
+ bigbox = {
+ id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
+ addresses = [ "tcp://192.168.0.10:51820" ];
+ };
+ };
+ type = types.attrsOf (types.submodule ({ config, ... }: {
+ options = {
+
+ name = mkOption {
+ type = types.str;
+ default = config._module.args.name;
+ description = ''
+ Name of the device
+ '';
+ };
+
+ addresses = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ The addresses used to connect to the device.
+ If this is let empty, dynamic configuration is attempted
+ '';
+ };
+
+ id = mkOption {
+ type = types.str;
+ description = ''
+ The id of the other peer, this is mandatory. It's documented at
+ https://docs.syncthing.net/dev/device-ids.html
+ '';
+ };
+
+ introducer = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If the device should act as an introducer and be allowed
+ to add folders on this computer.
+ '';
+ };
+
+ };
+ }));
+ };
+
+ overrideFolders = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to delete the folders which are not configured via the
+ <literal>declarative.folders</literal> option.
+ If set to false, folders added via the webinterface will persist
+ but will have to be deleted manually.
+ '';
+ };
+
+ folders = mkOption {
+ default = {};
+ description = ''
+ folders which should be shared by syncthing.
+ '';
+ example = {
+ "/home/user/sync" = {
+ id = "syncme";
+ devices = [ "bigbox" ];
+ };
+ };
+ type = types.attrsOf (types.submodule ({ config, ... }: {
+ options = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ share this folder.
+ This option is useful when you want to define all folders
+ in one place, but not every machine should share all folders.
+ '';
+ };
+
+ path = mkOption {
+ type = types.str;
+ default = config._module.args.name;
+ description = ''
+ The path to the folder which should be shared.
+ '';
+ };
+
+ id = mkOption {
+ type = types.str;
+ default = config._module.args.name;
+ description = ''
+ The id of the folder. Must be the same on all devices.
+ '';
+ };
+
+ label = mkOption {
+ type = types.str;
+ default = config._module.args.name;
+ description = ''
+ The label of the folder.
+ '';
+ };
+
+ devices = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ The devices this folder should be shared with. Must be defined
+ in the <literal>declarative.devices</literal> attribute.
+ '';
+ };
+
+ rescanInterval = mkOption {
+ type = types.int;
+ default = 3600;
+ description = ''
+ How often the folders should be rescaned for changes.
+ '';
+ };
+
+ type = mkOption {
+ type = types.enum [ "sendreceive" "sendonly" "receiveonly" ];
+ default = "sendreceive";
+ description = ''
+ Whether to send only changes from this folder, only receive them
+ or propagate both.
+ '';
+ };
+
+ watch = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether the folder should be watched for changes by inotify.
+ '';
+ };
+
+ watchDelay = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ The delay after an inotify event is triggered.
+ '';
+ };
+
+ ignorePerms = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to propagate permission changes.
+ '';
+ };
+
+ };
+ }));
+ };
+ };
+
+ guiAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8384";
+ description = ''
+ Address to serve the GUI.
+ '';
+ };
+
+ systemService = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Auto launch Syncthing as a system service.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = defaultUser;
+ description = ''
+ Syncthing will be run under this user (user will be created if it doesn't exist.
+ This can be your user name).
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = defaultUser;
+ description = ''
+ Syncthing will be run under this group (group will not be created if it doesn't exist.
+ This can be your user name).
+ '';
+ };
+
+ all_proxy = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "socks5://address.com:1234";
+ description = ''
+ Overwrites all_proxy environment variable for the syncthing process to
+ the given value. This is normaly used to let relay client connect
+ through SOCKS5 proxy server.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/syncthing";
+ description = ''
+ Path where synced directories will exist.
+ '';
+ };
+
+ configDir = mkOption {
+ type = types.path;
+ description = ''
+ Path where the settings and keys will exist.
+ '';
+ default =
+ let
+ nixos = config.system.stateVersion;
+ cond = versionAtLeast nixos "19.03";
+ in cfg.dataDir + (optionalString cond "/.config/syncthing");
+ };
+
+ openDefaultPorts = mkOption {
+ type = types.bool;
+ default = false;
+ example = literalExample "true";
+ description = ''
+ Open the default ports in the firewall:
+ - TCP 22000 for transfers
+ - UDP 21027 for discovery
+ If multiple users are running syncthing on this machine, you will need to manually open a set of ports for each instance and leave this disabled.
+ Alternatively, if are running only a single instance on this machine using the default ports, enable this.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.syncthing;
+ defaultText = "pkgs.syncthing";
+ example = literalExample "pkgs.syncthing";
+ description = ''
+ Syncthing package to use.
+ '';
+ };
+ };
+ };
+
+ imports = [
+ (mkRemovedOptionModule ["services" "syncthing" "useInotify"] ''
+ This option was removed because syncthing now has the inotify functionality included under the name "fswatcher".
+ It can be enabled on a per-folder basis through the webinterface.
+ '')
+ ];
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ networking.firewall = mkIf cfg.openDefaultPorts {
+ allowedTCPPorts = [ 22000 ];
+ allowedUDPPorts = [ 21027 ];
+ };
+
+ systemd.packages = [ pkgs.syncthing ];
+
+ users.users = mkIf (cfg.systemService && cfg.user == defaultUser) {
+ ${defaultUser} =
+ { group = cfg.group;
+ home = cfg.dataDir;
+ createHome = true;
+ uid = config.ids.uids.syncthing;
+ description = "Syncthing daemon user";
+ };
+ };
+
+ users.groups = mkIf (cfg.systemService && cfg.group == defaultUser) {
+ ${defaultUser}.gid =
+ config.ids.gids.syncthing;
+ };
+
+ systemd.services = {
+ syncthing = mkIf cfg.systemService {
+ description = "Syncthing service";
+ after = [ "network.target" ];
+ environment = {
+ STNORESTART = "yes";
+ STNOUPGRADE = "yes";
+ inherit (cfg) all_proxy;
+ } // config.networking.proxy.envVars;
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Restart = "on-failure";
+ SuccessExitStatus = "2 3 4";
+ RestartForceExitStatus="3 4";
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStartPre = mkIf (cfg.declarative.cert != null || cfg.declarative.key != null)
+ "+${pkgs.writers.writeBash "syncthing-copy-keys" ''
+ install -dm700 -o ${cfg.user} -g ${cfg.group} ${cfg.configDir}
+ ${optionalString (cfg.declarative.cert != null) ''
+ install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.cert} ${cfg.configDir}/cert.pem
+ ''}
+ ${optionalString (cfg.declarative.key != null) ''
+ install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.key} ${cfg.configDir}/key.pem
+ ''}
+ ''}"
+ ;
+ ExecStart = ''
+ ${cfg.package}/bin/syncthing \
+ -no-browser \
+ -gui-address=${cfg.guiAddress} \
+ -home=${cfg.configDir}
+ '';
+ };
+ };
+ syncthing-init = mkIf (
+ cfg.declarative.devices != {} || cfg.declarative.folders != {}
+ ) {
+ after = [ "syncthing.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = cfg.user;
+ RemainAfterExit = true;
+ Type = "oneshot";
+ ExecStart = updateConfig;
+ };
+ };
+
+ syncthing-resume = {
+ wantedBy = [ "suspend.target" ];
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tcpcrypt.nix b/nixpkgs/nixos/modules/services/networking/tcpcrypt.nix
new file mode 100644
index 00000000000..a0ccb995009
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tcpcrypt.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.networking.tcpcrypt;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ networking.tcpcrypt.enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable opportunistic TCP encryption. If the other end
+ speaks Tcpcrypt, then your traffic will be encrypted; otherwise
+ it will be sent in clear text. Thus, Tcpcrypt alone provides no
+ guarantees -- it is best effort. If, however, a Tcpcrypt
+ connection is successful and any attackers that exist are
+ passive, then Tcpcrypt guarantees privacy.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users = singleton {
+ name = "tcpcryptd";
+ uid = config.ids.uids.tcpcryptd;
+ description = "tcpcrypt daemon user";
+ };
+
+ systemd.services.tcpcrypt = {
+ description = "tcpcrypt";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ path = [ pkgs.iptables pkgs.tcpcrypt pkgs.procps ];
+
+ preStart = ''
+ mkdir -p /run/tcpcryptd
+ chown tcpcryptd /run/tcpcryptd
+ sysctl -n net.ipv4.tcp_ecn > /run/tcpcryptd/pre-tcpcrypt-ecn-state
+ sysctl -w net.ipv4.tcp_ecn=0
+
+ iptables -t raw -N nixos-tcpcrypt
+ iptables -t raw -A nixos-tcpcrypt -p tcp -m mark --mark 0x0/0x10 -j NFQUEUE --queue-num 666
+ iptables -t raw -I PREROUTING -j nixos-tcpcrypt
+
+ iptables -t mangle -N nixos-tcpcrypt
+ iptables -t mangle -A nixos-tcpcrypt -p tcp -m mark --mark 0x0/0x10 -j NFQUEUE --queue-num 666
+ iptables -t mangle -I POSTROUTING -j nixos-tcpcrypt
+ '';
+
+ script = "tcpcryptd -x 0x10";
+
+ postStop = ''
+ if [ -f /run/tcpcryptd/pre-tcpcrypt-ecn-state ]; then
+ sysctl -w net.ipv4.tcp_ecn=$(cat /run/tcpcryptd/pre-tcpcrypt-ecn-state)
+ fi
+
+ iptables -t mangle -D POSTROUTING -j nixos-tcpcrypt || true
+ iptables -t raw -D PREROUTING -j nixos-tcpcrypt || true
+
+ iptables -t raw -F nixos-tcpcrypt || true
+ iptables -t raw -X nixos-tcpcrypt || true
+
+ iptables -t mangle -F nixos-tcpcrypt || true
+ iptables -t mangle -X nixos-tcpcrypt || true
+ '';
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/teamspeak3.nix b/nixpkgs/nixos/modules/services/networking/teamspeak3.nix
new file mode 100644
index 00000000000..fadb32dcd77
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/teamspeak3.nix
@@ -0,0 +1,142 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ ts3 = pkgs.teamspeak_server;
+ cfg = config.services.teamspeak3;
+ user = "teamspeak";
+ group = "teamspeak";
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.teamspeak3 = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to run the Teamspeak3 voice communication server daemon.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/teamspeak3-server";
+ description = ''
+ Directory to store TS3 database and other state/data files.
+ '';
+ };
+
+ logPath = mkOption {
+ type = types.path;
+ default = "/var/log/teamspeak3-server/";
+ description = ''
+ Directory to store log files in.
+ '';
+ };
+
+ voiceIP = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "0.0.0.0";
+ description = ''
+ IP on which the server instance will listen for incoming voice connections. Defaults to any IP.
+ '';
+ };
+
+ defaultVoicePort = mkOption {
+ type = types.int;
+ default = 9987;
+ description = ''
+ Default UDP port for clients to connect to virtual servers - used for first virtual server, subsequent ones will open on incrementing port numbers by default.
+ '';
+ };
+
+ fileTransferIP = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "0.0.0.0";
+ description = ''
+ IP on which the server instance will listen for incoming file transfer connections. Defaults to any IP.
+ '';
+ };
+
+ fileTransferPort = mkOption {
+ type = types.int;
+ default = 30033;
+ description = ''
+ TCP port opened for file transfers.
+ '';
+ };
+
+ queryIP = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "0.0.0.0";
+ description = ''
+ IP on which the server instance will listen for incoming ServerQuery connections. Defaults to any IP.
+ '';
+ };
+
+ queryPort = mkOption {
+ type = types.int;
+ default = 10011;
+ description = ''
+ TCP port opened for ServerQuery connections.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ users.users.teamspeak = {
+ description = "Teamspeak3 voice communication server daemon";
+ group = group;
+ uid = config.ids.uids.teamspeak;
+ home = cfg.dataDir;
+ createHome = true;
+ };
+
+ users.groups.teamspeak = {
+ gid = config.ids.gids.teamspeak;
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.logPath}' - ${user} ${group} - -"
+ ];
+
+ systemd.services.teamspeak3-server = {
+ description = "Teamspeak3 voice communication server daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = ''
+ ${ts3}/bin/ts3server \
+ dbsqlpath=${ts3}/lib/teamspeak/sql/ logpath=${cfg.logPath} \
+ ${optionalString (cfg.voiceIP != null) "voice_ip=${cfg.voiceIP}"} \
+ default_voice_port=${toString cfg.defaultVoicePort} \
+ ${optionalString (cfg.fileTransferIP != null) "filetransfer_ip=${cfg.fileTransferIP}"} \
+ filetransfer_port=${toString cfg.fileTransferPort} \
+ ${optionalString (cfg.queryIP != null) "query_ip=${cfg.queryIP}"} \
+ query_port=${toString cfg.queryPort} license_accepted=1
+ '';
+ WorkingDirectory = cfg.dataDir;
+ User = user;
+ Group = group;
+ };
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ arobyn ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tedicross.nix b/nixpkgs/nixos/modules/services/networking/tedicross.nix
new file mode 100644
index 00000000000..0716975f594
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tedicross.nix
@@ -0,0 +1,100 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ dataDir = "/var/lib/tedicross";
+ cfg = config.services.tedicross;
+ configJSON = pkgs.writeText "tedicross-settings.json" (builtins.toJSON cfg.config);
+ configYAML = pkgs.runCommand "tedicross-settings.yaml" { preferLocalBuild = true; } ''
+ ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
+ '';
+
+in {
+ options = {
+ services.tedicross = {
+ enable = mkEnableOption "the TediCross Telegram-Discord bridge service";
+
+ config = mkOption {
+ type = types.attrs;
+ # from https://github.com/TediCross/TediCross/blob/master/example.settings.yaml
+ example = literalExample ''
+ {
+ telegram = {
+ useFirstNameInsteadOfUsername = false;
+ colonAfterSenderName = false;
+ skipOldMessages = true;
+ sendEmojiWithStickers = true;
+ };
+ discord = {
+ useNickname = false;
+ skipOldMessages = true;
+ displayTelegramReplies = "embed";
+ replyLength = 100;
+ };
+ bridges = [
+ {
+ name = "Default bridge";
+ direction = "both";
+ telegram = {
+ chatId = -123456789;
+ relayJoinMessages = true;
+ relayLeaveMessages = true;
+ sendUsernames = true;
+ ignoreCommands = true;
+ };
+ discord = {
+ serverId = "DISCORD_SERVER_ID";
+ channelId = "DISCORD_CHANNEL_ID";
+ relayJoinMessages = true;
+ relayLeaveMessages = true;
+ sendUsernames = true;
+ crossDeleteOnTelegram = true;
+ };
+ }
+ ];
+
+ debug = false;
+ }
+ '';
+ description = ''
+ <filename>settings.yaml</filename> configuration as a Nix attribute set.
+ Secret tokens should be specified using <option>environmentFile</option>
+ instead of this world-readable file.
+ '';
+ };
+
+ environmentFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ File containing environment variables to be passed to the TediCross service,
+ in which secret tokens can be specified securely using the
+ <literal>TELEGRAM_BOT_TOKEN</literal> and <literal>DISCORD_BOT_TOKEN</literal>
+ keys.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # from https://github.com/TediCross/TediCross/blob/master/guides/autostart/Linux.md
+ systemd.services.tedicross = {
+ description = "TediCross Telegram-Discord bridge service";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${pkgs.nodePackages.tedicross}/bin/tedicross --config='${configYAML}' --data-dir='${dataDir}'";
+ Restart = "always";
+ DynamicUser = true;
+ StateDirectory = baseNameOf dataDir;
+ EnvironmentFile = cfg.environmentFile;
+ };
+ };
+ };
+
+ meta.maintainers = with maintainers; [ pacien ];
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/tftpd.nix b/nixpkgs/nixos/modules/services/networking/tftpd.nix
new file mode 100644
index 00000000000..c9c0a2b321d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tftpd.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.tftpd.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable tftpd, a Trivial File Transfer Protocol server.
+ The server will be run as an xinetd service.
+ '';
+ };
+
+ services.tftpd.path = mkOption {
+ type = types.path;
+ default = "/srv/tftp";
+ description = ''
+ Where the tftp server files are stored.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.tftpd.enable {
+
+ services.xinetd.enable = true;
+
+ services.xinetd.services = singleton
+ { name = "tftp";
+ protocol = "udp";
+ server = "${pkgs.netkittftp}/sbin/in.tftpd";
+ serverArgs = "${config.services.tftpd.path}";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/thelounge.nix b/nixpkgs/nixos/modules/services/networking/thelounge.nix
new file mode 100644
index 00000000000..b1d23372955
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/thelounge.nix
@@ -0,0 +1,75 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+ cfg = config.services.thelounge;
+ dataDir = "/var/lib/thelounge";
+ configJsData = "module.exports = " + builtins.toJSON (
+ { private = cfg.private; port = cfg.port; } // cfg.extraConfig
+ );
+in {
+ options.services.thelounge = {
+ enable = mkEnableOption "The Lounge web IRC client";
+
+ private = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Make your The Lounge instance private. You will need to configure user
+ accounts by using the (<command>thelounge</command>) command or by adding
+ entries in <filename>${dataDir}/users</filename>. You might need to restart
+ The Lounge after making changes to the state directory.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 9000;
+ description = "TCP port to listen on for http connections.";
+ };
+
+ extraConfig = mkOption {
+ default = {};
+ type = types.attrs;
+ example = literalExample ''{
+ reverseProxy = true;
+ defaults = {
+ name = "Your Network";
+ host = "localhost";
+ port = 6697;
+ };
+ }'';
+ description = ''
+ The Lounge's <filename>config.js</filename> contents as attribute set (will be
+ converted to JSON to generate the configuration file).
+
+ The options defined here will be merged to the default configuration file.
+ Note: In case of duplicate configuration, options from <option>extraConfig</option> have priority.
+
+ Documentation: <link xlink:href="https://thelounge.chat/docs/server/configuration" />
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.thelounge = {
+ description = "thelounge service user";
+ group = "thelounge";
+ };
+ users.groups.thelounge = {};
+ systemd.services.thelounge = {
+ description = "The Lounge web IRC client";
+ wantedBy = [ "multi-user.target" ];
+ environment = { THELOUNGE_HOME = dataDir; };
+ preStart = "ln -sf ${pkgs.writeText "config.js" configJsData} ${dataDir}/config.js";
+ serviceConfig = {
+ User = "thelounge";
+ StateDirectory = baseNameOf dataDir;
+ ExecStart = "${pkgs.thelounge}/bin/thelounge start";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.thelounge ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tinc.nix b/nixpkgs/nixos/modules/services/networking/tinc.nix
new file mode 100644
index 00000000000..e98aafc2093
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tinc.nix
@@ -0,0 +1,212 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.tinc;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.tinc = {
+
+ networks = mkOption {
+ default = { };
+ type = with types; attrsOf (submodule {
+ options = {
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra lines to add to the tinc service configuration file.
+ '';
+ };
+
+ name = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The name of the node which is used as an identifier when communicating
+ with the remote nodes in the mesh. If null then the hostname of the system
+ is used to derive a name (note that tinc may replace non-alphanumeric characters in
+ hostnames by underscores).
+ '';
+ };
+
+ ed25519PrivateKeyFile = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = ''
+ Path of the private ed25519 keyfile.
+ '';
+ };
+
+ debugLevel = mkOption {
+ default = 0;
+ type = types.addCheck types.int (l: l >= 0 && l <= 5);
+ description = ''
+ The amount of debugging information to add to the log. 0 means little
+ logging while 5 is the most logging. <command>man tincd</command> for
+ more details.
+ '';
+ };
+
+ hosts = mkOption {
+ default = { };
+ type = types.attrsOf types.lines;
+ description = ''
+ The name of the host in the network as well as the configuration for that host.
+ This name should only contain alphanumerics and underscores.
+ '';
+ };
+
+ interfaceType = mkOption {
+ default = "tun";
+ type = types.enum [ "tun" "tap" ];
+ description = ''
+ The type of virtual interface used for the network connection
+ '';
+ };
+
+ listenAddress = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The ip address to listen on for incoming connections.
+ '';
+ };
+
+ bindToAddress = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The ip address to bind to (both listen on and send packets from).
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.tinc_pre;
+ defaultText = "pkgs.tinc_pre";
+ description = ''
+ The package to use for the tinc daemon's binary.
+ '';
+ };
+
+ chroot = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Change process root directory to the directory where the config file is located (/etc/tinc/netname/), for added security.
+ The chroot is performed after all the initialization is done, after writing pid files and opening network sockets.
+
+ Note that tinc can't run scripts anymore (such as tinc-down or host-up), unless it is setup to be runnable inside chroot environment.
+ '';
+ };
+ };
+ });
+
+ description = ''
+ Defines the tinc networks which will be started.
+ Each network invokes a different daemon.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf (cfg.networks != { }) {
+
+ environment.etc = fold (a: b: a // b) { }
+ (flip mapAttrsToList cfg.networks (network: data:
+ flip mapAttrs' data.hosts (host: text: nameValuePair
+ ("tinc/${network}/hosts/${host}")
+ ({ mode = "0644"; user = "tinc.${network}"; inherit text; })
+ ) // {
+ "tinc/${network}/tinc.conf" = {
+ mode = "0444";
+ text = ''
+ Name = ${if data.name == null then "$HOST" else data.name}
+ DeviceType = ${data.interfaceType}
+ ${optionalString (data.ed25519PrivateKeyFile != null) "Ed25519PrivateKeyFile = ${data.ed25519PrivateKeyFile}"}
+ ${optionalString (data.listenAddress != null) "ListenAddress = ${data.listenAddress}"}
+ ${optionalString (data.bindToAddress != null) "BindToAddress = ${data.bindToAddress}"}
+ Interface = tinc.${network}
+ ${data.extraConfig}
+ '';
+ };
+ }
+ ));
+
+ systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair
+ ("tinc.${network}")
+ ({
+ description = "Tinc Daemon - ${network}";
+ wantedBy = [ "multi-user.target" ];
+ path = [ data.package ];
+ restartTriggers = [ config.environment.etc."tinc/${network}/tinc.conf".source ];
+ serviceConfig = {
+ Type = "simple";
+ Restart = "always";
+ RestartSec = "3";
+ ExecStart = "${data.package}/bin/tincd -D -U tinc.${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}";
+ };
+ preStart = ''
+ mkdir -p /etc/tinc/${network}/hosts
+ chown tinc.${network} /etc/tinc/${network}/hosts
+ mkdir -p /etc/tinc/${network}/invitations
+ chown tinc.${network} /etc/tinc/${network}/invitations
+
+ # Determine how we should generate our keys
+ if type tinc >/dev/null 2>&1; then
+ # Tinc 1.1+ uses the tinc helper application for key generation
+ ${if data.ed25519PrivateKeyFile != null then " # Keyfile managed by nix" else ''
+ # Prefer ED25519 keys (only in 1.1+)
+ [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys
+ ''}
+ # Otherwise use RSA keys
+ [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096
+ else
+ # Tinc 1.0 uses the tincd application
+ [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096
+ fi
+ '';
+ })
+ );
+
+ environment.systemPackages = let
+ cli-wrappers = pkgs.stdenv.mkDerivation {
+ name = "tinc-cli-wrappers";
+ buildInputs = [ pkgs.makeWrapper ];
+ buildCommand = ''
+ mkdir -p $out/bin
+ ${concatStringsSep "\n" (mapAttrsToList (network: data:
+ optionalString (versionAtLeast data.package.version "1.1pre") ''
+ makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" \
+ --add-flags "--pidfile=/run/tinc.${network}.pid" \
+ --add-flags "--config=/etc/tinc/${network}"
+ '') cfg.networks)}
+ '';
+ };
+ in [ cli-wrappers ];
+
+ users.users = flip mapAttrs' cfg.networks (network: _:
+ nameValuePair ("tinc.${network}") ({
+ description = "Tinc daemon user for ${network}";
+ isSystemUser = true;
+ })
+ );
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tinydns.nix b/nixpkgs/nixos/modules/services/networking/tinydns.nix
new file mode 100644
index 00000000000..7d5db71601e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tinydns.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ ###### interface
+
+ options = {
+ services.tinydns = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to run the tinydns dns server";
+ };
+
+ data = mkOption {
+ type = types.lines;
+ default = "";
+ description = "The DNS data to serve, in the format described by tinydns-data(8)";
+ };
+
+ ip = mkOption {
+ default = "0.0.0.0";
+ type = types.str;
+ description = "IP address on which to listen for connections";
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf config.services.tinydns.enable {
+ environment.systemPackages = [ pkgs.djbdns ];
+
+ users.users.tinydns = {};
+
+ systemd.services.tinydns = {
+ description = "djbdns tinydns server";
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [ daemontools djbdns ];
+ preStart = ''
+ rm -rf /var/lib/tinydns
+ tinydns-conf tinydns tinydns /var/lib/tinydns ${config.services.tinydns.ip}
+ cd /var/lib/tinydns/root/
+ ln -sf ${pkgs.writeText "tinydns-data" config.services.tinydns.data} data
+ tinydns-data
+ '';
+ script = ''
+ cd /var/lib/tinydns
+ exec ./run
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix b/nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix
new file mode 100644
index 00000000000..1d349215169
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ home = "/var/lib/tox-bootstrapd";
+ PIDFile = "${home}/pid";
+
+ pkg = pkgs.libtoxcore;
+ cfg = config.services.toxBootstrapd;
+ cfgFile = builtins.toFile "tox-bootstrapd.conf"
+ ''
+ port = ${toString cfg.port}
+ keys_file_path = "${home}/keys"
+ pid_file_path = "${PIDFile}"
+ ${cfg.extraConfig}
+ '';
+in
+{
+ options =
+ { services.toxBootstrapd =
+ { enable = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Whether to enable the Tox DHT bootstrap daemon.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 33445;
+ description = "Listening port (UDP).";
+ };
+
+ keysFile = mkOption {
+ type = types.str;
+ default = "${home}/keys";
+ description = "Node key file.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ ''
+ Configuration for bootstrap daemon.
+ See <link xlink:href="https://github.com/irungentoo/toxcore/blob/master/other/bootstrap_daemon/tox-bootstrapd.conf"/>
+ and <link xlink:href="http://wiki.tox.im/Nodes"/>.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf config.services.toxBootstrapd.enable {
+
+ users.users = singleton
+ { name = "tox-bootstrapd";
+ uid = config.ids.uids.tox-bootstrapd;
+ description = "Tox bootstrap daemon user";
+ inherit home;
+ createHome = true;
+ };
+
+ systemd.services.tox-bootstrapd = {
+ description = "Tox DHT bootstrap daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig =
+ { ExecStart = "${pkg}/bin/tox-bootstrapd --config=${cfgFile}";
+ Type = "forking";
+ inherit PIDFile;
+ User = "tox-bootstrapd";
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tox-node.nix b/nixpkgs/nixos/modules/services/networking/tox-node.nix
new file mode 100644
index 00000000000..c24e7fd1285
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tox-node.nix
@@ -0,0 +1,95 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+ pkg = pkgs.tox-node;
+ cfg = config.services.tox-node;
+ homeDir = "/var/lib/tox-node";
+
+ configFile = let
+ # fetchurl should be switched to getting this file from tox-node.src once
+ # the dpkg directory is in a release
+ src = pkgs.fetchurl {
+ url = "https://raw.githubusercontent.com/tox-rs/tox-node/master/dpkg/config.yml";
+ sha256 = "1431wzpzm786mcvyzk1rp7ar418n45dr75hdggxvlm7pkpam31xa";
+ };
+ confJSON = pkgs.writeText "config.json" (
+ builtins.toJSON {
+ log-type = cfg.logType;
+ keys-file = cfg.keysFile;
+ udp-address = cfg.udpAddress;
+ tcp-addresses = cfg.tcpAddresses;
+ tcp-connections-limit = cfg.tcpConnectionLimit;
+ lan-discovery = cfg.lanDiscovery;
+ threads = cfg.threads;
+ motd = cfg.motd;
+ }
+ );
+ in with pkgs; runCommand "config.yml" {} ''
+ ${remarshal}/bin/remarshal -if yaml -of json ${src} -o src.json
+ ${jq}/bin/jq -s '(.[0] | with_entries( select(.key == "bootstrap-nodes"))) * .[1]' src.json ${confJSON} > $out
+ '';
+
+in {
+ options.services.tox-node = {
+ enable = mkEnableOption "Tox Node service";
+
+ logType = mkOption {
+ type = types.enum [ "Stderr" "Stdout" "Syslog" "None" ];
+ default = "Stderr";
+ description = "Logging implementation.";
+ };
+ keysFile = mkOption {
+ type = types.str;
+ default = "${homeDir}/keys";
+ description = "Path to the file where DHT keys are stored.";
+ };
+ udpAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0:33445";
+ description = "UDP address to run DHT node.";
+ };
+ tcpAddresses = mkOption {
+ type = types.listOf types.str;
+ default = [ "0.0.0.0:33445" ];
+ description = "TCP addresses to run TCP relay.";
+ };
+ tcpConnectionLimit = mkOption {
+ type = types.int;
+ default = 8192;
+ description = "Maximum number of active TCP connections relay can hold";
+ };
+ lanDiscovery = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Enable local network discovery.";
+ };
+ threads = mkOption {
+ type = types.int;
+ default = 1;
+ description = "Number of threads for execution";
+ };
+ motd = mkOption {
+ type = types.str;
+ default = "Hi from tox-rs! I'm up {{uptime}}. TCP: incoming {{tcp_packets_in}}, outgoing {{tcp_packets_out}}, UDP: incoming {{udp_packets_in}}, outgoing {{udp_packets_out}}";
+ description = "Message of the day";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ systemd.services.tox-node = {
+ description = "Tox Node";
+
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "${pkg}/bin/tox-node config ${configFile}";
+ StateDirectory = "tox-node";
+ DynamicUser = true;
+ Restart = "always";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/toxvpn.nix b/nixpkgs/nixos/modules/services/networking/toxvpn.nix
new file mode 100644
index 00000000000..9e97faeebc1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/toxvpn.nix
@@ -0,0 +1,68 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+ options = {
+ services.toxvpn = {
+ enable = mkEnableOption "toxvpn running on startup";
+
+ localip = mkOption {
+ type = types.str;
+ default = "10.123.123.1";
+ description = "your ip on the vpn";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 33445;
+ description = "udp port for toxcore, port-forward to help with connectivity if you run many nodes behind one NAT";
+ };
+
+ auto_add_peers = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ''[ "toxid1" "toxid2" ]'';
+ description = "peers to automatically connect to on startup";
+ };
+ };
+ };
+
+ config = mkIf config.services.toxvpn.enable {
+ systemd.services.toxvpn = {
+ description = "toxvpn daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ preStart = ''
+ mkdir -p /run/toxvpn || true
+ chown toxvpn /run/toxvpn
+ '';
+
+ path = [ pkgs.toxvpn ];
+
+ script = ''
+ exec toxvpn -i ${config.services.toxvpn.localip} -l /run/toxvpn/control -u toxvpn -p ${toString config.services.toxvpn.port} ${lib.concatMapStringsSep " " (x: "-a ${x}") config.services.toxvpn.auto_add_peers}
+ '';
+
+ serviceConfig = {
+ KillMode = "process";
+ Restart = "on-success";
+ Type = "notify";
+ };
+
+ restartIfChanged = false; # Likely to be used for remote admin
+ };
+
+ environment.systemPackages = [ pkgs.toxvpn ];
+
+ users.users = {
+ toxvpn = {
+ uid = config.ids.uids.toxvpn;
+ home = "/var/lib/toxvpn";
+ createHome = true;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tvheadend.nix b/nixpkgs/nixos/modules/services/networking/tvheadend.nix
new file mode 100644
index 00000000000..ccf87999663
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tvheadend.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.tvheadend;
+ pidFile = "${config.users.users.tvheadend.home}/tvheadend.pid";
+in
+
+{
+ options = {
+ services.tvheadend = {
+ enable = mkEnableOption "Tvheadend";
+ httpPort = mkOption {
+ type = types.int;
+ default = 9981;
+ description = "Port to bind HTTP to.";
+ };
+
+ htspPort = mkOption {
+ type = types.int;
+ default = 9982;
+ description = "Port to bind HTSP to.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.tvheadend = {
+ description = "Tvheadend Service user";
+ home = "/var/lib/tvheadend";
+ createHome = true;
+ uid = config.ids.uids.tvheadend;
+ };
+
+ systemd.services.tvheadend = {
+ description = "Tvheadend TV streaming server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ Type = "forking";
+ PIDFile = pidFile;
+ Restart = "always";
+ RestartSec = 5;
+ User = "tvheadend";
+ Group = "video";
+ ExecStart = ''
+ ${pkgs.tvheadend}/bin/tvheadend \
+ --http_port ${toString cfg.httpPort} \
+ --htsp_port ${toString cfg.htspPort} \
+ -f \
+ -C \
+ -p ${pidFile} \
+ -u tvheadend \
+ -g video
+ '';
+ ExecStop = "${pkgs.coreutils}/bin/rm ${pidFile}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/unbound.nix b/nixpkgs/nixos/modules/services/networking/unbound.nix
new file mode 100644
index 00000000000..3cf82e8839b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/unbound.nix
@@ -0,0 +1,141 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.unbound;
+
+ stateDir = "/var/lib/unbound";
+
+ access = concatMapStringsSep "\n " (x: "access-control: ${x} allow") cfg.allowedAccess;
+
+ interfaces = concatMapStringsSep "\n " (x: "interface: ${x}") cfg.interfaces;
+
+ isLocalAddress = x: substring 0 3 x == "::1" || substring 0 9 x == "127.0.0.1";
+
+ forward =
+ optionalString (any isLocalAddress cfg.forwardAddresses) ''
+ do-not-query-localhost: no
+ '' +
+ optionalString (cfg.forwardAddresses != []) ''
+ forward-zone:
+ name: .
+ '' +
+ concatMapStringsSep "\n" (x: " forward-addr: ${x}") cfg.forwardAddresses;
+
+ rootTrustAnchorFile = "${stateDir}/root.key";
+
+ trustAnchor = optionalString cfg.enableRootTrustAnchor
+ "auto-trust-anchor-file: ${rootTrustAnchorFile}";
+
+ confFile = pkgs.writeText "unbound.conf" ''
+ server:
+ directory: "${stateDir}"
+ username: unbound
+ chroot: "${stateDir}"
+ pidfile: ""
+ ${interfaces}
+ ${access}
+ ${trustAnchor}
+ ${cfg.extraConfig}
+ ${forward}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.unbound = {
+
+ enable = mkEnableOption "Unbound domain name server";
+
+ allowedAccess = mkOption {
+ default = [ "127.0.0.0/24" ];
+ type = types.listOf types.str;
+ description = "What networks are allowed to use unbound as a resolver.";
+ };
+
+ interfaces = mkOption {
+ default = [ "127.0.0.1" ] ++ optional config.networking.enableIPv6 "::1";
+ type = types.listOf types.str;
+ description = "What addresses the server should listen on.";
+ };
+
+ forwardAddresses = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = "What servers to forward queries to.";
+ };
+
+ enableRootTrustAnchor = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Use and update root trust anchor for DNSSEC validation.";
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra unbound config. See
+ <citerefentry><refentrytitle>unbound.conf</refentrytitle><manvolnum>8
+ </manvolnum></citerefentry>.
+ '';
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.unbound ];
+
+ users.users.unbound = {
+ description = "unbound daemon user";
+ isSystemUser = true;
+ };
+
+ networking.resolvconf.useLocalResolver = mkDefault true;
+
+ systemd.services.unbound = {
+ description = "Unbound recursive Domain Name Server";
+ after = [ "network.target" ];
+ before = [ "nss-lookup.target" ];
+ wants = [ "nss-lookup.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}/dev/
+ cp ${confFile} ${stateDir}/unbound.conf
+ ${optionalString cfg.enableRootTrustAnchor ''
+ ${pkgs.unbound}/bin/unbound-anchor -a ${rootTrustAnchorFile} || echo "Root anchor updated!"
+ chown unbound ${stateDir} ${rootTrustAnchorFile}
+ ''}
+ touch ${stateDir}/dev/random
+ ${pkgs.utillinux}/bin/mount --bind -n /dev/urandom ${stateDir}/dev/random
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkgs.unbound}/bin/unbound -d -c ${stateDir}/unbound.conf";
+ ExecStopPost="${pkgs.utillinux}/bin/umount ${stateDir}/dev/random";
+
+ ProtectSystem = true;
+ ProtectHome = true;
+ PrivateDevices = true;
+ Restart = "always";
+ RestartSec = "5s";
+ };
+ };
+
+ # If networkmanager is enabled, ask it to interface with unbound.
+ networking.networkmanager.dns = "unbound";
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/unifi.nix b/nixpkgs/nixos/modules/services/networking/unifi.nix
new file mode 100644
index 00000000000..c922ba15960
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/unifi.nix
@@ -0,0 +1,189 @@
+{ config, lib, pkgs, utils, ... }:
+with lib;
+let
+ cfg = config.services.unifi;
+ stateDir = "/var/lib/unifi";
+ cmd = ''
+ @${cfg.jrePackage}/bin/java java \
+ ${optionalString (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m"} \
+ ${optionalString (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m"} \
+ -jar ${stateDir}/lib/ace.jar
+ '';
+ mountPoints = [
+ {
+ what = "${cfg.unifiPackage}/dl";
+ where = "${stateDir}/dl";
+ }
+ {
+ what = "${cfg.unifiPackage}/lib";
+ where = "${stateDir}/lib";
+ }
+ {
+ what = "${cfg.mongodbPackage}/bin";
+ where = "${stateDir}/bin";
+ }
+ {
+ what = "${cfg.dataDir}";
+ where = "${stateDir}/data";
+ }
+ ];
+ systemdMountPoints = map (m: "${utils.escapeSystemdPath m.where}.mount") mountPoints;
+in
+{
+
+ options = {
+
+ services.unifi.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether or not to enable the unifi controller service.
+ '';
+ };
+
+ services.unifi.jrePackage = mkOption {
+ type = types.package;
+ default = pkgs.jre8;
+ defaultText = "pkgs.jre8";
+ description = ''
+ The JRE package to use. Check the release notes to ensure it is supported.
+ '';
+ };
+
+ services.unifi.unifiPackage = mkOption {
+ type = types.package;
+ default = pkgs.unifiLTS;
+ defaultText = "pkgs.unifiLTS";
+ description = ''
+ The unifi package to use.
+ '';
+ };
+
+ services.unifi.mongodbPackage = mkOption {
+ type = types.package;
+ default = pkgs.mongodb;
+ defaultText = "pkgs.mongodb";
+ description = ''
+ The mongodb package to use.
+ '';
+ };
+
+ services.unifi.dataDir = mkOption {
+ type = types.str;
+ default = "${stateDir}/data";
+ description = ''
+ Where to store the database and other data.
+
+ This directory will be bind-mounted to ${stateDir}/data as part of the service startup.
+ '';
+ };
+
+ services.unifi.openPorts = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether or not to open the minimum required ports on the firewall.
+
+ This is necessary to allow firmware upgrades and device discovery to
+ work. For remote login, you should additionally open (or forward) port
+ 8443.
+ '';
+ };
+
+ services.unifi.initialJavaHeapSize = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 1024;
+ description = ''
+ Set the initial heap size for the JVM in MB. If this option isn't set, the
+ JVM will decide this value at runtime.
+ '';
+ };
+
+ services.unifi.maximumJavaHeapSize = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 4096;
+ description = ''
+ Set the maximimum heap size for the JVM in MB. If this option isn't set, the
+ JVM will decide this value at runtime.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ users.users.unifi = {
+ uid = config.ids.uids.unifi;
+ description = "UniFi controller daemon user";
+ home = "${stateDir}";
+ };
+
+ networking.firewall = mkIf cfg.openPorts {
+ # https://help.ubnt.com/hc/en-us/articles/218506997
+ allowedTCPPorts = [
+ 8080 # Port for UAP to inform controller.
+ 8880 # Port for HTTP portal redirect, if guest portal is enabled.
+ 8843 # Port for HTTPS portal redirect, ditto.
+ 6789 # Port for UniFi mobile speed test.
+ ];
+ allowedUDPPorts = [
+ 3478 # UDP port used for STUN.
+ 10001 # UDP port used for device discovery.
+ ];
+ };
+
+ # We must create the binary directories as bind mounts instead of symlinks
+ # This is because the controller resolves all symlinks to absolute paths
+ # to be used as the working directory.
+ systemd.mounts = map ({ what, where }: {
+ bindsTo = [ "unifi.service" ];
+ partOf = [ "unifi.service" ];
+ unitConfig.RequiresMountsFor = stateDir;
+ options = "bind";
+ what = what;
+ where = where;
+ }) mountPoints;
+
+ systemd.tmpfiles.rules = [
+ "e '${stateDir}' 0700 unifi - - -"
+ "d '${stateDir}/data' 0700 unifi - - -"
+ ];
+
+ systemd.services.unifi = {
+ description = "UniFi controller daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ] ++ systemdMountPoints;
+ partOf = systemdMountPoints;
+ bindsTo = systemdMountPoints;
+ unitConfig.RequiresMountsFor = stateDir;
+ # This a HACK to fix missing dependencies of dynamic libs extracted from jars
+ environment.LD_LIBRARY_PATH = with pkgs.stdenv; "${cc.cc.lib}/lib";
+
+ preStart = ''
+ # Create the volatile webapps
+ rm -rf "${stateDir}/webapps"
+ mkdir -p "${stateDir}/webapps"
+ ln -s "${cfg.unifiPackage}/webapps/ROOT" "${stateDir}/webapps/ROOT"
+ '';
+
+ postStop = ''
+ rm -rf "${stateDir}/webapps"
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${(removeSuffix "\n" cmd)} start";
+ ExecStop = "${(removeSuffix "\n" cmd)} stop";
+ Restart = "on-failure";
+ User = "unifi";
+ UMask = "0077";
+ WorkingDirectory = "${stateDir}";
+ };
+ };
+
+ };
+
+ meta.maintainers = with lib.maintainers; [ erictapen ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/vsftpd.nix b/nixpkgs/nixos/modules/services/networking/vsftpd.nix
new file mode 100644
index 00000000000..67be60da567
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/vsftpd.nix
@@ -0,0 +1,235 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ /* minimal secure setup:
+
+ enable = true;
+ forceLocalLoginsSSL = true;
+ forceLocalDataSSL = true;
+ userlistDeny = false;
+ localUsers = true;
+ userlist = ["non-root-user" "other-non-root-user"];
+ rsaCertFile = "/var/vsftpd/vsftpd.pem";
+
+ */
+
+ cfg = config.services.vsftpd;
+
+ inherit (pkgs) vsftpd;
+
+ yesNoOption = nixosName: vsftpdName: default: description: {
+ cfgText = "${vsftpdName}=${if getAttr nixosName cfg then "YES" else "NO"}";
+
+ nixosOption = {
+ type = types.bool;
+ name = nixosName;
+ value = mkOption {
+ inherit description default;
+ type = types.bool;
+ };
+ };
+ };
+
+ optionDescription = [
+ (yesNoOption "anonymousUser" "anonymous_enable" false ''
+ Whether to enable the anonymous FTP user.
+ '')
+ (yesNoOption "anonymousUserNoPassword" "no_anon_password" false ''
+ Whether to disable the password for the anonymous FTP user.
+ '')
+ (yesNoOption "localUsers" "local_enable" false ''
+ Whether to enable FTP for local users.
+ '')
+ (yesNoOption "writeEnable" "write_enable" false ''
+ Whether any write activity is permitted to users.
+ '')
+ (yesNoOption "anonymousUploadEnable" "anon_upload_enable" false ''
+ Whether any uploads are permitted to anonymous users.
+ '')
+ (yesNoOption "anonymousMkdirEnable" "anon_mkdir_write_enable" false ''
+ Whether any uploads are permitted to anonymous users.
+ '')
+ (yesNoOption "chrootlocalUser" "chroot_local_user" false ''
+ Whether local users are confined to their home directory.
+ '')
+ (yesNoOption "userlistEnable" "userlist_enable" false ''
+ Whether users are included.
+ '')
+ (yesNoOption "userlistDeny" "userlist_deny" false ''
+ Specifies whether <option>userlistFile</option> is a list of user
+ names to allow or deny access.
+ The default <literal>false</literal> means whitelist/allow.
+ '')
+ (yesNoOption "forceLocalLoginsSSL" "force_local_logins_ssl" false ''
+ Only applies if <option>sslEnable</option> is true. Non anonymous (local) users
+ must use a secure SSL connection to send a password.
+ '')
+ (yesNoOption "forceLocalDataSSL" "force_local_data_ssl" false ''
+ Only applies if <option>sslEnable</option> is true. Non anonymous (local) users
+ must use a secure SSL connection for sending/receiving data on data connection.
+ '')
+ (yesNoOption "portPromiscuous" "port_promiscuous" false ''
+ Set to YES if you want to disable the PORT security check that ensures that
+ outgoing data connections can only connect to the client. Only enable if you
+ know what you are doing!
+ '')
+ (yesNoOption "ssl_tlsv1" "ssl_tlsv1" true '' '')
+ (yesNoOption "ssl_sslv2" "ssl_sslv2" false '' '')
+ (yesNoOption "ssl_sslv3" "ssl_sslv3" false '' '')
+ ];
+
+ configFile = pkgs.writeText "vsftpd.conf"
+ ''
+ ${concatMapStrings (x: "${x.cfgText}\n") optionDescription}
+ ${optionalString (cfg.rsaCertFile != null) ''
+ ssl_enable=YES
+ rsa_cert_file=${cfg.rsaCertFile}
+ ''}
+ ${optionalString (cfg.rsaKeyFile != null) ''
+ rsa_private_key_file=${cfg.rsaKeyFile}
+ ''}
+ ${optionalString (cfg.userlistFile != null) ''
+ userlist_file=${cfg.userlistFile}
+ ''}
+ background=YES
+ listen=YES
+ nopriv_user=vsftpd
+ secure_chroot_dir=/var/empty
+ syslog_enable=YES
+ ${optionalString (pkgs.stdenv.hostPlatform.system == "x86_64-linux") ''
+ seccomp_sandbox=NO
+ ''}
+ anon_umask=${cfg.anonymousUmask}
+ ${optionalString cfg.anonymousUser ''
+ anon_root=${cfg.anonymousUserHome}
+ ''}
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.vsftpd = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable the vsftpd FTP server.";
+ };
+
+ userlist = mkOption {
+ default = [];
+ description = "See <option>userlistFile</option>.";
+ };
+
+ userlistFile = mkOption {
+ type = types.path;
+ default = pkgs.writeText "userlist" (concatMapStrings (x: "${x}\n") cfg.userlist);
+ defaultText = "pkgs.writeText \"userlist\" (concatMapStrings (x: \"\${x}\n\") cfg.userlist)";
+ description = ''
+ Newline separated list of names to be allowed/denied if <option>userlistEnable</option>
+ is <literal>true</literal>. Meaning see <option>userlistDeny</option>.
+
+ The default is a file containing the users from <option>userlist</option>.
+
+ If explicitely set to null userlist_file will not be set in vsftpd's config file.
+ '';
+ };
+
+ anonymousUserHome = mkOption {
+ type = types.path;
+ default = "/home/ftp/";
+ description = ''
+ Directory to consider the HOME of the anonymous user.
+ '';
+ };
+
+ rsaCertFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "RSA certificate file.";
+ };
+
+ rsaKeyFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = "RSA private key file.";
+ };
+
+ anonymousUmask = mkOption {
+ type = types.str;
+ default = "077";
+ example = "002";
+ description = "Anonymous write umask.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "ftpd_banner=Hello";
+ description = "Extra configuration to add at the bottom of the generated configuration file.";
+ };
+
+ } // (listToAttrs (catAttrs "nixosOption" optionDescription));
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = singleton
+ { assertion =
+ (cfg.forceLocalLoginsSSL -> cfg.rsaCertFile != null)
+ && (cfg.forceLocalDataSSL -> cfg.rsaCertFile != null);
+ message = "vsftpd: If forceLocalLoginsSSL or forceLocalDataSSL is true then a rsaCertFile must be provided!";
+ };
+
+ users.users =
+ [ { name = "vsftpd";
+ uid = config.ids.uids.vsftpd;
+ description = "VSFTPD user";
+ home = "/homeless-shelter";
+ }
+ ] ++ optional cfg.anonymousUser
+ { name = "ftp";
+ uid = config.ids.uids.ftp;
+ group = "ftp";
+ description = "Anonymous FTP user";
+ home = cfg.anonymousUserHome;
+ };
+
+ users.groups.ftp.gid = config.ids.gids.ftp;
+
+ # If you really have to access root via FTP use mkOverride or userlistDeny
+ # = false and whitelist root
+ services.vsftpd.userlist = if cfg.userlistDeny then ["root"] else [];
+
+ systemd.services.vsftpd =
+ { description = "Vsftpd Server";
+
+ wantedBy = [ "multi-user.target" ];
+
+ preStart =
+ optionalString cfg.anonymousUser
+ ''
+ mkdir -p -m 555 ${cfg.anonymousUserHome}
+ chown -R ftp:ftp ${cfg.anonymousUserHome}
+ '';
+
+ serviceConfig.ExecStart = "@${vsftpd}/sbin/vsftpd vsftpd ${configFile}";
+ serviceConfig.Restart = "always";
+ serviceConfig.Type = "forking";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wakeonlan.nix b/nixpkgs/nixos/modules/services/networking/wakeonlan.nix
new file mode 100644
index 00000000000..ebfba263cd8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wakeonlan.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ interfaces = config.services.wakeonlan.interfaces;
+
+ ethtool = "${pkgs.ethtool}/sbin/ethtool";
+
+ passwordParameter = password : if (password == "") then "" else
+ "sopass ${password}";
+
+ methodParameter = {method, password} :
+ if method == "magicpacket" then "wol g"
+ else if method == "password" then "wol s so ${passwordParameter password}"
+ else throw "Wake-On-Lan method not supported";
+
+ line = { interface, method ? "magicpacket", password ? "" }: ''
+ ${ethtool} -s ${interface} ${methodParameter {inherit method password;}}
+ '';
+
+ concatStrings = fold (x: y: x + y) "";
+ lines = concatStrings (map (l: line l) interfaces);
+
+in
+{
+
+ ###### interface
+
+ options = {
+
+ services.wakeonlan.interfaces = mkOption {
+ default = [ ];
+ example = [
+ {
+ interface = "eth0";
+ method = "password";
+ password = "00:11:22:33:44:55";
+ }
+ ];
+ description = ''
+ Interfaces where to enable Wake-On-LAN, and how. Two methods available:
+ "magicpacket" and "password". The password has the shape of six bytes
+ in hexadecimal separated by a colon each. For more information,
+ check the ethtool manual.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config.powerManagement.powerDownCommands = lines;
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/websockify.nix b/nixpkgs/nixos/modules/services/networking/websockify.nix
new file mode 100644
index 00000000000..d9177df65bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/websockify.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.networking.websockify; in {
+ options = {
+ services.networking.websockify = {
+ enable = mkOption {
+ description = "Whether to enable websockify to forward websocket connections to TCP connections.";
+
+ default = false;
+
+ type = types.bool;
+ };
+
+ sslCert = mkOption {
+ description = "Path to the SSL certificate.";
+ type = types.path;
+ };
+
+ sslKey = mkOption {
+ description = "Path to the SSL key.";
+ default = cfg.sslCert;
+ defaultText = "config.services.networking.websockify.sslCert";
+ type = types.path;
+ };
+
+ portMap = mkOption {
+ description = "Ports to map by default.";
+ default = {};
+ type = types.attrsOf types.int;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services."websockify@" = {
+ description = "Service to forward websocket connections to TCP connections (from port:to port %I)";
+ script = ''
+ IFS=':' read -a array <<< "$1"
+ ${pkgs.pythonPackages.websockify}/bin/websockify --ssl-only \
+ --cert=${cfg.sslCert} --key=${cfg.sslKey} 0.0.0.0:''${array[0]} 0.0.0.0:''${array[1]}
+ '';
+ scriptArgs = "%i";
+ };
+
+ systemd.targets.default-websockify = {
+ description = "Target to start all default websockify@ services";
+ unitConfig.X-StopOnReconfiguration = true;
+ wants = mapAttrsToList (name: value: "websockify@${name}:${toString value}.service") cfg.portMap;
+ wantedBy = [ "multi-user.target" ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wg-quick.nix b/nixpkgs/nixos/modules/services/networking/wg-quick.nix
new file mode 100644
index 00000000000..b770d47d269
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wg-quick.nix
@@ -0,0 +1,312 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.networking.wg-quick;
+
+ kernel = config.boot.kernelPackages;
+
+ # interface options
+
+ interfaceOpts = { ... }: {
+ options = {
+ address = mkOption {
+ example = [ "192.168.2.1/24" ];
+ default = [];
+ type = with types; listOf str;
+ description = "The IP addresses of the interface.";
+ };
+
+ dns = mkOption {
+ example = [ "192.168.2.2" ];
+ default = [];
+ type = with types; listOf str;
+ description = "The IP addresses of DNS servers to configure.";
+ };
+
+ privateKey = mkOption {
+ example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Base64 private key generated by wg genkey.
+
+ Warning: Consider using privateKeyFile instead if you do not
+ want to store the key in the world-readable Nix store.
+ '';
+ };
+
+ privateKeyFile = mkOption {
+ example = "/private/wireguard_key";
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Private key file as generated by wg genkey.
+ '';
+ };
+
+ listenPort = mkOption {
+ default = null;
+ type = with types; nullOr int;
+ example = 51820;
+ description = ''
+ 16-bit port for listening. Optional; if not specified,
+ automatically generated based on interface name.
+ '';
+ };
+
+ preUp = mkOption {
+ example = literalExample ''
+ ${pkgs.iproute}/bin/ip netns add foo
+ '';
+ default = "";
+ type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+ description = ''
+ Commands called at the start of the interface setup.
+ '';
+ };
+
+ preDown = mkOption {
+ example = literalExample ''
+ ${pkgs.iproute}/bin/ip netns del foo
+ '';
+ default = "";
+ type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+ description = ''
+ Command called before the interface is taken down.
+ '';
+ };
+
+ postUp = mkOption {
+ example = literalExample ''
+ ${pkgs.iproute}/bin/ip netns add foo
+ '';
+ default = "";
+ type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+ description = ''
+ Commands called after the interface setup.
+ '';
+ };
+
+ postDown = mkOption {
+ example = literalExample ''
+ ${pkgs.iproute}/bin/ip netns del foo
+ '';
+ default = "";
+ type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+ description = ''
+ Command called after the interface is taken down.
+ '';
+ };
+
+ table = mkOption {
+ example = "main";
+ default = null;
+ type = with types; nullOr str;
+ description = ''
+ The kernel routing table to add this interface's
+ associated routes to. Setting this is useful for e.g. policy routing
+ ("ip rule") or virtual routing and forwarding ("ip vrf"). Both numeric
+ table IDs and table names (/etc/rt_tables) can be used. Defaults to
+ "main".
+ '';
+ };
+
+ mtu = mkOption {
+ example = 1248;
+ default = null;
+ type = with types; nullOr int;
+ description = ''
+ If not specified, the MTU is automatically determined
+ from the endpoint addresses or the system default route, which is usually
+ a sane choice. However, to manually specify an MTU to override this
+ automatic discovery, this value may be specified explicitly.
+ '';
+ };
+
+ peers = mkOption {
+ default = [];
+ description = "Peers linked to the interface.";
+ type = with types; listOf (submodule peerOpts);
+ };
+ };
+ };
+
+ # peer options
+
+ peerOpts = {
+ options = {
+ publicKey = mkOption {
+ example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
+ type = types.str;
+ description = "The base64 public key the peer.";
+ };
+
+ presharedKey = mkOption {
+ default = null;
+ example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
+ type = with types; nullOr str;
+ description = ''
+ Base64 preshared key generated by wg genpsk. Optional,
+ and may be omitted. This option adds an additional layer of
+ symmetric-key cryptography to be mixed into the already existing
+ public-key cryptography, for post-quantum resistance.
+
+ Warning: Consider using presharedKeyFile instead if you do not
+ want to store the key in the world-readable Nix store.
+ '';
+ };
+
+ presharedKeyFile = mkOption {
+ default = null;
+ example = "/private/wireguard_psk";
+ type = with types; nullOr str;
+ description = ''
+ File pointing to preshared key as generated by wg pensk. Optional,
+ and may be omitted. This option adds an additional layer of
+ symmetric-key cryptography to be mixed into the already existing
+ public-key cryptography, for post-quantum resistance.
+ '';
+ };
+
+ allowedIPs = mkOption {
+ example = [ "10.192.122.3/32" "10.192.124.1/24" ];
+ type = with types; listOf str;
+ description = ''List of IP (v4 or v6) addresses with CIDR masks from
+ which this peer is allowed to send incoming traffic and to which
+ outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
+ be specified for matching all IPv4 addresses, and ::/0 may be specified
+ for matching all IPv6 addresses.'';
+ };
+
+ endpoint = mkOption {
+ default = null;
+ example = "demo.wireguard.io:12913";
+ type = with types; nullOr str;
+ description = ''Endpoint IP or hostname of the peer, followed by a colon,
+ and then a port number of the peer.'';
+ };
+
+ persistentKeepalive = mkOption {
+ default = null;
+ type = with types; nullOr int;
+ example = 25;
+ description = ''This is optional and is by default off, because most
+ users will not need it. It represents, in seconds, between 1 and 65535
+ inclusive, how often to send an authenticated empty packet to the peer,
+ for the purpose of keeping a stateful firewall or NAT mapping valid
+ persistently. For example, if the interface very rarely sends traffic,
+ but it might at anytime receive traffic from a peer, and it is behind
+ NAT, the interface might benefit from having a persistent keepalive
+ interval of 25 seconds; however, most users will not need this.'';
+ };
+ };
+ };
+
+ writeScriptFile = name: text: ((pkgs.writeShellScriptBin name text) + "/bin/${name}");
+
+ generateUnit = name: values:
+ assert assertMsg ((values.privateKey != null) != (values.privateKeyFile != null)) "Only one of privateKey or privateKeyFile may be set";
+ let
+ preUpFile = if values.preUp != "" then writeScriptFile "preUp.sh" values.preUp else null;
+ postUp =
+ optional (values.privateKeyFile != null) "wg set ${name} private-key <(cat ${values.privateKeyFile})" ++
+ (concatMap (peer: optional (peer.presharedKeyFile != null) "wg set ${name} peer ${peer.publicKey} preshared-key <(cat ${peer.presharedKeyFile})") values.peers) ++
+ optional (values.postUp != null) values.postUp;
+ postUpFile = if postUp != [] then writeScriptFile "postUp.sh" (concatMapStringsSep "\n" (line: line) postUp) else null;
+ preDownFile = if values.preDown != "" then writeScriptFile "preDown.sh" values.preDown else null;
+ postDownFile = if values.postDown != "" then writeScriptFile "postDown.sh" values.postDown else null;
+ configDir = pkgs.writeTextFile {
+ name = "config-${name}";
+ executable = false;
+ destination = "/${name}.conf";
+ text =
+ ''
+ [interface]
+ ${concatMapStringsSep "\n" (address:
+ "Address = ${address}"
+ ) values.address}
+ ${concatMapStringsSep "\n" (dns:
+ "DNS = ${dns}"
+ ) values.dns}
+ '' +
+ optionalString (values.table != null) "Table = ${values.table}\n" +
+ optionalString (values.mtu != null) "MTU = ${toString values.mtu}\n" +
+ optionalString (values.privateKey != null) "PrivateKey = ${values.privateKey}\n" +
+ optionalString (values.listenPort != null) "ListenPort = ${toString values.listenPort}\n" +
+ optionalString (preUpFile != null) "PreUp = ${preUpFile}\n" +
+ optionalString (postUpFile != null) "PostUp = ${postUpFile}\n" +
+ optionalString (preDownFile != null) "PreDown = ${preDownFile}\n" +
+ optionalString (postDownFile != null) "PostDown = ${postDownFile}\n" +
+ concatMapStringsSep "\n" (peer:
+ assert assertMsg (!((peer.presharedKeyFile != null) && (peer.presharedKey != null))) "Only one of presharedKey or presharedKeyFile may be set";
+ "[Peer]\n" +
+ "PublicKey = ${peer.publicKey}\n" +
+ optionalString (peer.presharedKey != null) "PresharedKey = ${peer.presharedKey}\n" +
+ optionalString (peer.endpoint != null) "Endpoint = ${peer.endpoint}\n" +
+ optionalString (peer.persistentKeepalive != null) "PersistentKeepalive = ${toString peer.persistentKeepalive}\n" +
+ optionalString (peer.allowedIPs != []) "AllowedIPs = ${concatStringsSep "," peer.allowedIPs}\n"
+ ) values.peers;
+ };
+ configPath = "${configDir}/${name}.conf";
+ in
+ nameValuePair "wg-quick-${name}"
+ {
+ description = "wg-quick WireGuard Tunnel - ${name}";
+ requires = [ "network-online.target" ];
+ after = [ "network.target" "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ environment.DEVICE = name;
+ path = [ pkgs.kmod pkgs.wireguard-tools ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+
+ script = ''
+ ${optionalString (!config.boot.isContainer) "modprobe wireguard"}
+ wg-quick up ${configPath}
+ '';
+
+ preStop = ''
+ wg-quick down ${configPath}
+ '';
+ };
+in {
+
+ ###### interface
+
+ options = {
+ networking.wg-quick = {
+ interfaces = mkOption {
+ description = "Wireguard interfaces.";
+ default = {};
+ example = {
+ wg0 = {
+ address = [ "192.168.20.4/24" ];
+ privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
+ peers = [
+ { allowedIPs = [ "192.168.20.1/32" ];
+ publicKey = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
+ endpoint = "demo.wireguard.io:12913"; }
+ ];
+ };
+ };
+ type = with types; attrsOf (submodule interfaceOpts);
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf (cfg.interfaces != {}) {
+ boot.extraModulePackages = [ kernel.wireguard ];
+ environment.systemPackages = [ pkgs.wireguard-tools ];
+ # This is forced to false for now because the default "--validmark" rpfilter we apply on reverse path filtering
+ # breaks the wg-quick routing because wireguard packets leave with a fwmark from wireguard.
+ networking.firewall.checkReversePath = false;
+ systemd.services = mapAttrs' generateUnit cfg.interfaces;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wicd.nix b/nixpkgs/nixos/modules/services/networking/wicd.nix
new file mode 100644
index 00000000000..03c6bd28aab
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wicd.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ networking.wicd.enable = mkOption {
+ default = false;
+ description = ''
+ Whether to start <command>wicd</command>. Wired and
+ wireless network configurations can then be managed by
+ wicd-client.
+ '';
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.networking.wicd.enable {
+
+ environment.systemPackages = [pkgs.wicd];
+
+ systemd.services.wicd = {
+ after = [ "network-pre.target" ];
+ before = [ "network.target" ];
+ wants = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ script = "${pkgs.wicd}/sbin/wicd -f";
+ };
+
+ services.dbus.enable = true;
+ services.dbus.packages = [pkgs.wicd];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wireguard.nix b/nixpkgs/nixos/modules/services/networking/wireguard.nix
new file mode 100644
index 00000000000..4176da2c8cb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wireguard.nix
@@ -0,0 +1,408 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.networking.wireguard;
+
+ kernel = config.boot.kernelPackages;
+
+ # interface options
+
+ interfaceOpts = { ... }: {
+
+ options = {
+
+ ips = mkOption {
+ example = [ "192.168.2.1/24" ];
+ default = [];
+ type = with types; listOf str;
+ description = "The IP addresses of the interface.";
+ };
+
+ privateKey = mkOption {
+ example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Base64 private key generated by <command>wg genkey</command>.
+
+ Warning: Consider using privateKeyFile instead if you do not
+ want to store the key in the world-readable Nix store.
+ '';
+ };
+
+ generatePrivateKeyFile = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Automatically generate a private key with
+ <command>wg genkey</command>, at the privateKeyFile location.
+ '';
+ };
+
+ privateKeyFile = mkOption {
+ example = "/private/wireguard_key";
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Private key file as generated by <command>wg genkey</command>.
+ '';
+ };
+
+ listenPort = mkOption {
+ default = null;
+ type = with types; nullOr int;
+ example = 51820;
+ description = ''
+ 16-bit port for listening. Optional; if not specified,
+ automatically generated based on interface name.
+ '';
+ };
+
+ preSetup = mkOption {
+ example = literalExample ''
+ ${pkgs.iproute}/bin/ip netns add foo
+ '';
+ default = "";
+ type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+ description = ''
+ Commands called at the start of the interface setup.
+ '';
+ };
+
+ postSetup = mkOption {
+ example = literalExample ''
+ printf "nameserver 10.200.100.1" | ${pkgs.openresolv}/bin/resolvconf -a wg0 -m 0
+ '';
+ default = "";
+ type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+ description = "Commands called at the end of the interface setup.";
+ };
+
+ postShutdown = mkOption {
+ example = literalExample "${pkgs.openresolv}/bin/resolvconf -d wg0";
+ default = "";
+ type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+ description = "Commands called after shutting down the interface.";
+ };
+
+ table = mkOption {
+ default = "main";
+ type = types.str;
+ description = ''The kernel routing table to add this interface's
+ associated routes to. Setting this is useful for e.g. policy routing
+ ("ip rule") or virtual routing and forwarding ("ip vrf"). Both numeric
+ table IDs and table names (/etc/rt_tables) can be used. Defaults to
+ "main".'';
+ };
+
+ peers = mkOption {
+ default = [];
+ description = "Peers linked to the interface.";
+ type = with types; listOf (submodule peerOpts);
+ };
+
+ allowedIPsAsRoutes = mkOption {
+ example = false;
+ default = true;
+ type = types.bool;
+ description = ''
+ Determines whether to add allowed IPs as routes or not.
+ '';
+ };
+ };
+
+ };
+
+ # peer options
+
+ peerOpts = {
+
+ options = {
+
+ publicKey = mkOption {
+ example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
+ type = types.str;
+ description = "The base64 public key the peer.";
+ };
+
+ presharedKey = mkOption {
+ default = null;
+ example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
+ type = with types; nullOr str;
+ description = ''
+ Base64 preshared key generated by <command>wg genpsk</command>.
+ Optional, and may be omitted. This option adds an additional layer of
+ symmetric-key cryptography to be mixed into the already existing
+ public-key cryptography, for post-quantum resistance.
+
+ Warning: Consider using presharedKeyFile instead if you do not
+ want to store the key in the world-readable Nix store.
+ '';
+ };
+
+ presharedKeyFile = mkOption {
+ default = null;
+ example = "/private/wireguard_psk";
+ type = with types; nullOr str;
+ description = ''
+ File pointing to preshared key as generated by <command>wg pensk</command>.
+ Optional, and may be omitted. This option adds an additional layer of
+ symmetric-key cryptography to be mixed into the already existing
+ public-key cryptography, for post-quantum resistance.
+ '';
+ };
+
+ allowedIPs = mkOption {
+ example = [ "10.192.122.3/32" "10.192.124.1/24" ];
+ type = with types; listOf str;
+ description = ''List of IP (v4 or v6) addresses with CIDR masks from
+ which this peer is allowed to send incoming traffic and to which
+ outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
+ be specified for matching all IPv4 addresses, and ::/0 may be specified
+ for matching all IPv6 addresses.'';
+ };
+
+ endpoint = mkOption {
+ default = null;
+ example = "demo.wireguard.io:12913";
+ type = with types; nullOr str;
+ description = ''Endpoint IP or hostname of the peer, followed by a colon,
+ and then a port number of the peer.'';
+ };
+
+ persistentKeepalive = mkOption {
+ default = null;
+ type = with types; nullOr int;
+ example = 25;
+ description = ''This is optional and is by default off, because most
+ users will not need it. It represents, in seconds, between 1 and 65535
+ inclusive, how often to send an authenticated empty packet to the peer,
+ for the purpose of keeping a stateful firewall or NAT mapping valid
+ persistently. For example, if the interface very rarely sends traffic,
+ but it might at anytime receive traffic from a peer, and it is behind
+ NAT, the interface might benefit from having a persistent keepalive
+ interval of 25 seconds; however, most users will not need this.'';
+ };
+
+ };
+
+ };
+
+
+ generatePathUnit = name: values:
+ assert (values.privateKey == null);
+ assert (values.privateKeyFile != null);
+ nameValuePair "wireguard-${name}"
+ {
+ description = "WireGuard Tunnel - ${name} - Private Key";
+ requiredBy = [ "wireguard-${name}.service" ];
+ before = [ "wireguard-${name}.service" ];
+ pathConfig.PathExists = values.privateKeyFile;
+ };
+
+ generateKeyServiceUnit = name: values:
+ assert values.generatePrivateKeyFile;
+ nameValuePair "wireguard-${name}-key"
+ {
+ description = "WireGuard Tunnel - ${name} - Key Generator";
+ wantedBy = [ "wireguard-${name}.service" ];
+ requiredBy = [ "wireguard-${name}.service" ];
+ before = [ "wireguard-${name}.service" ];
+ path = with pkgs; [ wireguard ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+
+ script = ''
+ mkdir --mode 0644 -p "${dirOf values.privateKeyFile}"
+ if [ ! -f "${values.privateKeyFile}" ]; then
+ touch "${values.privateKeyFile}"
+ chmod 0600 "${values.privateKeyFile}"
+ wg genkey > "${values.privateKeyFile}"
+ chmod 0400 "${values.privateKeyFile}"
+ fi
+ '';
+ };
+
+ generatePeerUnit = { interfaceName, interfaceCfg, peer }:
+ let
+ keyToUnitName = replaceChars
+ [ "/" "-" " " "+" "=" ]
+ [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ];
+ unitName = keyToUnitName peer.publicKey;
+ psk =
+ if peer.presharedKey != null
+ then pkgs.writeText "wg-psk" peer.presharedKey
+ else peer.presharedKeyFile;
+ in nameValuePair "wireguard-${interfaceName}-peer-${unitName}"
+ {
+ description = "WireGuard Peer - ${interfaceName} - ${peer.publicKey}";
+ requires = [ "wireguard-${interfaceName}.service" ];
+ after = [ "wireguard-${interfaceName}.service" ];
+ wantedBy = [ "multi-user.target" "wireguard-${interfaceName}.service" ];
+ environment.DEVICE = interfaceName;
+ environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity";
+ path = with pkgs; [ iproute wireguard-tools ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+
+ script = let
+ wg_setup = "wg set ${interfaceName} peer ${peer.publicKey}" +
+ optionalString (psk != null) " preshared-key ${psk}" +
+ optionalString (peer.endpoint != null) " endpoint ${peer.endpoint}" +
+ optionalString (peer.persistentKeepalive != null) " persistent-keepalive ${toString peer.persistentKeepalive}" +
+ optionalString (peer.allowedIPs != []) " allowed-ips ${concatStringsSep "," peer.allowedIPs}";
+ route_setup =
+ optionalString (interfaceCfg.allowedIPsAsRoutes != false)
+ (concatMapStringsSep "\n"
+ (allowedIP:
+ "ip route replace ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
+ ) peer.allowedIPs);
+ in ''
+ ${wg_setup}
+ ${route_setup}
+ '';
+
+ postStop = let
+ route_destroy = optionalString (interfaceCfg.allowedIPsAsRoutes != false)
+ (concatMapStringsSep "\n"
+ (allowedIP:
+ "ip route delete ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
+ ) peer.allowedIPs);
+ in ''
+ wg set ${interfaceName} peer ${peer.publicKey} remove
+ ${route_destroy}
+ '';
+ };
+
+ generateInterfaceUnit = name: values:
+ # exactly one way to specify the private key must be set
+ #assert (values.privateKey != null) != (values.privateKeyFile != null);
+ let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey;
+ in
+ nameValuePair "wireguard-${name}"
+ {
+ description = "WireGuard Tunnel - ${name}";
+ requires = [ "network-online.target" ];
+ after = [ "network.target" "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ environment.DEVICE = name;
+ path = with pkgs; [ kmod iproute wireguard-tools ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+
+ script = ''
+ ${optionalString (!config.boot.isContainer) "modprobe wireguard || true"}
+
+ ${values.preSetup}
+
+ ip link add dev ${name} type wireguard
+
+ ${concatMapStringsSep "\n" (ip:
+ "ip address add ${ip} dev ${name}"
+ ) values.ips}
+
+ wg set ${name} private-key ${privKey} ${
+ optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}"}
+
+ ip link set up dev ${name}
+
+ ${values.postSetup}
+ '';
+
+ postStop = ''
+ ip link del dev ${name}
+ ${values.postShutdown}
+ '';
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ networking.wireguard = {
+
+ enable = mkOption {
+ description = "Whether to enable WireGuard.";
+ type = types.bool;
+ # 2019-05-25: Backwards compatibility.
+ default = cfg.interfaces != {};
+ example = true;
+ };
+
+ interfaces = mkOption {
+ description = "WireGuard interfaces.";
+ default = {};
+ example = {
+ wg0 = {
+ ips = [ "192.168.20.4/24" ];
+ privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
+ peers = [
+ { allowedIPs = [ "192.168.20.1/32" ];
+ publicKey = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
+ endpoint = "demo.wireguard.io:12913"; }
+ ];
+ };
+ };
+ type = with types; attrsOf (submodule interfaceOpts);
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable (let
+ all_peers = flatten
+ (mapAttrsToList (interfaceName: interfaceCfg:
+ map (peer: { inherit interfaceName interfaceCfg peer;}) interfaceCfg.peers
+ ) cfg.interfaces);
+ in {
+
+ assertions = (attrValues (
+ mapAttrs (name: value: {
+ assertion = (value.privateKey != null) != (value.privateKeyFile != null);
+ message = "Either networking.wireguard.interfaces.${name}.privateKey or networking.wireguard.interfaces.${name}.privateKeyFile must be set.";
+ }) cfg.interfaces))
+ ++ (attrValues (
+ mapAttrs (name: value: {
+ assertion = value.generatePrivateKeyFile -> (value.privateKey == null);
+ message = "networking.wireguard.interfaces.${name}.generatePrivateKey must not be set if networking.wireguard.interfaces.${name}.privateKey is set.";
+ }) cfg.interfaces))
+ ++ map ({ interfaceName, peer, ... }: {
+ assertion = (peer.presharedKey == null) || (peer.presharedKeyFile == null);
+ message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used.";
+ }) all_peers;
+
+ boot.extraModulePackages = [ kernel.wireguard ];
+ environment.systemPackages = [ pkgs.wireguard-tools ];
+
+ systemd.services =
+ (mapAttrs' generateInterfaceUnit cfg.interfaces)
+ // (listToAttrs (map generatePeerUnit all_peers))
+ // (mapAttrs' generateKeyServiceUnit
+ (filterAttrs (name: value: value.generatePrivateKeyFile) cfg.interfaces));
+
+ systemd.paths = mapAttrs' generatePathUnit
+ (filterAttrs (name: value: value.privateKeyFile != null) cfg.interfaces);
+
+ });
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix b/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix
new file mode 100644
index 00000000000..63e59e7c8fa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix
@@ -0,0 +1,252 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+ cfg = config.networking.wireless;
+ configFile = if cfg.networks != {} then pkgs.writeText "wpa_supplicant.conf" ''
+ ${optionalString cfg.userControlled.enable ''
+ ctrl_interface=DIR=/run/wpa_supplicant GROUP=${cfg.userControlled.group}
+ update_config=1''}
+ ${cfg.extraConfig}
+ ${concatStringsSep "\n" (mapAttrsToList (ssid: config: with config; let
+ key = if psk != null
+ then ''"${psk}"''
+ else pskRaw;
+ baseAuth = if key != null
+ then ''psk=${key}''
+ else ''key_mgmt=NONE'';
+ in ''
+ network={
+ ssid="${ssid}"
+ ${optionalString (priority != null) ''priority=${toString priority}''}
+ ${optionalString hidden "scan_ssid=1"}
+ ${if (auth != null) then auth else baseAuth}
+ ${extraConfig}
+ }
+ '') cfg.networks)}
+ '' else "/etc/wpa_supplicant.conf";
+in {
+ options = {
+ networking.wireless = {
+ enable = mkEnableOption "wpa_supplicant";
+
+ interfaces = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "wlan0" "wlan1" ];
+ description = ''
+ The interfaces <command>wpa_supplicant</command> will use. If empty, it will
+ automatically use all wireless interfaces.
+ '';
+ };
+
+ driver = mkOption {
+ type = types.str;
+ default = "nl80211,wext";
+ description = "Force a specific wpa_supplicant driver.";
+ };
+
+ networks = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ psk = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The network's pre-shared key in plaintext defaulting
+ to being a network without any authentication.
+
+ Be aware that these will be written to the nix store
+ in plaintext!
+
+ Mutually exclusive with <varname>pskRaw</varname>.
+ '';
+ };
+
+ pskRaw = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The network's pre-shared key in hex defaulting
+ to being a network without any authentication.
+
+ Mutually exclusive with <varname>psk</varname>.
+ '';
+ };
+
+ auth = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = ''
+ key_mgmt=WPA-EAP
+ eap=PEAP
+ identity="user@example.com"
+ password="secret"
+ '';
+ description = ''
+ Use this option to configure advanced authentication methods like EAP.
+ See
+ <citerefentry>
+ <refentrytitle>wpa_supplicant.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ for example configurations.
+
+ Mutually exclusive with <varname>psk</varname> and <varname>pskRaw</varname>.
+ '';
+ };
+
+ hidden = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Set this to <literal>true</literal> if the SSID of the network is hidden.
+ '';
+ };
+
+ priority = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ By default, all networks will get same priority group (0). If some of the
+ networks are more desirable, this field can be used to change the order in
+ which wpa_supplicant goes through the networks when selecting a BSS. The
+ priority groups will be iterated in decreasing priority (i.e., the larger the
+ priority value, the sooner the network is matched against the scan results).
+ Within each priority group, networks will be selected based on security
+ policy, signal strength, etc.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ bssid_blacklist=02:11:22:33:44:55 02:22:aa:44:55:66
+ '';
+ description = ''
+ Extra configuration lines appended to the network block.
+ See
+ <citerefentry>
+ <refentrytitle>wpa_supplicant.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ for available options.
+ '';
+ };
+
+ };
+ });
+ description = ''
+ The network definitions to automatically connect to when
+ <command>wpa_supplicant</command> is running. If this
+ parameter is left empty wpa_supplicant will use
+ /etc/wpa_supplicant.conf as the configuration file.
+ '';
+ default = {};
+ example = literalExample ''
+ { echelon = {
+ psk = "abcdefgh";
+ };
+ "free.wifi" = {};
+ }
+ '';
+ };
+
+ userControlled = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli.
+ This is useful for laptop users that switch networks a lot and don't want
+ to depend on a large package such as NetworkManager just to pick nearby
+ access points.
+
+ When using a declarative network specification you cannot persist any
+ settings via wpa_gui or wpa_cli.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "wheel";
+ example = "network";
+ description = "Members of this group can control wpa_supplicant.";
+ };
+ };
+ extraConfig = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ p2p_disabled=1
+ '';
+ description = ''
+ Extra lines appended to the configuration file.
+ See
+ <citerefentry>
+ <refentrytitle>wpa_supplicant.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ for available options.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = flip mapAttrsToList cfg.networks (name: cfg: {
+ assertion = with cfg; count (x: x != null) [ psk pskRaw auth ] <= 1;
+ message = ''options networking.wireless."${name}".{psk,pskRaw,auth} are mutually exclusive'';
+ });
+
+ environment.systemPackages = [ pkgs.wpa_supplicant ];
+
+ services.dbus.packages = [ pkgs.wpa_supplicant ];
+ services.udev.packages = [ pkgs.crda ];
+
+ # FIXME: start a separate wpa_supplicant instance per interface.
+ systemd.services.wpa_supplicant = let
+ ifaces = cfg.interfaces;
+ deviceUnit = interface: [ "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device" ];
+ in {
+ description = "WPA Supplicant";
+
+ after = lib.concatMap deviceUnit ifaces;
+ before = [ "network.target" ];
+ wants = [ "network.target" ];
+ requires = lib.concatMap deviceUnit ifaces;
+ wantedBy = [ "multi-user.target" ];
+ stopIfChanged = false;
+
+ path = [ pkgs.wpa_supplicant ];
+
+ script = ''
+ ${if ifaces == [] then ''
+ for i in $(cd /sys/class/net && echo *); do
+ DEVTYPE=
+ source /sys/class/net/$i/uevent
+ if [ "$DEVTYPE" = "wlan" -o -e /sys/class/net/$i/wireless ]; then
+ ifaces="$ifaces''${ifaces:+ -N} -i$i"
+ fi
+ done
+ '' else ''
+ ifaces="${concatStringsSep " -N " (map (i: "-i${i}") ifaces)}"
+ ''}
+ exec wpa_supplicant -s -u -D${cfg.driver} -c ${configFile} $ifaces
+ '';
+ };
+
+ powerManagement.resumeCommands = ''
+ ${config.systemd.package}/bin/systemctl try-restart wpa_supplicant
+ '';
+
+ # Restart wpa_supplicant when a wlan device appears or disappears.
+ services.udev.extraRules = ''
+ ACTION=="add|remove", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", RUN+="${config.systemd.package}/bin/systemctl try-restart wpa_supplicant.service"
+ '';
+ };
+
+ meta.maintainers = with lib.maintainers; [ globin ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/xinetd.nix b/nixpkgs/nixos/modules/services/networking/xinetd.nix
new file mode 100644
index 00000000000..8dc6f845ed8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/xinetd.nix
@@ -0,0 +1,152 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xinetd;
+
+ configFile = pkgs.writeText "xinetd.conf"
+ ''
+ defaults
+ {
+ log_type = SYSLOG daemon info
+ log_on_failure = HOST
+ log_on_success = PID HOST DURATION EXIT
+ ${cfg.extraDefaults}
+ }
+
+ ${concatMapStrings makeService cfg.services}
+ '';
+
+ makeService = srv:
+ ''
+ service ${srv.name}
+ {
+ protocol = ${srv.protocol}
+ ${optionalString srv.unlisted "type = UNLISTED"}
+ ${optionalString (srv.flags != "") "flags = ${srv.flags}"}
+ socket_type = ${if srv.protocol == "udp" then "dgram" else "stream"}
+ ${if srv.port != 0 then "port = ${toString srv.port}" else ""}
+ wait = ${if srv.protocol == "udp" then "yes" else "no"}
+ user = ${srv.user}
+ server = ${srv.server}
+ ${optionalString (srv.serverArgs != "") "server_args = ${srv.serverArgs}"}
+ ${srv.extraConfig}
+ }
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.xinetd.enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable the xinetd super-server daemon.
+ '';
+ };
+
+ services.xinetd.extraDefaults = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Additional configuration lines added to the default section of xinetd's configuration.
+ '';
+ };
+
+ services.xinetd.services = mkOption {
+ default = [];
+ description = ''
+ A list of services provided by xinetd.
+ '';
+
+ type = with types; listOf (submodule ({
+
+ options = {
+
+ name = mkOption {
+ type = types.str;
+ example = "login";
+ description = "Name of the service.";
+ };
+
+ protocol = mkOption {
+ type = types.str;
+ default = "tcp";
+ description =
+ "Protocol of the service. Usually <literal>tcp</literal> or <literal>udp</literal>.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 0;
+ example = 123;
+ description = "Port number of the service.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "nobody";
+ description = "User account for the service";
+ };
+
+ server = mkOption {
+ type = types.str;
+ example = "/foo/bin/ftpd";
+ description = "Path of the program that implements the service.";
+ };
+
+ serverArgs = mkOption {
+ type = types.separatedString " ";
+ default = "";
+ description = "Command-line arguments for the server program.";
+ };
+
+ flags = mkOption {
+ type = types.str;
+ default = "";
+ description = "";
+ };
+
+ unlisted = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether this server is listed in
+ <filename>/etc/services</filename>. If so, the port
+ number can be omitted.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra configuration-lines added to the section of the service.";
+ };
+
+ };
+
+ }));
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.xinetd = {
+ description = "xinetd server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.xinetd ];
+ script = "exec xinetd -syslog daemon -dontfork -stayalive -f ${configFile}";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/xl2tpd.nix b/nixpkgs/nixos/modules/services/networking/xl2tpd.nix
new file mode 100644
index 00000000000..7dbe51422d9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/xl2tpd.nix
@@ -0,0 +1,143 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+ options = {
+ services.xl2tpd = {
+ enable = mkEnableOption "xl2tpd, the Layer 2 Tunnelling Protocol Daemon";
+
+ serverIp = mkOption {
+ type = types.str;
+ description = "The server-side IP address.";
+ default = "10.125.125.1";
+ };
+
+ clientIpRange = mkOption {
+ type = types.str;
+ description = "The range from which client IPs are drawn.";
+ default = "10.125.125.2-11";
+ };
+
+ extraXl2tpOptions = mkOption {
+ type = types.lines;
+ description = "Adds extra lines to the xl2tpd configuration file.";
+ default = "";
+ };
+
+ extraPppdOptions = mkOption {
+ type = types.lines;
+ description = "Adds extra lines to the pppd options file.";
+ default = "";
+ example = ''
+ ms-dns 8.8.8.8
+ ms-dns 8.8.4.4
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.xl2tpd.enable {
+ systemd.services.xl2tpd = let
+ cfg = config.services.xl2tpd;
+
+ # Config files from https://help.ubuntu.com/community/L2TPServer
+ xl2tpd-conf = pkgs.writeText "xl2tpd.conf" ''
+ [global]
+ ipsec saref = no
+
+ [lns default]
+ local ip = ${cfg.serverIp}
+ ip range = ${cfg.clientIpRange}
+ pppoptfile = ${pppd-options}
+ length bit = yes
+
+ ; Extra
+ ${cfg.extraXl2tpOptions}
+ '';
+
+ pppd-options = pkgs.writeText "ppp-options-xl2tpd.conf" ''
+ refuse-pap
+ refuse-chap
+ refuse-mschap
+ require-mschap-v2
+ # require-mppe-128
+ asyncmap 0
+ auth
+ crtscts
+ idle 1800
+ mtu 1200
+ mru 1200
+ lock
+ hide-password
+ local
+ # debug
+ name xl2tpd
+ # proxyarp
+ lcp-echo-interval 30
+ lcp-echo-failure 4
+
+ # Extra:
+ ${cfg.extraPppdOptions}
+ '';
+
+ xl2tpd-ppp-wrapped = pkgs.stdenv.mkDerivation {
+ name = "xl2tpd-ppp-wrapped";
+ phases = [ "installPhase" ];
+ buildInputs = with pkgs; [ makeWrapper ];
+ installPhase = ''
+ mkdir -p $out/bin
+
+ makeWrapper ${pkgs.ppp}/sbin/pppd $out/bin/pppd \
+ --set LD_PRELOAD "${pkgs.libredirect}/lib/libredirect.so" \
+ --set NIX_REDIRECTS "/etc/ppp=/etc/xl2tpd/ppp"
+
+ makeWrapper ${pkgs.xl2tpd}/bin/xl2tpd $out/bin/xl2tpd \
+ --set LD_PRELOAD "${pkgs.libredirect}/lib/libredirect.so" \
+ --set NIX_REDIRECTS "${pkgs.ppp}/sbin/pppd=$out/bin/pppd"
+ '';
+ };
+ in {
+ description = "xl2tpd server";
+
+ requires = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ mkdir -p -m 700 /etc/xl2tpd
+
+ pushd /etc/xl2tpd > /dev/null
+
+ mkdir -p -m 700 ppp
+
+ [ -f ppp/chap-secrets ] || cat > ppp/chap-secrets << EOF
+ # Secrets for authentication using CHAP
+ # client server secret IP addresses
+ #username xl2tpd password *
+ EOF
+
+ chown root.root ppp/chap-secrets
+ chmod 600 ppp/chap-secrets
+
+ # The documentation says this file should be present but doesn't explain why and things work even if not there:
+ [ -f l2tp-secrets ] || (echo -n "* * "; ${pkgs.apg}/bin/apg -n 1 -m 32 -x 32 -a 1 -M LCN) > l2tp-secrets
+ chown root.root l2tp-secrets
+ chmod 600 l2tp-secrets
+
+ popd > /dev/null
+
+ mkdir -p /run/xl2tpd
+ chown root.root /run/xl2tpd
+ chmod 700 /run/xl2tpd
+ '';
+
+ serviceConfig = {
+ ExecStart = "${xl2tpd-ppp-wrapped}/bin/xl2tpd -D -c ${xl2tpd-conf} -s /etc/xl2tpd/l2tp-secrets -p /run/xl2tpd/pid -C /run/xl2tpd/control";
+ KillMode = "process";
+ Restart = "on-success";
+ Type = "simple";
+ PIDFile = "/run/xl2tpd/pid";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/xrdp.nix b/nixpkgs/nixos/modules/services/networking/xrdp.nix
new file mode 100644
index 00000000000..b7dd1c5d99d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/xrdp.nix
@@ -0,0 +1,171 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xrdp;
+ confDir = pkgs.runCommand "xrdp.conf" { preferLocalBuild = true; } ''
+ mkdir $out
+
+ cp ${cfg.package}/etc/xrdp/{km-*,xrdp,sesman,xrdp_keyboard}.ini $out
+
+ cat > $out/startwm.sh <<EOF
+ #!/bin/sh
+ . /etc/profile
+ ${cfg.defaultWindowManager}
+ EOF
+ chmod +x $out/startwm.sh
+
+ substituteInPlace $out/xrdp.ini \
+ --replace "#rsakeys_ini=" "rsakeys_ini=/run/xrdp/rsakeys.ini" \
+ --replace "certificate=" "certificate=${cfg.sslCert}" \
+ --replace "key_file=" "key_file=${cfg.sslKey}" \
+ --replace LogFile=xrdp.log LogFile=/dev/null \
+ --replace EnableSyslog=true EnableSyslog=false
+
+ substituteInPlace $out/sesman.ini \
+ --replace LogFile=xrdp-sesman.log LogFile=/dev/null \
+ --replace EnableSyslog=1 EnableSyslog=0
+
+ # Ensure that clipboard works for non-ASCII characters
+ sed -i -e '/.*SessionVariables.*/ a\
+ LANG=${config.i18n.defaultLocale}\
+ LOCALE_ARCHIVE=${config.i18n.glibcLocales}/lib/locale/locale-archive
+ ' $out/sesman.ini
+ '';
+in
+{
+
+ ###### interface
+
+ options = {
+
+ services.xrdp = {
+
+ enable = mkEnableOption "xrdp, the Remote Desktop Protocol server";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.xrdp;
+ defaultText = "pkgs.xrdp";
+ description = ''
+ The package to use for the xrdp daemon's binary.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 3389;
+ description = ''
+ Specifies on which port the xrdp daemon listens.
+ '';
+ };
+
+ sslKey = mkOption {
+ type = types.str;
+ default = "/etc/xrdp/key.pem";
+ example = "/path/to/your/key.pem";
+ description = ''
+ ssl private key path
+ A self-signed certificate will be generated if file not exists.
+ '';
+ };
+
+ sslCert = mkOption {
+ type = types.str;
+ default = "/etc/xrdp/cert.pem";
+ example = "/path/to/your/cert.pem";
+ description = ''
+ ssl certificate path
+ A self-signed certificate will be generated if file not exists.
+ '';
+ };
+
+ defaultWindowManager = mkOption {
+ type = types.str;
+ default = "xterm";
+ example = "xfce4-session";
+ description = ''
+ The script to run when user log in, usually a window manager, e.g. "icewm", "xfce4-session"
+ This is per-user overridable, if file ~/startwm.sh exists it will be used instead.
+ '';
+ };
+
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ # xrdp can run X11 program even if "services.xserver.enable = false"
+ xdg = {
+ autostart.enable = true;
+ menus.enable = true;
+ mime.enable = true;
+ icons.enable = true;
+ };
+
+ fonts.enableDefaultFonts = mkDefault true;
+
+ systemd = {
+ services.xrdp = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ description = "xrdp daemon";
+ requires = [ "xrdp-sesman.service" ];
+ preStart = ''
+ # prepare directory for unix sockets (the sockets will be owned by loggedinuser:xrdp)
+ mkdir -p /tmp/.xrdp || true
+ chown xrdp:xrdp /tmp/.xrdp
+ chmod 3777 /tmp/.xrdp
+
+ # generate a self-signed certificate
+ if [ ! -s ${cfg.sslCert} -o ! -s ${cfg.sslKey} ]; then
+ mkdir -p $(dirname ${cfg.sslCert}) || true
+ mkdir -p $(dirname ${cfg.sslKey}) || true
+ ${pkgs.openssl.bin}/bin/openssl req -x509 -newkey rsa:2048 -sha256 -nodes -days 365 \
+ -subj /C=US/ST=CA/L=Sunnyvale/O=xrdp/CN=www.xrdp.org \
+ -config ${cfg.package}/share/xrdp/openssl.conf \
+ -keyout ${cfg.sslKey} -out ${cfg.sslCert}
+ chown root:xrdp ${cfg.sslKey} ${cfg.sslCert}
+ chmod 440 ${cfg.sslKey} ${cfg.sslCert}
+ fi
+ if [ ! -s /run/xrdp/rsakeys.ini ]; then
+ mkdir -p /run/xrdp
+ ${cfg.package}/bin/xrdp-keygen xrdp /run/xrdp/rsakeys.ini
+ fi
+ '';
+ serviceConfig = {
+ User = "xrdp";
+ Group = "xrdp";
+ PermissionsStartOnly = true;
+ ExecStart = "${cfg.package}/bin/xrdp --nodaemon --port ${toString cfg.port} --config ${confDir}/xrdp.ini";
+ };
+ };
+
+ services.xrdp-sesman = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ description = "xrdp session manager";
+ restartIfChanged = false; # do not restart on "nixos-rebuild switch". like "display-manager", it can have many interactive programs as children
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/xrdp-sesman --nodaemon --config ${confDir}/sesman.ini";
+ ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+ };
+ };
+
+ };
+
+ users.users.xrdp = {
+ description = "xrdp daemon user";
+ isSystemUser = true;
+ group = "xrdp";
+ };
+ users.groups.xrdp = {};
+
+ security.pam.services.xrdp-sesman = { allowNullPassword = true; startSession = true; };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/zerobin.nix b/nixpkgs/nixos/modules/services/networking/zerobin.nix
new file mode 100644
index 00000000000..78de246a816
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/zerobin.nix
@@ -0,0 +1,102 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+ cfg = config.services.zerobin;
+
+ zerobin_config = pkgs.writeText "zerobin-config.py" ''
+ PASTE_FILES_ROOT = "${cfg.dataDir}"
+ ${cfg.extraConfig}
+ '';
+
+in
+ {
+ options = {
+ services.zerobin = {
+ enable = mkEnableOption "0bin";
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/zerobin";
+ description = ''
+ Path to the 0bin data directory
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "zerobin";
+ description = ''
+ The user 0bin should run as
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "zerobin";
+ description = ''
+ The group 0bin should run as
+ '';
+ };
+
+ listenPort = mkOption {
+ type = types.int;
+ default = 8000;
+ example = 1357;
+ description = ''
+ The port zerobin should listen on
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "localhost";
+ example = "127.0.0.1";
+ description = ''
+ The address zerobin should listen to
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ MENU = (
+ ('Home', '/'),
+ )
+ COMPRESSED_STATIC_FILE = True
+ '';
+ description = ''
+ Extra configuration to be appended to the 0bin config file
+ (see https://0bin.readthedocs.org/en/latest/en/options.html)
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.enable) {
+ users.users.${cfg.user} =
+ if cfg.user == "zerobin" then {
+ isSystemUser = true;
+ group = cfg.group;
+ home = cfg.dataDir;
+ createHome = true;
+ }
+ else {};
+ users.groups.${cfg.group} = {};
+
+ systemd.services.zerobin = {
+ enable = true;
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${pkgs.pythonPackages.zerobin}/bin/zerobin ${cfg.listenAddress} ${toString cfg.listenPort} false ${cfg.user} ${cfg.group} ${zerobin_config}";
+ serviceConfig.PrivateTmp="yes";
+ serviceConfig.User = cfg.user;
+ serviceConfig.Group = cfg.group;
+ preStart = ''
+ mkdir -p ${cfg.dataDir}
+ chown ${cfg.user} ${cfg.dataDir}
+ '';
+ };
+ };
+ }
+
diff --git a/nixpkgs/nixos/modules/services/networking/zeronet.nix b/nixpkgs/nixos/modules/services/networking/zeronet.nix
new file mode 100644
index 00000000000..f4988a90268
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/zeronet.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.zeronet;
+
+ zConfFile = pkgs.writeTextFile {
+ name = "zeronet.conf";
+
+ text = ''
+ [global]
+ data_dir = ${cfg.dataDir}
+ log_dir = ${cfg.logDir}
+ '' + lib.optionalString (cfg.port != null) ''
+ ui_port = ${toString cfg.port}
+ '' + lib.optionalString (cfg.fileserverPort != null) ''
+ fileserver_port = ${toString cfg.fileserverPort}
+ '' + lib.optionalString (cfg.torAlways) ''
+ tor = always
+ '' + cfg.extraConfig;
+ };
+in with lib; {
+ options.services.zeronet = {
+ enable = mkEnableOption "zeronet";
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/zeronet";
+ example = "/home/okina/zeronet";
+ description = "Path to the zeronet data directory.";
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ default = "/var/log/zeronet";
+ example = "/home/okina/zeronet/log";
+ description = "Path to the zeronet log directory.";
+ };
+
+ port = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 43110;
+ description = "Optional zeronet web UI port.";
+ };
+
+ fileserverPort = mkOption {
+ # Not optional: when absent zeronet tries to write one to the
+ # read-only config file and crashes
+ type = types.int;
+ default = 12261;
+ example = 12261;
+ description = "Zeronet fileserver port.";
+ };
+
+ tor = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Use TOR for zeronet traffic where possible.";
+ };
+
+ torAlways = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Use TOR for all zeronet traffic.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+
+ description = ''
+ Extra configuration. Contents will be added verbatim to the
+ configuration file at the end.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.tor = mkIf cfg.tor {
+ enable = true;
+ controlPort = 9051;
+ extraConfig = ''
+ CacheDirectoryGroupReadable 1
+ CookieAuthentication 1
+ CookieAuthFileGroupReadable 1
+ '';
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 750 zeronet zeronet - -"
+ "d '${cfg.logDir}' 750 zeronet zeronet - -"
+ ];
+
+ systemd.services.zeronet = {
+ description = "zeronet";
+ after = [ "network.target" (optionalString cfg.tor "tor.service") ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ PrivateTmp = "yes";
+ User = "zeronet";
+ Group = "zeronet";
+ ExecStart = "${pkgs.zeronet}/bin/zeronet --config_file ${zConfFile}";
+ };
+ };
+
+ users = {
+ groups.zeronet.gid = config.ids.gids.zeronet;
+
+ users.zeronet = {
+ description = "zeronet service user";
+ home = cfg.dataDir;
+ createHome = true;
+ group = "zeronet";
+ extraGroups = mkIf cfg.tor [ "tor" ];
+ uid = config.ids.uids.zeronet;
+ };
+ };
+ };
+
+ meta.maintainers = with maintainers; [ chiiruno ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/zerotierone.nix b/nixpkgs/nixos/modules/services/networking/zerotierone.nix
new file mode 100644
index 00000000000..764af3846fe
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/zerotierone.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.zerotierone;
+in
+{
+ options.services.zerotierone.enable = mkEnableOption "ZeroTierOne";
+
+ options.services.zerotierone.joinNetworks = mkOption {
+ default = [];
+ example = [ "a8a2c3c10c1a68de" ];
+ type = types.listOf types.str;
+ description = ''
+ List of ZeroTier Network IDs to join on startup
+ '';
+ };
+
+ options.services.zerotierone.port = mkOption {
+ default = 9993;
+ example = 9993;
+ type = types.int;
+ description = ''
+ Network port used by ZeroTier.
+ '';
+ };
+
+ options.services.zerotierone.package = mkOption {
+ default = pkgs.zerotierone;
+ defaultText = "pkgs.zerotierone";
+ type = types.package;
+ description = ''
+ ZeroTier One package to use.
+ '';
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.zerotierone = {
+ description = "ZeroTierOne";
+ path = [ cfg.package ];
+ bindsTo = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -p /var/lib/zerotier-one/networks.d
+ chmod 700 /var/lib/zerotier-one
+ chown -R root:root /var/lib/zerotier-one
+ '' + (concatMapStrings (netId: ''
+ touch "/var/lib/zerotier-one/networks.d/${netId}.conf"
+ '') cfg.joinNetworks);
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/zerotier-one -p${toString cfg.port}";
+ Restart = "always";
+ KillMode = "process";
+ };
+ };
+
+ # ZeroTier does not issue DHCP leases, but some strangers might...
+ networking.dhcpcd.denyInterfaces = [ "zt*" ];
+
+ # ZeroTier receives UDP transmissions
+ networking.firewall.allowedUDPPorts = [ cfg.port ];
+
+ environment.systemPackages = [ cfg.package ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/znc/default.nix b/nixpkgs/nixos/modules/services/networking/znc/default.nix
new file mode 100644
index 00000000000..05f97bfa539
--- /dev/null
+++ b/nixpkgs/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: optionals (value.${subname} != null) ([
+ "<${name} ${subname}>"
+ ] ++ map (line: "\t${line}") (toLines value.${subname}) ++ [
+ "</${name}>"
+ ])) (filter (v: v != null) (attrNames value));
+
+ }.${builtins.typeOf value};
+
+ # One level "above" encode, acts upon a set and uses encode on each name,value pair
+ toLines = set: concatMap (name: encode name set.${name}) (sortedAttrs set);
+
+ in
+ concatStringsSep "\n" (toLines cfg.config);
+
+ semanticTypes = with types; rec {
+ zncAtom = nullOr (oneOf [ int bool str ]);
+ zncAttr = attrsOf (nullOr zncConf);
+ zncAll = oneOf [ zncAtom (listOf zncAtom) zncAttr ];
+ zncConf = attrsOf (zncAll // {
+ # Since this is a recursive type and the description by default contains
+ # the description of its subtypes, infinite recursion would occur without
+ # explicitly breaking this cycle
+ description = "znc values (null, atoms (str, int, bool), list of atoms, or attrsets of znc values)";
+ });
+ };
+
+in
+
+{
+
+ imports = [ ./options.nix ];
+
+ options = {
+ services.znc = {
+ enable = mkEnableOption "ZNC";
+
+ user = mkOption {
+ default = "znc";
+ example = "john";
+ type = types.str;
+ description = ''
+ The name of an existing user account to use to own the ZNC server
+ process. If not specified, a default user will be created.
+ '';
+ };
+
+ group = mkOption {
+ default = defaultUser;
+ example = "users";
+ type = types.str;
+ description = ''
+ Group to own the ZNC process.
+ '';
+ };
+
+ dataDir = mkOption {
+ default = "/var/lib/znc/";
+ example = "/home/john/.znc/";
+ type = types.path;
+ description = ''
+ The state directory for ZNC. The config and the modules will be linked
+ to from this directory as well.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to open ports in the firewall for ZNC. Does work with
+ ports for listeners specified in
+ <option>services.znc.config.Listener</option>.
+ '';
+ };
+
+ config = mkOption {
+ type = semanticTypes.zncConf;
+ default = {};
+ example = literalExample ''
+ {
+ LoadModule = [ "webadmin" "adminlog" ];
+ User.paul = {
+ Admin = true;
+ Nick = "paul";
+ AltNick = "paul1";
+ LoadModule = [ "chansaver" "controlpanel" ];
+ Network.freenode = {
+ Server = "chat.freenode.net +6697";
+ LoadModule = [ "simple_away" ];
+ Chan = {
+ "#nixos" = { Detached = false; };
+ "##linux" = { Disabled = true; };
+ };
+ };
+ Pass.password = {
+ Method = "sha256";
+ Hash = "e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93";
+ Salt = "l5Xryew4g*!oa(ECfX2o";
+ };
+ };
+ }
+ '';
+ description = ''
+ Configuration for ZNC, see
+ <link xlink:href="https://wiki.znc.in/Configuration"/> for details. The
+ Nix value declared here will be translated directly to the xml-like
+ format ZNC expects. This is much more flexible than the legacy options
+ under <option>services.znc.confOptions.*</option>, but also can't do
+ any type checking.
+ </para>
+ <para>
+ You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.services.znc.config</command>
+ to view the current value. By default it contains a listener for port
+ 5000 with SSL enabled.
+ </para>
+ <para>
+ Nix attributes called <literal>extraConfig</literal> will be inserted
+ verbatim into the resulting config file.
+ </para>
+ <para>
+ If <option>services.znc.useLegacyConfig</option> is turned on, the
+ option values in <option>services.znc.confOptions.*</option> will be
+ gracefully be applied to this option.
+ </para>
+ <para>
+ If you intend to update the configuration through this option, be sure
+ to enable <option>services.znc.mutable</option>, otherwise none of the
+ changes here will be applied after the initial deploy.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ example = "~/.znc/configs/znc.conf";
+ description = ''
+ Configuration file for ZNC. It is recommended to use the
+ <option>config</option> option instead.
+ </para>
+ <para>
+ Setting this option will override any auto-generated config file
+ through the <option>confOptions</option> or <option>config</option>
+ options.
+ '';
+ };
+
+ modulePackages = mkOption {
+ type = types.listOf types.package;
+ default = [ ];
+ example = literalExample "[ pkgs.zncModules.fish pkgs.zncModules.push ]";
+ description = ''
+ A list of global znc module packages to add to znc.
+ '';
+ };
+
+ mutable = mkOption {
+ default = true; # TODO: Default to true when config is set, make sure to not delete the old config if present
+ type = types.bool;
+ description = ''
+ Indicates whether to allow the contents of the
+ <literal>dataDir</literal> directory to be changed by the user at
+ run-time.
+ </para>
+ <para>
+ If enabled, modifications to the ZNC configuration after its initial
+ creation are not overwritten by a NixOS rebuild. If disabled, the
+ ZNC configuration is rebuilt on every NixOS rebuild.
+ </para>
+ <para>
+ If the user wants to manage the ZNC service using the web admin
+ interface, this option should be enabled.
+ '';
+ };
+
+ extraFlags = mkOption {
+ default = [ ];
+ example = [ "--debug" ];
+ type = types.listOf types.str;
+ description = ''
+ Extra arguments to use for executing znc.
+ '';
+ };
+ };
+ };
+
+
+ ###### Implementation
+
+ config = mkIf cfg.enable {
+
+ services.znc = {
+ configFile = mkDefault (pkgs.writeText "znc-generated.conf" semanticString);
+ config = {
+ Version = (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/nixpkgs/nixos/modules/services/networking/znc/options.nix b/nixpkgs/nixos/modules/services/networking/znc/options.nix
new file mode 100644
index 00000000000..048dbd73863
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/znc/options.nix
@@ -0,0 +1,270 @@
+{ lib, config, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.znc;
+
+ networkOpts = {
+ options = {
+
+ server = mkOption {
+ type = types.str;
+ example = "chat.freenode.net";
+ description = ''
+ IRC server address.
+ '';
+ };
+
+ port = mkOption {
+ type = types.ints.u16;
+ default = 6697;
+ description = ''
+ IRC server port.
+ '';
+ };
+
+ password = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ IRC server password, such as for a Slack gateway.
+ '';
+ };
+
+ useSSL = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to use SSL to connect to the IRC server.
+ '';
+ };
+
+ modules = mkOption {
+ type = types.listOf types.str;
+ default = [ "simple_away" ];
+ example = literalExample "[ simple_away sasl ]";
+ description = ''
+ ZNC network modules to load.
+ '';
+ };
+
+ channels = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "nixos" ];
+ description = ''
+ IRC channels to join.
+ '';
+ };
+
+ hasBitlbeeControlChannel = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to add the special Bitlbee operations channel.
+ '';
+ };
+
+ extraConf = mkOption {
+ default = "";
+ type = types.lines;
+ example = ''
+ Encoding = ^UTF-8
+ FloodBurst = 4
+ FloodRate = 1.00
+ IRCConnectEnabled = true
+ Ident = johntron
+ JoinDelay = 0
+ Nick = johntron
+ '';
+ description = ''
+ Extra config for the network. Consider using
+ <option>services.znc.config</option> instead.
+ '';
+ };
+ };
+ };
+
+in
+
+{
+
+ options = {
+ services.znc = {
+
+ useLegacyConfig = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to propagate the legacy options under
+ <option>services.znc.confOptions.*</option> to the znc config. If this
+ is turned on, the znc config will contain a user with the default name
+ "znc", global modules "webadmin" and "adminlog" will be enabled by
+ default, and more, all controlled through the
+ <option>services.znc.confOptions.*</option> options.
+ You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.services.znc.config</command>
+ to view the current value of the config.
+ </para>
+ <para>
+ In any case, if you need more flexibility,
+ <option>services.znc.config</option> can be used to override/add to
+ all of the legacy options.
+ '';
+ };
+
+ confOptions = {
+ modules = mkOption {
+ type = types.listOf types.str;
+ default = [ "webadmin" "adminlog" ];
+ example = [ "partyline" "webadmin" "adminlog" "log" ];
+ description = ''
+ A list of modules to include in the `znc.conf` file.
+ '';
+ };
+
+ userModules = mkOption {
+ type = types.listOf types.str;
+ default = [ "chansaver" "controlpanel" ];
+ example = [ "chansaver" "controlpanel" "fish" "push" ];
+ description = ''
+ A list of user modules to include in the `znc.conf` file.
+ '';
+ };
+
+ userName = mkOption {
+ default = "znc";
+ example = "johntron";
+ type = types.str;
+ description = ''
+ The user name used to log in to the ZNC web admin interface.
+ '';
+ };
+
+ networks = mkOption {
+ default = { };
+ type = with types; attrsOf (submodule networkOpts);
+ description = ''
+ IRC networks to connect the user to.
+ '';
+ example = literalExample ''
+ {
+ "freenode" = {
+ server = "chat.freenode.net";
+ port = 6697;
+ useSSL = true;
+ modules = [ "simple_away" ];
+ };
+ };
+ '';
+ };
+
+ nick = mkOption {
+ default = "znc-user";
+ example = "john";
+ type = types.str;
+ description = ''
+ The IRC nick.
+ '';
+ };
+
+ passBlock = mkOption {
+ example = literalExample ''
+ &lt;Pass password&gt;
+ Method = sha256
+ Hash = e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93
+ Salt = l5Xryew4g*!oa(ECfX2o
+ &lt;/Pass&gt;
+ '';
+ type = types.str;
+ description = ''
+ Generate with `nix-shell -p znc --command "znc --makepass"`.
+ This is the password used to log in to the ZNC web admin interface.
+ You can also set this through
+ <option>services.znc.config.User.&lt;username&gt;.Pass.Method</option>
+ and co.
+ '';
+ };
+
+ port = mkOption {
+ default = 5000;
+ type = types.int;
+ description = ''
+ Specifies the port on which to listen.
+ '';
+ };
+
+ useSSL = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Indicates whether the ZNC server should use SSL when listening on
+ the specified port. A self-signed certificate will be generated.
+ '';
+ };
+
+ uriPrefix = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/znc/";
+ description = ''
+ An optional URI prefix for the ZNC web interface. Can be
+ used to make ZNC available behind a reverse proxy.
+ '';
+ };
+
+ extraZncConf = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra config to `znc.conf` file.
+ '';
+ };
+ };
+
+ };
+ };
+
+ config = mkIf cfg.useLegacyConfig {
+
+ services.znc.config = let
+ c = cfg.confOptions;
+ # defaults here should override defaults set in the non-legacy part
+ mkDefault = mkOverride 900;
+ in {
+ LoadModule = mkDefault c.modules;
+ Listener.l = {
+ Port = mkDefault c.port;
+ IPv4 = mkDefault true;
+ IPv6 = mkDefault true;
+ SSL = mkDefault c.useSSL;
+ URIPrefix = c.uriPrefix;
+ };
+ User.${c.userName} = {
+ Admin = mkDefault true;
+ Nick = mkDefault c.nick;
+ AltNick = mkDefault "${c.nick}_";
+ Ident = mkDefault c.nick;
+ RealName = mkDefault c.nick;
+ LoadModule = mkDefault c.userModules;
+ Network = mapAttrs (name: net: {
+ LoadModule = mkDefault net.modules;
+ Server = mkDefault "${net.server} ${optionalString net.useSSL "+"}${toString net.port} ${net.password}";
+ Chan = optionalAttrs net.hasBitlbeeControlChannel { "&bitlbee" = mkDefault {}; } //
+ listToAttrs (map (n: nameValuePair "#${n}" (mkDefault {})) net.channels);
+ extraConfig = if net.extraConf == "" then mkDefault null else net.extraConf;
+ }) c.networks;
+ extraConfig = [ c.passBlock ];
+ };
+ extraConfig = optional (c.extraZncConf != "") c.extraZncConf;
+ };
+ };
+
+ imports = [
+ (mkRemovedOptionModule ["services" "znc" "zncConf"] ''
+ Instead of `services.znc.zncConf = "... foo ...";`, use
+ `services.znc.configFile = pkgs.writeText "znc.conf" "... foo ...";`.
+ '')
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/printing/cupsd.nix b/nixpkgs/nixos/modules/services/printing/cupsd.nix
new file mode 100644
index 00000000000..3fcae611dc7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/printing/cupsd.nix
@@ -0,0 +1,442 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs) cups cups-pk-helper cups-filters;
+
+ cfg = config.services.printing;
+
+ avahiEnabled = config.services.avahi.enable;
+ polkitEnabled = config.security.polkit.enable;
+
+ additionalBackends = pkgs.runCommand "additional-cups-backends" {
+ preferLocalBuild = true;
+ } ''
+ mkdir -p $out
+ if [ ! -e ${cups.out}/lib/cups/backend/smb ]; then
+ mkdir -p $out/lib/cups/backend
+ ln -sv ${pkgs.samba}/bin/smbspool $out/lib/cups/backend/smb
+ fi
+
+ # Provide support for printing via HTTPS.
+ if [ ! -e ${cups.out}/lib/cups/backend/https ]; then
+ mkdir -p $out/lib/cups/backend
+ ln -sv ${cups.out}/lib/cups/backend/ipp $out/lib/cups/backend/https
+ fi
+ '';
+
+ # Here we can enable additional backends, filters, etc. that are not
+ # part of CUPS itself, e.g. the SMB backend is part of Samba. Since
+ # we can't update ${cups.out}/lib/cups itself, we create a symlink tree
+ # here and add the additional programs. The ServerBin directive in
+ # cupsd.conf tells cupsd to use this tree.
+ bindir = pkgs.buildEnv {
+ name = "cups-progs";
+ paths =
+ [ cups.out additionalBackends cups-filters pkgs.ghostscript ]
+ ++ cfg.drivers;
+ pathsToLink = [ "/lib" "/share/cups" "/bin" ];
+ postBuild = cfg.bindirCmds;
+ ignoreCollisions = true;
+ };
+
+ writeConf = name: text: pkgs.writeTextFile {
+ inherit name text;
+ destination = "/etc/cups/${name}";
+ };
+
+ cupsFilesFile = writeConf "cups-files.conf" ''
+ SystemGroup root wheel
+
+ ServerBin ${bindir}/lib/cups
+ DataDir ${bindir}/share/cups
+ DocumentRoot ${cups.out}/share/doc/cups
+
+ AccessLog syslog
+ ErrorLog syslog
+ PageLog syslog
+
+ TempDir ${cfg.tempDir}
+
+ SetEnv PATH /var/lib/cups/path/lib/cups/filter:/var/lib/cups/path/bin
+
+ # User and group used to run external programs, including
+ # those that actually send the job to the printer. Note that
+ # Udev sets the group of printer devices to `lp', so we want
+ # these programs to run as `lp' as well.
+ User cups
+ Group lp
+
+ ${cfg.extraFilesConf}
+ '';
+
+ cupsdFile = writeConf "cupsd.conf" ''
+ ${concatMapStrings (addr: ''
+ Listen ${addr}
+ '') cfg.listenAddresses}
+ Listen /run/cups/cups.sock
+
+ DefaultShared ${if cfg.defaultShared then "Yes" else "No"}
+
+ Browsing ${if cfg.browsing then "Yes" else "No"}
+
+ WebInterface ${if cfg.webInterface then "Yes" else "No"}
+
+ LogLevel ${cfg.logLevel}
+
+ ${cfg.extraConf}
+ '';
+
+ browsedFile = writeConf "cups-browsed.conf" cfg.browsedConf;
+
+ rootdir = pkgs.buildEnv {
+ name = "cups-progs";
+ paths = [
+ cupsFilesFile
+ cupsdFile
+ (writeConf "client.conf" cfg.clientConf)
+ (writeConf "snmp.conf" cfg.snmpConf)
+ ] ++ optional avahiEnabled browsedFile
+ ++ cfg.drivers;
+ pathsToLink = [ "/etc/cups" ];
+ ignoreCollisions = true;
+ };
+
+ filterGutenprint = pkgs: filter (pkg: pkg.meta.isGutenprint or false == true) pkgs;
+ containsGutenprint = pkgs: length (filterGutenprint pkgs) > 0;
+ getGutenprint = pkgs: head (filterGutenprint pkgs);
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.printing = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable printing support through the CUPS daemon.
+ '';
+ };
+
+ startWhenNeeded = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If set, CUPS is socket-activated; that is,
+ instead of having it permanently running as a daemon,
+ systemd will start it on the first incoming connection.
+ '';
+ };
+
+ listenAddresses = mkOption {
+ type = types.listOf types.str;
+ default = [ "localhost:631" ];
+ example = [ "*:631" ];
+ description = ''
+ A list of addresses and ports on which to listen.
+ '';
+ };
+
+ bindirCmds = mkOption {
+ type = types.lines;
+ internal = true;
+ default = "";
+ description = ''
+ Additional commands executed while creating the directory
+ containing the CUPS server binaries.
+ '';
+ };
+
+ defaultShared = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Specifies whether local printers are shared by default.
+ '';
+ };
+
+ browsing = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Specifies whether shared printers are advertised.
+ '';
+ };
+
+ webInterface = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Specifies whether the web interface is enabled.
+ '';
+ };
+
+ logLevel = mkOption {
+ type = types.str;
+ default = "info";
+ example = "debug";
+ description = ''
+ Specifies the cupsd logging verbosity.
+ '';
+ };
+
+ extraFilesConf = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra contents of the configuration file of the CUPS daemon
+ (<filename>cups-files.conf</filename>).
+ '';
+ };
+
+ extraConf = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ BrowsePoll cups.example.com
+ MaxCopies 42
+ '';
+ description = ''
+ Extra contents of the configuration file of the CUPS daemon
+ (<filename>cupsd.conf</filename>).
+ '';
+ };
+
+ clientConf = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ ServerName server.example.com
+ Encryption Never
+ '';
+ description = ''
+ The contents of the client configuration.
+ (<filename>client.conf</filename>)
+ '';
+ };
+
+ browsedConf = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ BrowsePoll cups.example.com
+ '';
+ description = ''
+ The contents of the configuration. file of the CUPS Browsed daemon
+ (<filename>cups-browsed.conf</filename>)
+ '';
+ };
+
+ snmpConf = mkOption {
+ type = types.lines;
+ default = ''
+ Address @LOCAL
+ '';
+ description = ''
+ The contents of <filename>/etc/cups/snmp.conf</filename>. See "man
+ cups-snmp.conf" for a complete description.
+ '';
+ };
+
+ drivers = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ example = literalExample "with pkgs; [ gutenprint hplip splix cups-googlecloudprint ]";
+ description = ''
+ CUPS drivers to use. Drivers provided by CUPS, cups-filters,
+ Ghostscript and Samba are added unconditionally. If this list contains
+ Gutenprint (i.e. a derivation with
+ <literal>meta.isGutenprint = true</literal>) the PPD files in
+ <filename>/var/lib/cups/ppd</filename> will be updated automatically
+ to avoid errors due to incompatible versions.
+ '';
+ };
+
+ tempDir = mkOption {
+ type = types.path;
+ default = "/tmp";
+ example = "/tmp/cups";
+ description = ''
+ CUPSd temporary directory.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.printing.enable {
+
+ users.users = singleton
+ { name = "cups";
+ uid = config.ids.uids.cups;
+ group = "lp";
+ description = "CUPS printing services";
+ };
+
+ environment.systemPackages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
+ environment.etc.cups.source = "/var/lib/cups";
+
+ services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
+
+ # Allow asswordless printer admin for members of wheel group
+ security.polkit.extraConfig = mkIf polkitEnabled ''
+ polkit.addRule(function(action, subject) {
+ if (action.id == "org.opensuse.cupspkhelper.mechanism.all-edit" &&
+ subject.isInGroup("wheel")){
+ return polkit.Result.YES;
+ }
+ });
+ '';
+
+ # Cups uses libusb to talk to printers, and does not use the
+ # linux kernel driver. If the driver is not in a black list, it
+ # gets loaded, and then cups cannot access the printers.
+ boot.blacklistedKernelModules = [ "usblp" ];
+
+ # Some programs like print-manager rely on this value to get
+ # printer test pages.
+ environment.sessionVariables.CUPS_DATADIR = "${bindir}/share/cups";
+
+ systemd.packages = [ cups.out ];
+
+ systemd.sockets.cups = mkIf cfg.startWhenNeeded {
+ wantedBy = [ "sockets.target" ];
+ listenStreams = [ "/run/cups/cups.sock" ]
+ ++ map (x: replaceStrings ["localhost"] ["127.0.0.1"] (removePrefix "*:" x)) cfg.listenAddresses;
+ };
+
+ systemd.services.cups =
+ { wantedBy = optionals (!cfg.startWhenNeeded) [ "multi-user.target" ];
+ wants = [ "network.target" ];
+ after = [ "network.target" ];
+
+ path = [ cups.out ];
+
+ preStart =
+ ''
+ mkdir -m 0700 -p /var/cache/cups
+ mkdir -m 0700 -p /var/spool/cups
+ mkdir -m 0755 -p ${cfg.tempDir}
+
+ mkdir -m 0755 -p /var/lib/cups
+ # While cups will automatically create self-signed certificates if accessed via TLS,
+ # this directory to store the certificates needs to be created manually.
+ mkdir -m 0700 -p /var/lib/cups/ssl
+
+ # Backwards compatibility
+ if [ ! -L /etc/cups ]; then
+ mv /etc/cups/* /var/lib/cups
+ rmdir /etc/cups
+ ln -s /var/lib/cups /etc/cups
+ fi
+ # First, clean existing symlinks
+ if [ -n "$(ls /var/lib/cups)" ]; then
+ for i in /var/lib/cups/*; do
+ [ -L "$i" ] && rm "$i"
+ done
+ fi
+ # Then, populate it with static files
+ cd ${rootdir}/etc/cups
+ for i in *; do
+ [ ! -e "/var/lib/cups/$i" ] && ln -s "${rootdir}/etc/cups/$i" "/var/lib/cups/$i"
+ done
+
+ #update path reference
+ [ -L /var/lib/cups/path ] && \
+ rm /var/lib/cups/path
+ [ ! -e /var/lib/cups/path ] && \
+ ln -s ${bindir} /var/lib/cups/path
+
+ ${optionalString (containsGutenprint cfg.drivers) ''
+ if [ -d /var/lib/cups/ppd ]; then
+ ${getGutenprint cfg.drivers}/bin/cups-genppdupdate -p /var/lib/cups/ppd
+ fi
+ ''}
+ '';
+
+ serviceConfig = {
+ PrivateTmp = true;
+ RuntimeDirectory = [ "cups" ];
+ };
+ };
+
+ systemd.services.cups-browsed = mkIf avahiEnabled
+ { description = "CUPS Remote Printer Discovery";
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
+ bindsTo = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
+ partOf = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
+ after = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
+
+ path = [ cups ];
+
+ serviceConfig.ExecStart = "${cups-filters}/bin/cups-browsed";
+
+ restartTriggers = [ browsedFile ];
+ };
+
+ services.printing.extraConf =
+ ''
+ DefaultAuthType Basic
+
+ <Location />
+ Order allow,deny
+ Allow localhost
+ </Location>
+
+ <Location /admin>
+ Order allow,deny
+ Allow localhost
+ </Location>
+
+ <Location /admin/conf>
+ AuthType Basic
+ Require user @SYSTEM
+ Order allow,deny
+ Allow localhost
+ </Location>
+
+ <Policy default>
+ <Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job CUPS-Move-Job>
+ Require user @OWNER @SYSTEM
+ Order deny,allow
+ </Limit>
+
+ <Limit Pause-Printer Resume-Printer Set-Printer-Attributes Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After CUPS-Add-Printer CUPS-Delete-Printer CUPS-Add-Class CUPS-Delete-Class CUPS-Accept-Jobs CUPS-Reject-Jobs CUPS-Set-Default>
+ AuthType Basic
+ Require user @SYSTEM
+ Order deny,allow
+ </Limit>
+
+ <Limit Cancel-Job CUPS-Authenticate-Job>
+ Require user @OWNER @SYSTEM
+ Order deny,allow
+ </Limit>
+
+ <Limit All>
+ Order deny,allow
+ </Limit>
+ </Policy>
+ '';
+
+ security.pam.services.cups = {};
+
+ };
+
+ meta.maintainers = with lib.maintainers; [ matthewbauer ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/scheduling/atd.nix b/nixpkgs/nixos/modules/services/scheduling/atd.nix
new file mode 100644
index 00000000000..a32907647a0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/scheduling/atd.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.atd;
+
+ inherit (pkgs) at;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.atd.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the <command>at</command> daemon, a command scheduler.
+ '';
+ };
+
+ services.atd.allowEveryone = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to make <filename>/var/spool/at{jobs,spool}</filename>
+ writeable by everyone (and sticky). This is normally not
+ needed since the <command>at</command> commands are
+ setuid/setgid <literal>atd</literal>.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ # Not wrapping "batch" because it's a shell script (kernel drops perms
+ # anyway) and it's patched to invoke the "at" setuid wrapper.
+ security.wrappers = builtins.listToAttrs (
+ map (program: { name = "${program}"; value = {
+ source = "${at}/bin/${program}";
+ owner = "atd";
+ group = "atd";
+ setuid = true;
+ setgid = true;
+ };}) [ "at" "atq" "atrm" ]);
+
+ environment.systemPackages = [ at ];
+
+ security.pam.services.atd = {};
+
+ users.users = singleton
+ { name = "atd";
+ uid = config.ids.uids.atd;
+ description = "atd user";
+ home = "/var/empty";
+ };
+
+ users.groups = singleton
+ { name = "atd";
+ gid = config.ids.gids.atd;
+ };
+
+ systemd.services.atd = {
+ description = "Job Execution Daemon (atd)";
+ after = [ "systemd-udev-settle.service" ];
+ wants = [ "systemd-udev-settle.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ at ];
+
+ preStart = ''
+ # Snippets taken and adapted from the original `install' rule of
+ # the makefile.
+
+ # We assume these values are those actually used in Nixpkgs for
+ # `at'.
+ spooldir=/var/spool/atspool
+ jobdir=/var/spool/atjobs
+ etcdir=/etc/at
+
+ for dir in "$spooldir" "$jobdir" "$etcdir"; do
+ if [ ! -d "$dir" ]; then
+ mkdir -p "$dir"
+ chown atd:atd "$dir"
+ fi
+ done
+ chmod 1770 "$spooldir" "$jobdir"
+ ${if cfg.allowEveryone then ''chmod a+rwxt "$spooldir" "$jobdir" '' else ""}
+ if [ ! -f "$etcdir"/at.deny ]; then
+ touch "$etcdir"/at.deny
+ chown root:atd "$etcdir"/at.deny
+ chmod 640 "$etcdir"/at.deny
+ fi
+ if [ ! -f "$jobdir"/.SEQ ]; then
+ touch "$jobdir"/.SEQ
+ chown atd:atd "$jobdir"/.SEQ
+ chmod 600 "$jobdir"/.SEQ
+ fi
+ '';
+
+ script = "atd";
+
+ serviceConfig.Type = "forking";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/scheduling/chronos.nix b/nixpkgs/nixos/modules/services/scheduling/chronos.nix
new file mode 100644
index 00000000000..9a8ed4c09ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/scheduling/chronos.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.chronos;
+
+in {
+
+ ###### interface
+
+ options.services.chronos = {
+ enable = mkOption {
+ description = "Whether to enable graphite web frontend.";
+ default = false;
+ type = types.bool;
+ };
+
+ httpPort = mkOption {
+ description = "Chronos listening port";
+ default = 4400;
+ type = types.int;
+ };
+
+ master = mkOption {
+ description = "Chronos mesos master zookeeper address";
+ default = "zk://${head cfg.zookeeperHosts}/mesos";
+ type = types.str;
+ };
+
+ zookeeperHosts = mkOption {
+ description = "Chronos mesos zookepper addresses";
+ default = [ "localhost:2181" ];
+ type = types.listOf types.str;
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.chronos = {
+ description = "Chronos Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "zookeeper.service" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.chronos}/bin/chronos --master ${cfg.master} --zk_hosts ${concatStringsSep "," cfg.zookeeperHosts} --http_port ${toString cfg.httpPort}";
+ User = "chronos";
+ };
+ };
+
+ users.users.chronos.uid = config.ids.uids.chronos;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/scheduling/cron.nix b/nixpkgs/nixos/modules/services/scheduling/cron.nix
new file mode 100644
index 00000000000..3bc31832946
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/scheduling/cron.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ # Put all the system cronjobs together.
+ systemCronJobsFile = pkgs.writeText "system-crontab"
+ ''
+ SHELL=${pkgs.bash}/bin/bash
+ PATH=${config.system.path}/bin:${config.system.path}/sbin
+ ${optionalString (config.services.cron.mailto != null) ''
+ MAILTO="${config.services.cron.mailto}"
+ ''}
+ NIX_CONF_DIR=/etc/nix
+ ${lib.concatStrings (map (job: job + "\n") config.services.cron.systemCronJobs)}
+ '';
+
+ # Vixie cron requires build-time configuration for the sendmail path.
+ cronNixosPkg = pkgs.cron.override {
+ # The mail.nix nixos module, if there is any local mail system enabled,
+ # should have sendmail in this path.
+ sendmailPath = "/run/wrappers/bin/sendmail";
+ };
+
+ allFiles =
+ optional (config.services.cron.systemCronJobs != []) systemCronJobsFile
+ ++ config.services.cron.cronFiles;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.cron = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Vixie cron daemon.";
+ };
+
+ mailto = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Email address to which job output will be mailed.";
+ };
+
+ systemCronJobs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = literalExample ''
+ [ "* * * * * test ls -l / > /tmp/cronout 2>&1"
+ "* * * * * eelco echo Hello World > /home/eelco/cronout"
+ ]
+ '';
+ description = ''
+ A list of Cron jobs to be appended to the system-wide
+ crontab. See the manual page for crontab for the expected
+ format. If you want to get the results mailed you must setuid
+ sendmail. See <option>security.wrappers</option>
+
+ If neither /var/cron/cron.deny nor /var/cron/cron.allow exist only root
+ is allowed to have its own crontab file. The /var/cron/cron.deny file
+ is created automatically for you, so every user can use a crontab.
+
+ Many nixos modules set systemCronJobs, so if you decide to disable vixie cron
+ and enable another cron daemon, you may want it to get its system crontab
+ based on systemCronJobs.
+ '';
+ };
+
+ cronFiles = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ A list of extra crontab files that will be read and appended to the main
+ crontab file when the cron service starts.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkMerge [
+
+ { services.cron.enable = mkDefault (allFiles != []); }
+ (mkIf (config.services.cron.enable) {
+ security.wrappers.crontab.source = "${cronNixosPkg}/bin/crontab";
+ environment.systemPackages = [ cronNixosPkg ];
+ environment.etc.crontab =
+ { source = pkgs.runCommand "crontabs" { inherit allFiles; preferLocalBuild = true; }
+ ''
+ touch $out
+ for i in $allFiles; do
+ cat "$i" >> $out
+ done
+ '';
+ mode = "0600"; # Cron requires this.
+ };
+
+ systemd.services.cron =
+ { description = "Cron Daemon";
+
+ wantedBy = [ "multi-user.target" ];
+
+ preStart =
+ ''
+ mkdir -m 710 -p /var/cron
+
+ # By default, allow all users to create a crontab. This
+ # is denoted by the existence of an empty cron.deny file.
+ if ! test -e /var/cron/cron.allow -o -e /var/cron/cron.deny; then
+ touch /var/cron/cron.deny
+ fi
+ '';
+
+ restartTriggers = [ config.time.timeZone ];
+ serviceConfig.ExecStart = "${cronNixosPkg}/bin/cron -n";
+ };
+
+ })
+
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/scheduling/fcron.nix b/nixpkgs/nixos/modules/services/scheduling/fcron.nix
new file mode 100644
index 00000000000..e43ca014e14
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/scheduling/fcron.nix
@@ -0,0 +1,166 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.fcron;
+
+ queuelen = if cfg.queuelen == null then "" else "-q ${toString cfg.queuelen}";
+
+ # Duplicate code, also found in cron.nix. Needs deduplication.
+ systemCronJobs =
+ ''
+ SHELL=${pkgs.bash}/bin/bash
+ PATH=${config.system.path}/bin:${config.system.path}/sbin
+ ${optionalString (config.services.cron.mailto != null) ''
+ MAILTO="${config.services.cron.mailto}"
+ ''}
+ NIX_CONF_DIR=/etc/nix
+ ${lib.concatStrings (map (job: job + "\n") config.services.cron.systemCronJobs)}
+ '';
+
+ allowdeny = target: users:
+ { source = pkgs.writeText "fcron.${target}" (concatStringsSep "\n" users);
+ target = "fcron.${target}";
+ mode = "644";
+ gid = config.ids.gids.fcron;
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.fcron = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the <command>fcron</command> daemon.";
+ };
+
+ allow = mkOption {
+ type = types.listOf types.str;
+ default = [ "all" ];
+ description = ''
+ Users allowed to use fcrontab and fcrondyn (one name per
+ line, <literal>all</literal> for everyone).
+ '';
+ };
+
+ deny = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Users forbidden from using fcron.";
+ };
+
+ maxSerialJobs = mkOption {
+ type = types.int;
+ default = 1;
+ description = "Maximum number of serial jobs which can run simultaneously.";
+ };
+
+ queuelen = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Number of jobs the serial queue and the lavg queue can contain.";
+ };
+
+ systab = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''The "system" crontab contents.'';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.fcron.systab = systemCronJobs;
+
+ environment.etc =
+ [ (allowdeny "allow" (cfg.allow))
+ (allowdeny "deny" cfg.deny)
+ # see man 5 fcron.conf
+ { source =
+ let
+ isSendmailWrapped =
+ lib.hasAttr "sendmail" config.security.wrappers;
+ sendmailPath =
+ if isSendmailWrapped then "/run/wrappers/bin/sendmail"
+ else "${config.system.path}/bin/sendmail";
+ in
+ pkgs.writeText "fcron.conf" ''
+ fcrontabs = /var/spool/fcron
+ pidfile = /run/fcron.pid
+ fifofile = /run/fcron.fifo
+ fcronallow = /etc/fcron.allow
+ fcrondeny = /etc/fcron.deny
+ shell = /bin/sh
+ sendmail = ${sendmailPath}
+ editor = ${pkgs.vim}/bin/vim
+ '';
+ target = "fcron.conf";
+ gid = config.ids.gids.fcron;
+ mode = "0644";
+ }
+ ];
+
+ environment.systemPackages = [ pkgs.fcron ];
+ users.users.fcron = {
+ uid = config.ids.uids.fcron;
+ home = "/var/spool/fcron";
+ group = "fcron";
+ };
+ users.groups.fcron.gid = config.ids.gids.fcron;
+
+ security.wrappers = {
+ fcrontab = {
+ source = "${pkgs.fcron}/bin/fcrontab";
+ owner = "fcron";
+ group = "fcron";
+ setgid = true;
+ setuid = true;
+ };
+ fcrondyn = {
+ source = "${pkgs.fcron}/bin/fcrondyn";
+ owner = "fcron";
+ group = "fcron";
+ setgid = true;
+ };
+ fcronsighup = {
+ source = "${pkgs.fcron}/bin/fcronsighup";
+ group = "fcron";
+ };
+ };
+ systemd.services.fcron = {
+ description = "fcron daemon";
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ pkgs.fcron ];
+
+ preStart = ''
+ install \
+ --mode 0770 \
+ --owner fcron \
+ --group fcron \
+ --directory /var/spool/fcron
+ # load system crontab file
+ /run/wrappers/bin/fcrontab -u systab - < ${pkgs.writeText "systab" cfg.systab}
+ '';
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.fcron}/sbin/fcron -m ${toString cfg.maxSerialJobs} ${queuelen}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/scheduling/marathon.nix b/nixpkgs/nixos/modules/services/scheduling/marathon.nix
new file mode 100644
index 00000000000..0961a67770e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/scheduling/marathon.nix
@@ -0,0 +1,98 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.marathon;
+
+in {
+
+ ###### interface
+
+ options.services.marathon = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the marathon mesos framework.
+ '';
+ };
+
+ master = mkOption {
+ type = types.str;
+ default = "zk://${concatStringsSep "," cfg.zookeeperHosts}/mesos";
+ example = "zk://1.2.3.4:2181,2.3.4.5:2181,3.4.5.6:2181/mesos";
+ description = ''
+ Mesos master address. See <link xlink:href="https://mesosphere.github.io/marathon/docs/"/> for details.
+ '';
+ };
+
+ zookeeperHosts = mkOption {
+ type = types.listOf types.str;
+ default = [ "localhost:2181" ];
+ example = [ "1.2.3.4:2181" "2.3.4.5:2181" "3.4.5.6:2181" ];
+ description = ''
+ ZooKeeper hosts' addresses.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "marathon";
+ example = "root";
+ description = ''
+ The user that the Marathon framework will be launched as. If the user doesn't exist it will be created.
+ If you want to run apps that require root access or you want to launch apps using arbitrary users, that
+ is using the `--mesos_user` flag then you need to change this to `root`.
+ '';
+ };
+
+ httpPort = mkOption {
+ type = types.int;
+ default = 8080;
+ description = ''
+ Marathon listening port for HTTP connections.
+ '';
+ };
+
+ extraCmdLineOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--https_port=8443" "--zk_timeout=10000" "--marathon_store_timeout=2000" ];
+ description = ''
+ Extra command line options to pass to Marathon.
+ See <link xlink:href="https://mesosphere.github.io/marathon/docs/command-line-flags.html"/> for all possible flags.
+ '';
+ };
+
+ environment = mkOption {
+ default = { };
+ type = types.attrs;
+ example = { JAVA_OPTS = "-Xmx512m"; MESOSPHERE_HTTP_CREDENTIALS = "username:password"; };
+ description = ''
+ Environment variables passed to Marathon.
+ '';
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.marathon = {
+ description = "Marathon Service";
+ environment = cfg.environment;
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "zookeeper.service" "mesos-master.service" "mesos-slave.service" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.marathon}/bin/marathon --master ${cfg.master} --zk zk://${concatStringsSep "," cfg.zookeeperHosts}/marathon --http_port ${toString cfg.httpPort} ${concatStringsSep " " cfg.extraCmdLineOptions}";
+ User = cfg.user;
+ Restart = "always";
+ RestartSec = "2";
+ };
+ };
+
+ users.users.${cfg.user} = { };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix b/nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix
new file mode 100644
index 00000000000..9620c3e0b6d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.elasticsearch-curator;
+ curatorConfig = pkgs.writeTextFile {
+ name = "config.yaml";
+ text = ''
+ ---
+ # Remember, leave a key empty if there is no value. None will be a string,
+ # not a Python "NoneType"
+ client:
+ hosts: ${builtins.toJSON cfg.hosts}
+ port: ${toString cfg.port}
+ url_prefix:
+ use_ssl: False
+ certificate:
+ client_cert:
+ client_key:
+ ssl_no_validate: False
+ http_auth:
+ timeout: 30
+ master_only: False
+ logging:
+ loglevel: INFO
+ logfile:
+ logformat: default
+ blacklist: ['elasticsearch', 'urllib3']
+ '';
+ };
+ curatorAction = pkgs.writeTextFile {
+ name = "action.yaml";
+ text = cfg.actionYAML;
+ };
+in {
+
+ options.services.elasticsearch-curator = {
+
+ enable = mkEnableOption "elasticsearch curator";
+ interval = mkOption {
+ description = "The frequency to run curator, a systemd.time such as 'hourly'";
+ default = "hourly";
+ type = types.str;
+ };
+ hosts = mkOption {
+ description = "a list of elasticsearch hosts to connect to";
+ type = types.listOf types.str;
+ default = ["localhost"];
+ };
+ port = mkOption {
+ description = "the port that elasticsearch is listening on";
+ type = types.int;
+ default = 9200;
+ };
+ actionYAML = mkOption {
+ description = "curator action.yaml file contents, alternatively use curator-cli which takes a simple action command";
+ example = ''
+ ---
+ actions:
+ 1:
+ action: delete_indices
+ description: >-
+ Delete indices older than 45 days (based on index name), for logstash-
+ prefixed indices. Ignore the error if the filter does not result in an
+ actionable list of indices (ignore_empty_list) and exit cleanly.
+ options:
+ ignore_empty_list: True
+ disable_action: False
+ filters:
+ - filtertype: pattern
+ kind: prefix
+ value: logstash-
+ - filtertype: age
+ source: name
+ direction: older
+ timestring: '%Y.%m.%d'
+ unit: days
+ unit_count: 45
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.elasticsearch-curator = {
+ startAt = cfg.interval;
+ serviceConfig = {
+ ExecStart =
+ "${pkgs.elasticsearch-curator}/bin/curator" +
+ " --config ${curatorConfig} ${curatorAction}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/search/elasticsearch.nix b/nixpkgs/nixos/modules/services/search/elasticsearch.nix
new file mode 100644
index 00000000000..91d8f544e16
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/elasticsearch.nix
@@ -0,0 +1,208 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.elasticsearch;
+
+ es6 = builtins.compareVersions cfg.package.version "6" >= 0;
+
+ esConfig = ''
+ network.host: ${cfg.listenAddress}
+ cluster.name: ${cfg.cluster_name}
+
+ http.port: ${toString cfg.port}
+ transport.tcp.port: ${toString cfg.tcp_port}
+
+ ${cfg.extraConf}
+ '';
+
+ configDir = cfg.dataDir + "/config";
+
+ elasticsearchYml = pkgs.writeTextFile {
+ name = "elasticsearch.yml";
+ text = esConfig;
+ };
+
+ loggingConfigFilename = "log4j2.properties";
+ loggingConfigFile = pkgs.writeTextFile {
+ name = loggingConfigFilename;
+ text = cfg.logging;
+ };
+
+ esPlugins = pkgs.buildEnv {
+ name = "elasticsearch-plugins";
+ paths = cfg.plugins;
+ postBuild = "${pkgs.coreutils}/bin/mkdir -p $out/plugins";
+ };
+
+in {
+
+ ###### interface
+
+ options.services.elasticsearch = {
+ enable = mkOption {
+ description = "Whether to enable elasticsearch.";
+ default = false;
+ type = types.bool;
+ };
+
+ package = mkOption {
+ description = "Elasticsearch package to use.";
+ default = pkgs.elasticsearch;
+ defaultText = "pkgs.elasticsearch";
+ type = types.package;
+ };
+
+ listenAddress = mkOption {
+ description = "Elasticsearch listen address.";
+ default = "127.0.0.1";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Elasticsearch port to listen for HTTP traffic.";
+ default = 9200;
+ type = types.int;
+ };
+
+ tcp_port = mkOption {
+ description = "Elasticsearch port for the node to node communication.";
+ default = 9300;
+ type = types.int;
+ };
+
+ cluster_name = mkOption {
+ description = "Elasticsearch name that identifies your cluster for auto-discovery.";
+ default = "elasticsearch";
+ type = types.str;
+ };
+
+ extraConf = mkOption {
+ description = "Extra configuration for elasticsearch.";
+ default = "";
+ type = types.str;
+ example = ''
+ node.name: "elasticsearch"
+ node.master: true
+ node.data: false
+ '';
+ };
+
+ logging = mkOption {
+ description = "Elasticsearch logging configuration.";
+ default = ''
+ logger.action.name = org.elasticsearch.action
+ logger.action.level = info
+
+ appender.console.type = Console
+ appender.console.name = console
+ appender.console.layout.type = PatternLayout
+ appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
+
+ rootLogger.level = info
+ rootLogger.appenderRef.console.ref = console
+ '';
+ type = types.str;
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/elasticsearch";
+ description = ''
+ Data directory for elasticsearch.
+ '';
+ };
+
+ extraCmdLineOptions = mkOption {
+ description = "Extra command line options for the elasticsearch launcher.";
+ default = [];
+ type = types.listOf types.str;
+ };
+
+ extraJavaOptions = mkOption {
+ description = "Extra command line options for Java.";
+ default = [];
+ type = types.listOf types.str;
+ example = [ "-Djava.net.preferIPv4Stack=true" ];
+ };
+
+ plugins = mkOption {
+ description = "Extra elasticsearch plugins";
+ default = [];
+ type = types.listOf types.package;
+ example = lib.literalExample "[ pkgs.elasticsearchPlugins.discovery-ec2 ]";
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.elasticsearch = {
+ description = "Elasticsearch Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = [ pkgs.inetutils ];
+ environment = {
+ ES_HOME = cfg.dataDir;
+ ES_JAVA_OPTS = toString ( optional (!es6) [ "-Des.path.conf=${configDir}" ]
+ ++ cfg.extraJavaOptions);
+ } // optionalAttrs es6 {
+ ES_PATH_CONF = configDir;
+ };
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/elasticsearch ${toString cfg.extraCmdLineOptions}";
+ User = "elasticsearch";
+ PermissionsStartOnly = true;
+ LimitNOFILE = "1024000";
+ };
+ preStart = ''
+ ${optionalString (!config.boot.isContainer) ''
+ # Only set vm.max_map_count if lower than ES required minimum
+ # This avoids conflict if configured via boot.kernel.sysctl
+ if [ `${pkgs.procps}/bin/sysctl -n vm.max_map_count` -lt 262144 ]; then
+ ${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
+ fi
+ ''}
+
+ mkdir -m 0700 -p ${cfg.dataDir}
+
+ # Install plugins
+ ln -sfT ${esPlugins}/plugins ${cfg.dataDir}/plugins
+ ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
+ ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
+
+ # elasticsearch needs to create the elasticsearch.keystore in the config directory
+ # so this directory needs to be writable.
+ mkdir -m 0700 -p ${configDir}
+
+ # Note that we copy config files from the nix store instead of symbolically linking them
+ # because otherwise X-Pack Security will raise the following exception:
+ # java.security.AccessControlException:
+ # access denied ("java.io.FilePermission" "/var/lib/elasticsearch/config/elasticsearch.yml" "read")
+
+ cp ${elasticsearchYml} ${configDir}/elasticsearch.yml
+ # Make sure the logging configuration for old elasticsearch versions is removed:
+ rm -f "${configDir}/logging.yml"
+ cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename}
+ mkdir -p ${configDir}/scripts
+ ${optionalString es6 "cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options"}
+
+ if [ "$(id -u)" = 0 ]; then chown -R elasticsearch:elasticsearch ${cfg.dataDir}; fi
+ '';
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ users = {
+ groups.elasticsearch.gid = config.ids.gids.elasticsearch;
+ users.elasticsearch = {
+ uid = config.ids.uids.elasticsearch;
+ description = "Elasticsearch daemon user";
+ home = cfg.dataDir;
+ group = "elasticsearch";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/search/hound.nix b/nixpkgs/nixos/modules/services/search/hound.nix
new file mode 100644
index 00000000000..6740928db9a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/hound.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.hound;
+in {
+ options = {
+ services.hound = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the hound code search daemon.
+ '';
+ };
+
+ user = mkOption {
+ default = "hound";
+ type = types.str;
+ description = ''
+ User the hound daemon should execute under.
+ '';
+ };
+
+ group = mkOption {
+ default = "hound";
+ type = types.str;
+ description = ''
+ Group the hound daemon should execute under.
+ '';
+ };
+
+ extraGroups = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "dialout" ];
+ description = ''
+ List of extra groups that the "hound" user should be a part of.
+ '';
+ };
+
+ home = mkOption {
+ default = "/var/lib/hound";
+ type = types.path;
+ description = ''
+ The path to use as hound's $HOME. If the default user
+ "hound" is configured then this is the home of the "hound"
+ user.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.hound;
+ defaultText = "pkgs.hound";
+ type = types.package;
+ description = ''
+ Package for running hound.
+ '';
+ };
+
+ config = mkOption {
+ type = types.str;
+ description = ''
+ The full configuration of the Hound daemon. Note the dbpath
+ should be an absolute path to a writable location on disk.
+ '';
+ example = ''
+ {
+ "max-concurrent-indexers" : 2,
+ "dbpath" : "''${services.hound.home}/data",
+ "repos" : {
+ "nixpkgs": {
+ "url" : "https://www.github.com/NixOS/nixpkgs.git"
+ }
+ }
+ }
+ '';
+ };
+
+ listen = mkOption {
+ type = types.str;
+ default = "0.0.0.0:6080";
+ example = "127.0.0.1:6080 or just :6080";
+ description = ''
+ Listen on this IP:port / :port
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.groups = optional (cfg.group == "hound") {
+ name = "hound";
+ gid = config.ids.gids.hound;
+ };
+
+ users.users = optional (cfg.user == "hound") {
+ name = "hound";
+ description = "hound code search";
+ createHome = true;
+ home = cfg.home;
+ group = cfg.group;
+ extraGroups = cfg.extraGroups;
+ uid = config.ids.uids.hound;
+ };
+
+ systemd.services.hound = {
+ description = "Hound Code Search";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ WorkingDirectory = cfg.home;
+ ExecStartPre = "${pkgs.git}/bin/git config --global --replace-all http.sslCAinfo /etc/ssl/certs/ca-certificates.crt";
+ ExecStart = "${cfg.package}/bin/houndd" +
+ " -addr ${cfg.listen}" +
+ " -conf ${pkgs.writeText "hound.json" cfg.config}";
+
+ };
+ path = [ pkgs.git pkgs.mercurial pkgs.openssh ];
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/search/kibana.nix b/nixpkgs/nixos/modules/services/search/kibana.nix
new file mode 100644
index 00000000000..43a63aa8fdc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/kibana.nix
@@ -0,0 +1,209 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.kibana;
+
+ ge7 = builtins.compareVersions cfg.package.version "7" >= 0;
+ lt6_6 = builtins.compareVersions cfg.package.version "6.6" < 0;
+
+ cfgFile = pkgs.writeText "kibana.json" (builtins.toJSON (
+ (filterAttrsRecursive (n: v: v != null && v != []) ({
+ server.host = cfg.listenAddress;
+ server.port = cfg.port;
+ server.ssl.certificate = cfg.cert;
+ server.ssl.key = cfg.key;
+
+ kibana.index = cfg.index;
+ kibana.defaultAppId = cfg.defaultAppId;
+
+ elasticsearch.url = cfg.elasticsearch.url;
+ elasticsearch.hosts = cfg.elasticsearch.hosts;
+ elasticsearch.username = cfg.elasticsearch.username;
+ elasticsearch.password = cfg.elasticsearch.password;
+
+ elasticsearch.ssl.certificate = cfg.elasticsearch.cert;
+ elasticsearch.ssl.key = cfg.elasticsearch.key;
+ elasticsearch.ssl.certificateAuthorities = cfg.elasticsearch.certificateAuthorities;
+ } // cfg.extraConf)
+ )));
+
+in {
+ options.services.kibana = {
+ enable = mkEnableOption "kibana service";
+
+ listenAddress = mkOption {
+ description = "Kibana listening host";
+ default = "127.0.0.1";
+ type = types.str;
+ };
+
+ port = mkOption {
+ description = "Kibana listening port";
+ default = 5601;
+ type = types.int;
+ };
+
+ cert = mkOption {
+ description = "Kibana ssl certificate.";
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ key = mkOption {
+ description = "Kibana ssl key.";
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ index = mkOption {
+ description = "Elasticsearch index to use for saving kibana config.";
+ default = ".kibana";
+ type = types.str;
+ };
+
+ defaultAppId = mkOption {
+ description = "Elasticsearch default application id.";
+ default = "discover";
+ type = types.str;
+ };
+
+ elasticsearch = {
+ url = mkOption {
+ description = ''
+ Elasticsearch url.
+
+ Defaults to <literal>"http://localhost:9200"</literal>.
+
+ Don't set this when using Kibana >= 7.0.0 because it will result in a
+ configuration error. Use <option>services.kibana.elasticsearch.hosts</option>
+ instead.
+ '';
+ default = null;
+ type = types.nullOr types.str;
+ };
+
+ hosts = mkOption {
+ description = ''
+ The URLs of the Elasticsearch instances to use for all your queries.
+ All nodes listed here must be on the same cluster.
+
+ Defaults to <literal>[ "http://localhost:9200" ]</literal>.
+
+ This option is only valid when using kibana >= 6.6.
+ '';
+ default = null;
+ type = types.nullOr (types.listOf types.str);
+ };
+
+ username = mkOption {
+ description = "Username for elasticsearch basic auth.";
+ default = null;
+ type = types.nullOr types.str;
+ };
+
+ password = mkOption {
+ description = "Password for elasticsearch basic auth.";
+ default = null;
+ type = types.nullOr types.str;
+ };
+
+ ca = mkOption {
+ description = ''
+ CA file to auth against elasticsearch.
+
+ It's recommended to use the <option>certificateAuthorities</option> option
+ when using kibana-5.4 or newer.
+ '';
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ certificateAuthorities = mkOption {
+ description = ''
+ CA files to auth against elasticsearch.
+
+ Please use the <option>ca</option> option when using kibana &lt; 5.4
+ because those old versions don't support setting multiple CA's.
+
+ This defaults to the singleton list [ca] when the <option>ca</option> option is defined.
+ '';
+ default = if cfg.elasticsearch.ca == null then [] else [ca];
+ type = types.listOf types.path;
+ };
+
+ cert = mkOption {
+ description = "Certificate file to auth against elasticsearch.";
+ default = null;
+ type = types.nullOr types.path;
+ };
+
+ key = mkOption {
+ description = "Key file to auth against elasticsearch.";
+ default = null;
+ type = types.nullOr types.path;
+ };
+ };
+
+ package = mkOption {
+ description = "Kibana package to use";
+ default = pkgs.kibana;
+ defaultText = "pkgs.kibana";
+ example = "pkgs.kibana";
+ type = types.package;
+ };
+
+ dataDir = mkOption {
+ description = "Kibana data directory";
+ default = "/var/lib/kibana";
+ type = types.path;
+ };
+
+ extraConf = mkOption {
+ description = "Kibana extra configuration";
+ default = {};
+ type = types.attrs;
+ };
+ };
+
+ config = mkIf (cfg.enable) {
+ assertions = [
+ {
+ assertion = ge7 -> cfg.elasticsearch.url == null;
+ message =
+ "The option services.kibana.elasticsearch.url has been removed when using kibana >= 7.0.0. " +
+ "Please use option services.kibana.elasticsearch.hosts instead.";
+ }
+ {
+ assertion = lt6_6 -> cfg.elasticsearch.hosts == null;
+ message =
+ "The option services.kibana.elasticsearch.hosts is only valid for kibana >= 6.6.";
+ }
+ ];
+ systemd.services.kibana = {
+ description = "Kibana Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "elasticsearch.service" ];
+ environment = { BABEL_CACHE_PATH = "${cfg.dataDir}/.babelcache.json"; };
+ serviceConfig = {
+ ExecStart =
+ "${cfg.package}/bin/kibana" +
+ " --config ${cfgFile}" +
+ " --path.data ${cfg.dataDir}";
+ User = "kibana";
+ WorkingDirectory = cfg.dataDir;
+ };
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ users.users = singleton {
+ name = "kibana";
+ uid = config.ids.uids.kibana;
+ description = "Kibana service user";
+ home = cfg.dataDir;
+ createHome = true;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/search/solr.nix b/nixpkgs/nixos/modules/services/search/solr.nix
new file mode 100644
index 00000000000..5ef7d9893a4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/solr.nix
@@ -0,0 +1,118 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.solr;
+
+in
+
+{
+ options = {
+ services.solr = {
+ enable = mkEnableOption "Solr";
+
+ # default to the 8.x series not forcing major version upgrade of those on the 7.x series
+ package = mkOption {
+ type = types.package;
+ default = if versionAtLeast config.system.stateVersion "19.09"
+ then pkgs.solr_8
+ else pkgs.solr_7
+ ;
+ defaultText = "pkgs.solr";
+ description = ''
+ Which Solr package to use. This defaults to version 7.x if
+ <literal>system.stateVersion &lt; 19.09</literal> and version 8.x
+ otherwise.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8983;
+ description = "Port on which Solr is ran.";
+ };
+
+ stateDir = mkOption {
+ type = types.path;
+ default = "/var/lib/solr";
+ description = "The solr home directory containing config, data, and logging files.";
+ };
+
+ extraJavaOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Extra command line options given to the java process running Solr.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "solr";
+ description = "User under which Solr is ran.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "solr";
+ description = "Group under which Solr is ran.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services.solr = {
+ after = [ "network.target" "remote-fs.target" "nss-lookup.target" "systemd-journald-dev-log.socket" ];
+ wantedBy = [ "multi-user.target" ];
+
+ environment = {
+ SOLR_HOME = "${cfg.stateDir}/data";
+ LOG4J_PROPS = "${cfg.stateDir}/log4j2.xml";
+ SOLR_LOGS_DIR = "${cfg.stateDir}/logs";
+ SOLR_PORT = "${toString cfg.port}";
+ };
+ path = with pkgs; [
+ gawk
+ procps
+ ];
+ preStart = ''
+ mkdir -p "${cfg.stateDir}/data";
+ mkdir -p "${cfg.stateDir}/logs";
+
+ if ! test -e "${cfg.stateDir}/data/solr.xml"; then
+ install -D -m0640 ${cfg.package}/server/solr/solr.xml "${cfg.stateDir}/data/solr.xml"
+ install -D -m0640 ${cfg.package}/server/solr/zoo.cfg "${cfg.stateDir}/data/zoo.cfg"
+ fi
+
+ if ! test -e "${cfg.stateDir}/log4j2.xml"; then
+ install -D -m0640 ${cfg.package}/server/resources/log4j2.xml "${cfg.stateDir}/log4j2.xml"
+ fi
+ '';
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart="${cfg.package}/bin/solr start -f -a \"${concatStringsSep " " cfg.extraJavaOptions}\"";
+ ExecStop="${cfg.package}/bin/solr stop";
+ };
+ };
+
+ users.users = optionalAttrs (cfg.user == "solr") (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/nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh b/nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh
new file mode 100644
index 00000000000..264a7da9cbb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+# Based on: https://github.com/dani-garcia/bitwarden_rs/wiki/Backing-up-your-vault
+if ! mkdir -p "$BACKUP_FOLDER"; then
+ echo "Could not create backup folder '$BACKUP_FOLDER'" >&2
+ exit 1
+fi
+
+if [[ ! -f "$DATA_FOLDER"/db.sqlite3 ]]; then
+ echo "Could not find SQLite database file '$DATA_FOLDER/db.sqlite3'" >&2
+ exit 1
+fi
+
+sqlite3 "$DATA_FOLDER"/db.sqlite3 ".backup '$BACKUP_FOLDER/db.sqlite3'"
+cp "$DATA_FOLDER"/rsa_key.{der,pem,pub.der} "$BACKUP_FOLDER"
+cp -r "$DATA_FOLDER"/attachments "$BACKUP_FOLDER"
+cp -r "$DATA_FOLDER"/icon_cache "$BACKUP_FOLDER"
diff --git a/nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix b/nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix
new file mode 100644
index 00000000000..80fd65891ff
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix
@@ -0,0 +1,126 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.bitwarden_rs;
+ user = config.users.users.bitwarden_rs.name;
+ group = config.users.groups.bitwarden_rs.name;
+
+ # Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
+ nameToEnvVar = name:
+ let
+ parts = builtins.split "([A-Z0-9]+)" name;
+ partsToEnvVar = parts: foldl' (key: x: let last = stringLength key - 1; in
+ if isList x then key + optionalString (key != "" && substring last 1 key != "_") "_" + head x
+ else if key != "" && elem (substring 0 1 x) lowerChars then # to handle e.g. [ "disable" [ "2FAR" ] "emember" ]
+ substring 0 last key + optionalString (substring (last - 1) 1 key != "_") "_" + substring last 1 key + toUpper x
+ else key + toUpper x) "" parts;
+ in if builtins.match "[A-Z0-9_]+" name != null then name else partsToEnvVar parts;
+
+ configFile = pkgs.writeText "bitwarden_rs.env" (concatMapStrings (s: s + "\n") (
+ (concatLists (mapAttrsToList (name: value:
+ if value != null then [ "${nameToEnvVar name}=${if isBool value then boolToString value else toString value}" ] else []
+ ) cfg.config))));
+
+in {
+ options.services.bitwarden_rs = with types; {
+ enable = mkEnableOption "bitwarden_rs";
+
+ backupDir = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ The directory under which bitwarden_rs will backup its persistent data.
+ '';
+ };
+
+ config = mkOption {
+ type = attrsOf (nullOr (oneOf [ bool int str ]));
+ default = {};
+ example = literalExample ''
+ {
+ domain = https://bw.domain.tld:8443;
+ signupsAllowed = true;
+ rocketPort = 8222;
+ rocketLog = "critical";
+ }
+ '';
+ description = ''
+ The configuration of bitwarden_rs is done through environment variables,
+ therefore the names are converted from camel case (e.g. disable2FARemember)
+ to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
+ In this conversion digits (0-9) are handled just like upper case characters,
+ so foo2 would be converted to FOO_2.
+ Names already in this format remain unchanged, so FOO2 remains FOO2 if passed as such,
+ even though foo2 would have been converted to FOO_2.
+ This allows working around any potential future conflicting naming conventions.
+
+ Based on the attributes passed to this config option a environment file will be generated
+ that is passed to bitwarden_rs's systemd service.
+
+ The available configuration options can be found in
+ <link xlink:href="https://github.com/dani-garcia/bitwarden_rs/blob/1.8.0/.env.template">the environment template file</link>.
+ '';
+ apply = config: optionalAttrs config.webVaultEnabled {
+ webVaultFolder = "${pkgs.bitwarden_rs-vault}/share/bitwarden_rs/vault";
+ } // config;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.bitwarden_rs.config = {
+ dataFolder = "/var/lib/bitwarden_rs";
+ webVaultEnabled = mkDefault true;
+ };
+
+ users.users.bitwarden_rs = { inherit group; };
+ users.groups.bitwarden_rs = { };
+
+ systemd.services.bitwarden_rs = {
+ after = [ "network.target" ];
+ path = with pkgs; [ openssl ];
+ serviceConfig = {
+ User = user;
+ Group = group;
+ EnvironmentFile = configFile;
+ ExecStart = "${pkgs.bitwarden_rs}/bin/bitwarden_rs";
+ LimitNOFILE = "1048576";
+ LimitNPROC = "64";
+ PrivateTmp = "true";
+ PrivateDevices = "true";
+ ProtectHome = "true";
+ ProtectSystem = "strict";
+ AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+ StateDirectory = "bitwarden_rs";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ systemd.services.backup-bitwarden_rs = mkIf (cfg.backupDir != null) {
+ description = "Backup bitwarden_rs";
+ environment = {
+ DATA_FOLDER = "/var/lib/bitwarden_rs";
+ BACKUP_FOLDER = cfg.backupDir;
+ };
+ path = with pkgs; [ sqlite ];
+ serviceConfig = {
+ SyslogIdentifier = "backup-bitwarden_rs";
+ User = mkDefault user;
+ Group = mkDefault group;
+ ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ systemd.timers.backup-bitwarden_rs = mkIf (cfg.backupDir != null) {
+ description = "Backup bitwarden_rs on time";
+ timerConfig = {
+ OnCalendar = mkDefault "23:00";
+ Persistent = "true";
+ Unit = "backup-bitwarden_rs.service";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/certmgr.nix b/nixpkgs/nixos/modules/services/security/certmgr.nix
new file mode 100644
index 00000000000..e89078883eb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/certmgr.nix
@@ -0,0 +1,201 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.certmgr;
+
+ specs = mapAttrsToList (n: v: rec {
+ name = n + ".json";
+ path = if isAttrs v then pkgs.writeText name (builtins.toJSON v) else v;
+ }) cfg.specs;
+
+ allSpecs = pkgs.linkFarm "certmgr.d" specs;
+
+ certmgrYaml = pkgs.writeText "certmgr.yaml" (builtins.toJSON {
+ dir = allSpecs;
+ default_remote = cfg.defaultRemote;
+ svcmgr = cfg.svcManager;
+ before = cfg.validMin;
+ interval = cfg.renewInterval;
+ inherit (cfg) metricsPort metricsAddress;
+ });
+
+ specPaths = map dirOf (concatMap (spec:
+ if isAttrs spec then
+ collect isString (filterAttrsRecursive (n: v: isAttrs v || n == "path") spec)
+ else
+ [ spec ]
+ ) (attrValues cfg.specs));
+
+ preStart = ''
+ ${concatStringsSep " \\\n" (["mkdir -p"] ++ map escapeShellArg specPaths)}
+ ${cfg.package}/bin/certmgr -f ${certmgrYaml} check
+ '';
+in
+{
+ options.services.certmgr = {
+ enable = mkEnableOption "certmgr";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.certmgr;
+ defaultText = "pkgs.certmgr";
+ description = "Which certmgr package to use in the service.";
+ };
+
+ defaultRemote = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8888";
+ description = "The default CA host:port to use.";
+ };
+
+ validMin = mkOption {
+ default = "72h";
+ type = types.str;
+ description = "The interval before a certificate expires to start attempting to renew it.";
+ };
+
+ renewInterval = mkOption {
+ default = "30m";
+ type = types.str;
+ description = "How often to check certificate expirations and how often to update the cert_next_expires metric.";
+ };
+
+ metricsAddress = mkOption {
+ default = "127.0.0.1";
+ type = types.str;
+ description = "The address for the Prometheus HTTP endpoint.";
+ };
+
+ metricsPort = mkOption {
+ default = 9488;
+ type = types.ints.u16;
+ description = "The port for the Prometheus HTTP endpoint.";
+ };
+
+ specs = mkOption {
+ default = {};
+ example = literalExample ''
+ {
+ exampleCert =
+ let
+ domain = "example.com";
+ secret = name: "/var/lib/secrets/''${name}.pem";
+ in {
+ service = "nginx";
+ action = "reload";
+ authority = {
+ file.path = secret "ca";
+ };
+ certificate = {
+ path = secret domain;
+ };
+ private_key = {
+ owner = "root";
+ group = "root";
+ mode = "0600";
+ path = secret "''${domain}-key";
+ };
+ request = {
+ CN = domain;
+ hosts = [ "mail.''${domain}" "www.''${domain}" ];
+ key = {
+ algo = "rsa";
+ size = 2048;
+ };
+ names = {
+ O = "Example Organization";
+ C = "USA";
+ };
+ };
+ };
+ otherCert = "/var/certmgr/specs/other-cert.json";
+ }
+ '';
+ type = with types; attrsOf (either (submodule {
+ options = {
+ service = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "The service on which to perform &lt;action&gt; after fetching.";
+ };
+
+ action = mkOption {
+ type = addCheck str (x: cfg.svcManager == "command" || elem x ["restart" "reload" "nop"]);
+ default = "nop";
+ description = "The action to take after fetching.";
+ };
+
+ # These ought all to be specified according to certmgr spec def.
+ authority = mkOption {
+ type = attrs;
+ description = "certmgr spec authority object.";
+ };
+
+ certificate = mkOption {
+ type = nullOr attrs;
+ description = "certmgr spec certificate object.";
+ };
+
+ private_key = mkOption {
+ type = nullOr attrs;
+ description = "certmgr spec private_key object.";
+ };
+
+ request = mkOption {
+ type = nullOr attrs;
+ description = "certmgr spec request object.";
+ };
+ };
+ }) path);
+ description = ''
+ Certificate specs as described by:
+ <link xlink:href="https://github.com/cloudflare/certmgr#certificate-specs" />
+ These will be added to the Nix store, so they will be world readable.
+ '';
+ };
+
+ svcManager = mkOption {
+ default = "systemd";
+ type = types.enum [ "circus" "command" "dummy" "openrc" "systemd" "sysv" ];
+ description = ''
+ This specifies the service manager to use for restarting or reloading services.
+ See: <link xlink:href="https://github.com/cloudflare/certmgr#certmgryaml" />.
+ For how to use the "command" service manager in particular,
+ see: <link xlink:href="https://github.com/cloudflare/certmgr#command-svcmgr-and-how-to-use-it" />.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = cfg.specs != {};
+ message = "Certmgr specs cannot be empty.";
+ }
+ {
+ assertion = !any (hasAttrByPath [ "authority" "auth_key" ]) (attrValues cfg.specs);
+ message = ''
+ Inline services.certmgr.specs are added to the Nix store rendering them world readable.
+ Specify paths as specs, if you want to use include auth_key - or use the auth_key_file option."
+ '';
+ }
+ ];
+
+ systemd.services.certmgr = {
+ description = "certmgr";
+ path = mkIf (cfg.svcManager == "command") [ pkgs.bash ];
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ inherit preStart;
+
+ serviceConfig = {
+ Restart = "always";
+ RestartSec = "10s";
+ ExecStart = "${cfg.package}/bin/certmgr -f ${certmgrYaml}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/cfssl.nix b/nixpkgs/nixos/modules/services/security/cfssl.nix
new file mode 100644
index 00000000000..ee6d5d91fe1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/cfssl.nix
@@ -0,0 +1,209 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cfssl;
+in {
+ options.services.cfssl = {
+ enable = mkEnableOption "the CFSSL CA api-server";
+
+ dataDir = mkOption {
+ default = "/var/lib/cfssl";
+ type = types.path;
+ description = "Cfssl work directory.";
+ };
+
+ address = mkOption {
+ default = "127.0.0.1";
+ type = types.str;
+ description = "Address to bind.";
+ };
+
+ port = mkOption {
+ default = 8888;
+ type = types.ints.u16;
+ description = "Port to bind.";
+ };
+
+ ca = mkOption {
+ defaultText = "\${cfg.dataDir}/ca.pem";
+ type = types.str;
+ description = "CA used to sign the new certificate -- accepts '[file:]fname' or 'env:varname'.";
+ };
+
+ caKey = mkOption {
+ defaultText = "file:\${cfg.dataDir}/ca-key.pem";
+ type = types.str;
+ description = "CA private key -- accepts '[file:]fname' or 'env:varname'.";
+ };
+
+ caBundle = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Path to root certificate store.";
+ };
+
+ intBundle = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Path to intermediate certificate store.";
+ };
+
+ intDir = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Intermediates directory.";
+ };
+
+ metadata = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = ''
+ Metadata file for root certificate presence.
+ The content of the file is a json dictionary (k,v): each key k is
+ a SHA-1 digest of a root certificate while value v is a list of key
+ store filenames.
+ '';
+ };
+
+ remote = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Remote CFSSL server.";
+ };
+
+ configFile = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Path to configuration file. Do not put this in nix-store as it might contain secrets.";
+ };
+
+ responder = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Certificate for OCSP responder.";
+ };
+
+ responderKey = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Private key for OCSP responder certificate. Do not put this in nix-store.";
+ };
+
+ tlsKey = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Other endpoint's CA private key. Do not put this in nix-store.";
+ };
+
+ tlsCert = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Other endpoint's CA to set up TLS protocol.";
+ };
+
+ mutualTlsCa = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Mutual TLS - require clients be signed by this CA.";
+ };
+
+ mutualTlsCn = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Mutual TLS - regex for whitelist of allowed client CNs.";
+ };
+
+ tlsRemoteCa = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "CAs to trust for remote TLS requests.";
+ };
+
+ mutualTlsClientCert = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Mutual TLS - client certificate to call remote instance requiring client certs.";
+ };
+
+ mutualTlsClientKey = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Mutual TLS - client key to call remote instance requiring client certs. Do not put this in nix-store.";
+ };
+
+ dbConfig = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Certificate db configuration file. Path must be writeable.";
+ };
+
+ logLevel = mkOption {
+ default = 1;
+ type = types.enum [ 0 1 2 3 4 5 ];
+ description = "Log level (0 = DEBUG, 5 = FATAL).";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.extraGroups.cfssl = {
+ gid = config.ids.gids.cfssl;
+ };
+
+ users.extraUsers.cfssl = {
+ description = "cfssl user";
+ createHome = true;
+ home = cfg.dataDir;
+ group = "cfssl";
+ uid = config.ids.uids.cfssl;
+ };
+
+ systemd.services.cfssl = {
+ description = "CFSSL CA API server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ WorkingDirectory = cfg.dataDir;
+ StateDirectory = cfg.dataDir;
+ StateDirectoryMode = 700;
+ Restart = "always";
+ User = "cfssl";
+
+ ExecStart = with cfg; let
+ opt = n: v: optionalString (v != null) ''-${n}="${v}"'';
+ in
+ lib.concatStringsSep " \\\n" [
+ "${pkgs.cfssl}/bin/cfssl serve"
+ (opt "address" address)
+ (opt "port" (toString port))
+ (opt "ca" ca)
+ (opt "ca-key" caKey)
+ (opt "ca-bundle" caBundle)
+ (opt "int-bundle" intBundle)
+ (opt "int-dir" intDir)
+ (opt "metadata" metadata)
+ (opt "remote" remote)
+ (opt "config" configFile)
+ (opt "responder" responder)
+ (opt "responder-key" responderKey)
+ (opt "tls-key" tlsKey)
+ (opt "tls-cert" tlsCert)
+ (opt "mutual-tls-ca" mutualTlsCa)
+ (opt "mutual-tls-cn" mutualTlsCn)
+ (opt "mutual-tls-client-key" mutualTlsClientKey)
+ (opt "mutual-tls-client-cert" mutualTlsClientCert)
+ (opt "tls-remote-ca" tlsRemoteCa)
+ (opt "db-config" dbConfig)
+ (opt "loglevel" (toString logLevel))
+ ];
+ };
+ };
+
+ services.cfssl = {
+ ca = mkDefault "${cfg.dataDir}/ca.pem";
+ caKey = mkDefault "${cfg.dataDir}/ca-key.pem";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/clamav.nix b/nixpkgs/nixos/modules/services/security/clamav.nix
new file mode 100644
index 00000000000..04b433f8f2b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/clamav.nix
@@ -0,0 +1,146 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ clamavUser = "clamav";
+ stateDir = "/var/lib/clamav";
+ runDir = "/run/clamav";
+ clamavGroup = clamavUser;
+ cfg = config.services.clamav;
+ pkg = pkgs.clamav;
+
+ clamdConfigFile = pkgs.writeText "clamd.conf" ''
+ DatabaseDirectory ${stateDir}
+ LocalSocket ${runDir}/clamd.ctl
+ PidFile ${runDir}/clamd.pid
+ TemporaryDirectory /tmp
+ User clamav
+ Foreground yes
+
+ ${cfg.daemon.extraConfig}
+ '';
+
+ freshclamConfigFile = pkgs.writeText "freshclam.conf" ''
+ DatabaseDirectory ${stateDir}
+ Foreground yes
+ Checks ${toString cfg.updater.frequency}
+
+ ${cfg.updater.extraConfig}
+
+ DatabaseMirror database.clamav.net
+ '';
+in
+{
+ options = {
+ services.clamav = {
+ daemon = {
+ enable = mkEnableOption "ClamAV clamd daemon";
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration for clamd. Contents will be added verbatim to the
+ configuration file.
+ '';
+ };
+ };
+ updater = {
+ enable = mkEnableOption "ClamAV freshclam updater";
+
+ frequency = mkOption {
+ type = types.int;
+ default = 12;
+ description = ''
+ Number of database checks per day.
+ '';
+ };
+
+ interval = mkOption {
+ type = types.str;
+ default = "hourly";
+ description = ''
+ How often freshclam is invoked. See systemd.time(7) for more
+ information about the format.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration for freshclam. Contents will be added verbatim to the
+ configuration file.
+ '';
+ };
+ };
+ };
+ };
+
+ config = mkIf (cfg.updater.enable || cfg.daemon.enable) {
+ environment.systemPackages = [ pkg ];
+
+ users.users = singleton {
+ name = clamavUser;
+ uid = config.ids.uids.clamav;
+ group = clamavGroup;
+ description = "ClamAV daemon user";
+ home = stateDir;
+ };
+
+ users.groups = singleton {
+ name = clamavGroup;
+ gid = config.ids.gids.clamav;
+ };
+
+ environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
+ environment.etc."clamav/clamd.conf".source = clamdConfigFile;
+
+ systemd.services.clamav-daemon = mkIf cfg.daemon.enable {
+ description = "ClamAV daemon (clamd)";
+ after = optional cfg.updater.enable "clamav-freshclam.service";
+ requires = optional cfg.updater.enable "clamav-freshclam.service";
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ clamdConfigFile ];
+
+ preStart = ''
+ mkdir -m 0755 -p ${runDir}
+ chown ${clamavUser}:${clamavGroup} ${runDir}
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkg}/bin/clamd";
+ ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
+ PrivateTmp = "yes";
+ PrivateDevices = "yes";
+ PrivateNetwork = "yes";
+ };
+ };
+
+ systemd.timers.clamav-freshclam = mkIf cfg.updater.enable {
+ description = "Timer for ClamAV virus database updater (freshclam)";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = cfg.updater.interval;
+ Unit = "clamav-freshclam.service";
+ };
+ };
+
+ systemd.services.clamav-freshclam = mkIf cfg.updater.enable {
+ description = "ClamAV virus database updater (freshclam)";
+ restartTriggers = [ freshclamConfigFile ];
+
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ chown ${clamavUser}:${clamavGroup} ${stateDir}
+ '';
+
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkg}/bin/freshclam";
+ SuccessExitStatus = "1"; # if databases are up to date
+ PrivateTmp = "yes";
+ PrivateDevices = "yes";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/fail2ban.nix b/nixpkgs/nixos/modules/services/security/fail2ban.nix
new file mode 100644
index 00000000000..716ae7a2d2f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/fail2ban.nix
@@ -0,0 +1,152 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.fail2ban;
+
+ fail2banConf = pkgs.writeText "fail2ban.conf" cfg.daemonConfig;
+
+ jailConf = pkgs.writeText "jail.conf"
+ (concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def:
+ optionalString (def != "")
+ ''
+ [${name}]
+ ${def}
+ ''))));
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.fail2ban = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to enable the fail2ban service.";
+ };
+
+ daemonConfig = mkOption {
+ default =
+ ''
+ [Definition]
+ loglevel = INFO
+ logtarget = SYSLOG
+ socket = /run/fail2ban/fail2ban.sock
+ pidfile = /run/fail2ban/fail2ban.pid
+ '';
+ type = types.lines;
+ description =
+ ''
+ The contents of Fail2ban's main configuration file. It's
+ generally not necessary to change it.
+ '';
+ };
+
+ jails = mkOption {
+ default = { };
+ example = literalExample ''
+ { apache-nohome-iptables = '''
+ # Block an IP address if it accesses a non-existent
+ # home directory more than 5 times in 10 minutes,
+ # since that indicates that it's scanning.
+ filter = apache-nohome
+ action = iptables-multiport[name=HTTP, port="http,https"]
+ logpath = /var/log/httpd/error_log*
+ findtime = 600
+ bantime = 600
+ maxretry = 5
+ ''';
+ }
+ '';
+ type = types.attrsOf types.lines;
+ description =
+ ''
+ The configuration of each Fail2ban “jail”. A jail
+ consists of an action (such as blocking a port using
+ <command>iptables</command>) that is triggered when a
+ filter applied to a log file triggers more than a certain
+ number of times in a certain time period. Actions are
+ defined in <filename>/etc/fail2ban/action.d</filename>,
+ while filters are defined in
+ <filename>/etc/fail2ban/filter.d</filename>.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.fail2ban ];
+
+ environment.etc."fail2ban/fail2ban.conf".source = fail2banConf;
+ environment.etc."fail2ban/jail.conf".source = jailConf;
+ environment.etc."fail2ban/action.d".source = "${pkgs.fail2ban}/etc/fail2ban/action.d/*.conf";
+ environment.etc."fail2ban/filter.d".source = "${pkgs.fail2ban}/etc/fail2ban/filter.d/*.conf";
+
+ systemd.services.fail2ban =
+ { description = "Fail2ban Intrusion Prevention System";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ partOf = optional config.networking.firewall.enable "firewall.service";
+
+ restartTriggers = [ fail2banConf jailConf ];
+ path = [ pkgs.fail2ban pkgs.iptables pkgs.iproute ];
+
+ preStart =
+ ''
+ mkdir -p /var/lib/fail2ban
+ '';
+
+ unitConfig.Documentation = "man:fail2ban(1)";
+
+ serviceConfig =
+ { Type = "forking";
+ ExecStart = "${pkgs.fail2ban}/bin/fail2ban-client -x start";
+ ExecStop = "${pkgs.fail2ban}/bin/fail2ban-client stop";
+ ExecReload = "${pkgs.fail2ban}/bin/fail2ban-client reload";
+ PIDFile = "/run/fail2ban/fail2ban.pid";
+ Restart = "always";
+
+ ReadOnlyDirectories = "/";
+ ReadWriteDirectories = "/run/fail2ban /var/tmp /var/lib";
+ PrivateTmp = "true";
+ RuntimeDirectory = "fail2ban";
+ CapabilityBoundingSet = "CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_NET_RAW";
+ };
+ };
+
+ # Add some reasonable default jails. The special "DEFAULT" jail
+ # sets default values for all other jails.
+ services.fail2ban.jails.DEFAULT =
+ ''
+ ignoreip = 127.0.0.1/8
+ bantime = 600
+ findtime = 600
+ maxretry = 3
+ backend = systemd
+ enabled = true
+ '';
+
+ # Block SSH if there are too many failing connection attempts.
+ services.fail2ban.jails.ssh-iptables =
+ ''
+ filter = sshd
+ action = iptables-multiport[name=SSH, port="${concatMapStringsSep "," (p: toString p) config.services.openssh.ports}", protocol=tcp]
+ maxretry = 5
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/fprintd.nix b/nixpkgs/nixos/modules/services/security/fprintd.nix
new file mode 100644
index 00000000000..5662ebc61d2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/fprintd.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.fprintd;
+
+in
+
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.fprintd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable fprintd daemon and PAM module for fingerprint readers handling.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.fprintd;
+ defaultText = "pkgs.fprintd";
+ example = "pkgs.fprintd-thinkpad";
+ description = ''
+ fprintd package to use.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.dbus.packages = [ pkgs.fprintd ];
+
+ environment.systemPackages = [ pkgs.fprintd ];
+
+ systemd.packages = [ cfg.package ];
+
+
+ # The upstream unit does not use StateDirectory, and will
+ # fail if the directory it needs is not present. Should be
+ # fixed when https://gitlab.freedesktop.org/libfprint/fprintd/merge_requests/5
+ # is merged.
+ systemd.services.fprintd.serviceConfig.StateDirectory = "fprint";
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/fprot.nix b/nixpkgs/nixos/modules/services/security/fprot.nix
new file mode 100644
index 00000000000..47449039146
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/fprot.nix
@@ -0,0 +1,88 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ fprotUser = "fprot";
+ stateDir = "/var/lib/fprot";
+ fprotGroup = fprotUser;
+ cfg = config.services.fprot;
+in {
+ options = {
+
+ services.fprot = {
+ updater = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable automatic F-Prot virus definitions database updates.
+ '';
+ };
+
+ productData = mkOption {
+ description = ''
+ product.data file. Defaults to the one supplied with installation package.
+ '';
+ };
+
+ frequency = mkOption {
+ default = 30;
+ description = ''
+ Update virus definitions every X minutes.
+ '';
+ };
+
+ licenseKeyfile = mkOption {
+ description = ''
+ License keyfile. Defaults to the one supplied with installation package.
+ '';
+ };
+
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.updater.enable {
+
+ services.fprot.updater.productData = mkDefault "${pkgs.fprot}/opt/f-prot/product.data";
+ services.fprot.updater.licenseKeyfile = mkDefault "${pkgs.fprot}/opt/f-prot/license.key";
+
+ environment.systemPackages = [ pkgs.fprot ];
+ environment.etc = singleton {
+ source = "${pkgs.fprot}/opt/f-prot/f-prot.conf";
+ target = "f-prot.conf";
+ };
+
+ users.users = singleton
+ { name = fprotUser;
+ uid = config.ids.uids.fprot;
+ description = "F-Prot daemon user";
+ home = stateDir;
+ };
+
+ users.groups = singleton
+ { name = fprotGroup;
+ gid = config.ids.gids.fprot;
+ };
+
+ services.cron.systemCronJobs = [ "*/${toString cfg.updater.frequency} * * * * root start fprot-updater" ];
+
+ systemd.services.fprot-updater = {
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = false;
+ };
+ wantedBy = [ "multi-user.target" ];
+
+ # have to copy fpupdate executable because it insists on storing the virus database in the same dir
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ chown ${fprotUser}:${fprotGroup} ${stateDir}
+ cp ${pkgs.fprot}/opt/f-prot/fpupdate ${stateDir}
+ ln -sf ${cfg.updater.productData} ${stateDir}/product.data
+ '';
+
+ script = "/var/lib/fprot/fpupdate --keyfile ${cfg.updater.licenseKeyfile}";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/haka.nix b/nixpkgs/nixos/modules/services/security/haka.nix
new file mode 100644
index 00000000000..618e689924f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/haka.nix
@@ -0,0 +1,156 @@
+# This module defines global configuration for Haka.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.haka;
+
+ haka = cfg.package;
+
+ hakaConf = pkgs.writeText "haka.conf"
+ ''
+ [general]
+ configuration = ${if lib.strings.hasPrefix "/" cfg.configFile
+ then "${cfg.configFile}"
+ else "${haka}/share/haka/sample/${cfg.configFile}"}
+ ${optionalString (builtins.lessThan 0 cfg.threads) "thread = ${cfg.threads}"}
+
+ [packet]
+ ${optionalString cfg.pcap ''module = "packet/pcap"''}
+ ${optionalString cfg.nfqueue ''module = "packet/nqueue"''}
+ ${optionalString cfg.dump.enable ''dump = "yes"''}
+ ${optionalString cfg.dump.enable ''dump_input = "${cfg.dump.input}"''}
+ ${optionalString cfg.dump.enable ''dump_output = "${cfg.dump.output}"''}
+
+ interfaces = "${lib.strings.concatStringsSep "," cfg.interfaces}"
+
+ [log]
+ # Select the log module
+ module = "log/syslog"
+
+ # Set the default logging level
+ #level = "info,packet=debug"
+
+ [alert]
+ # Select the alert module
+ module = "alert/syslog"
+
+ # Disable alert on standard output
+ #alert_on_stdout = no
+
+ # alert/file module option
+ #file = "/dev/null"
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.haka = {
+
+ enable = mkEnableOption "Haka";
+
+ package = mkOption {
+ default = pkgs.haka;
+ defaultText = "pkgs.haka";
+ type = types.package;
+ description = "
+ Which Haka derivation to use.
+ ";
+ };
+
+ configFile = mkOption {
+ default = "empty.lua";
+ example = "/srv/haka/myfilter.lua";
+ type = types.str;
+ description = ''
+ Specify which configuration file Haka uses.
+ It can be absolute path or a path relative to the sample directory of
+ the haka git repo.
+ '';
+ };
+
+ interfaces = mkOption {
+ default = [ "eth0" ];
+ example = [ "any" ];
+ type = with types; listOf str;
+ description = ''
+ Specify which interface(s) Haka listens to.
+ Use 'any' to listen to all interfaces.
+ '';
+ };
+
+ threads = mkOption {
+ default = 0;
+ example = 4;
+ type = types.int;
+ description = ''
+ The number of threads that will be used.
+ All system threads are used by default.
+ '';
+ };
+
+ pcap = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether to enable pcap";
+ };
+
+ nfqueue = mkEnableOption "nfqueue";
+
+ dump.enable = mkEnableOption "dump";
+ dump.input = mkOption {
+ default = "/tmp/input.pcap";
+ example = "/path/to/file.pcap";
+ type = types.path;
+ description = "Path to file where incoming packets are dumped";
+ };
+
+ dump.output = mkOption {
+ default = "/tmp/output.pcap";
+ example = "/path/to/file.pcap";
+ type = types.path;
+ description = "Path to file where outgoing packets are dumped";
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.pcap != cfg.nfqueue;
+ message = "either pcap or nfqueue can be enabled, not both.";
+ }
+ { assertion = cfg.nfqueue -> !dump.enable;
+ message = "dump can only be used with nfqueue.";
+ }
+ { assertion = cfg.interfaces != [];
+ message = "at least one interface must be specified.";
+ }];
+
+
+ environment.systemPackages = [ haka ];
+
+ systemd.services.haka = {
+ description = "Haka";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = "${haka}/bin/haka -c ${hakaConf}";
+ ExecStop = "${haka}/bin/hakactl stop";
+ User = "root";
+ Type = "forking";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/haveged.nix b/nixpkgs/nixos/modules/services/security/haveged.nix
new file mode 100644
index 00000000000..eca52918881
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/haveged.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.haveged;
+
+in
+
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.haveged = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable to haveged entropy daemon, which refills
+ /dev/random when low.
+ '';
+ };
+
+ refill_threshold = mkOption {
+ type = types.int;
+ default = 1024;
+ description = ''
+ The number of bits of available entropy beneath which
+ haveged should refill the entropy pool.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.haveged =
+ { description = "Entropy Harvesting Daemon";
+ unitConfig.Documentation = "man:haveged(8)";
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ pkgs.haveged ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.haveged}/bin/haveged -F -w ${toString cfg.refill_threshold} -v 1";
+ SuccessExitStatus = 143;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ PrivateNetwork = true;
+ ProtectSystem = "full";
+ ProtectHome = true;
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/hologram-agent.nix b/nixpkgs/nixos/modules/services/security/hologram-agent.nix
new file mode 100644
index 00000000000..a5087b0a99b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/hologram-agent.nix
@@ -0,0 +1,58 @@
+{pkgs, config, lib, ...}:
+
+with lib;
+
+let
+ cfg = config.services.hologram-agent;
+
+ cfgFile = pkgs.writeText "hologram-agent.json" (builtins.toJSON {
+ host = cfg.dialAddress;
+ });
+in {
+ options = {
+ services.hologram-agent = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Hologram agent for AWS instance credentials";
+ };
+
+ dialAddress = mkOption {
+ type = types.str;
+ default = "localhost:3100";
+ description = "Hologram server and port.";
+ };
+
+ httpPort = mkOption {
+ type = types.str;
+ default = "80";
+ description = "Port for metadata service to listen on.";
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+ boot.kernelModules = [ "dummy" ];
+
+ networking.interfaces.dummy0.ipv4.addresses = [
+ { address = "169.254.169.254"; prefixLength = 32; }
+ ];
+
+ systemd.services.hologram-agent = {
+ description = "Provide EC2 instance credentials to machines outside of EC2";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "network-link-dummy0.service" "network-addresses-dummy0.service" ];
+ preStart = ''
+ /run/current-system/sw/bin/rm -fv /run/hologram.sock
+ '';
+ serviceConfig = {
+ ExecStart = "${pkgs.hologram.bin}/bin/hologram-agent -debug -conf ${cfgFile} -port ${cfg.httpPort}";
+ };
+ };
+
+ };
+
+ meta.maintainers = with lib.maintainers; [ nand0p ];
+}
diff --git a/nixpkgs/nixos/modules/services/security/hologram-server.nix b/nixpkgs/nixos/modules/services/security/hologram-server.nix
new file mode 100644
index 00000000000..bad02c7440b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/hologram-server.nix
@@ -0,0 +1,130 @@
+{pkgs, config, lib, ...}:
+
+with lib;
+
+let
+ cfg = config.services.hologram-server;
+
+ cfgFile = pkgs.writeText "hologram-server.json" (builtins.toJSON {
+ ldap = {
+ host = cfg.ldapHost;
+ bind = {
+ dn = cfg.ldapBindDN;
+ password = cfg.ldapBindPassword;
+ };
+ insecureldap = cfg.ldapInsecure;
+ userattr = cfg.ldapUserAttr;
+ baseDN = cfg.ldapBaseDN;
+ enableldapRoles = cfg.enableLdapRoles;
+ roleAttr = cfg.roleAttr;
+ groupClassAttr = cfg.groupClassAttr;
+ };
+ aws = {
+ account = cfg.awsAccount;
+ defaultrole = cfg.awsDefaultRole;
+ };
+ stats = cfg.statsAddress;
+ listen = cfg.listenAddress;
+ cachetimeout = cfg.cacheTimeoutSeconds;
+ });
+in {
+ options = {
+ services.hologram-server = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Hologram server for AWS instance credentials";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0:3100";
+ description = "Address and port to listen on";
+ };
+
+ ldapHost = mkOption {
+ type = types.str;
+ description = "Address of the LDAP server to use";
+ };
+
+ ldapInsecure = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to connect to LDAP over SSL or not";
+ };
+
+ ldapUserAttr = mkOption {
+ type = types.str;
+ default = "cn";
+ description = "The LDAP attribute for usernames";
+ };
+
+ ldapBaseDN = mkOption {
+ type = types.str;
+ description = "The base DN for your Hologram users";
+ };
+
+ ldapBindDN = mkOption {
+ type = types.str;
+ description = "DN of account to use to query the LDAP server";
+ };
+
+ ldapBindPassword = mkOption {
+ type = types.str;
+ description = "Password of account to use to query the LDAP server";
+ };
+
+ enableLdapRoles = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to assign user roles based on the user's LDAP group memberships";
+ };
+
+ groupClassAttr = mkOption {
+ type = types.str;
+ default = "groupOfNames";
+ description = "The objectclass attribute to search for groups when enableLdapRoles is true";
+ };
+
+ roleAttr = mkOption {
+ type = types.str;
+ default = "businessCategory";
+ description = "Which LDAP group attribute to search for authorized role ARNs";
+ };
+
+ awsAccount = mkOption {
+ type = types.str;
+ description = "AWS account number";
+ };
+
+ awsDefaultRole = mkOption {
+ type = types.str;
+ description = "AWS default role";
+ };
+
+ statsAddress = mkOption {
+ type = types.str;
+ default = "";
+ description = "Address of statsd server";
+ };
+
+ cacheTimeoutSeconds = mkOption {
+ type = types.int;
+ default = 3600;
+ description = "How often (in seconds) to refresh the LDAP cache";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.hologram-server = {
+ description = "Provide EC2 instance credentials to machines outside of EC2";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.hologram.bin}/bin/hologram-server --debug --conf ${cfgFile}";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/munge.nix b/nixpkgs/nixos/modules/services/security/munge.nix
new file mode 100644
index 00000000000..89178886471
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/munge.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.munge;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.munge = {
+ enable = mkEnableOption "munge service";
+
+ password = mkOption {
+ default = "/etc/munge/munge.key";
+ type = types.path;
+ description = ''
+ The path to a daemon's secret key.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.munge ];
+
+ users.users.munge = {
+ description = "Munge daemon user";
+ isSystemUser = true;
+ group = "munge";
+ };
+
+ users.groups.munge = {};
+
+ systemd.services.munged = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ path = [ pkgs.munge pkgs.coreutils ];
+
+ serviceConfig = {
+ ExecStartPre = "+${pkgs.coreutils}/bin/chmod 0400 ${cfg.password}";
+ ExecStart = "${pkgs.munge}/bin/munged --syslog --key-file ${cfg.password}";
+ PIDFile = "/run/munge/munged.pid";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ User = "munge";
+ Group = "munge";
+ StateDirectory = "munge";
+ StateDirectoryMode = "0711";
+ RuntimeDirectory = "munge";
+ };
+
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/nginx-sso.nix b/nixpkgs/nixos/modules/services/security/nginx-sso.nix
new file mode 100644
index 00000000000..d792f90abe6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/nginx-sso.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nginx.sso;
+ pkg = getBin pkgs.nginx-sso;
+ configYml = pkgs.writeText "nginx-sso.yml" (builtins.toJSON cfg.configuration);
+in {
+ options.services.nginx.sso = {
+ enable = mkEnableOption "nginx-sso service";
+
+ configuration = mkOption {
+ type = types.attrsOf types.unspecified;
+ default = {};
+ example = literalExample ''
+ {
+ listen = { addr = "127.0.0.1"; port = 8080; };
+
+ providers.token.tokens = {
+ myuser = "MyToken";
+ };
+
+ acl = {
+ rule_sets = [
+ {
+ rules = [ { field = "x-application"; equals = "MyApp"; } ];
+ allow = [ "myuser" ];
+ }
+ ];
+ };
+ }
+ '';
+ description = ''
+ nginx-sso configuration
+ (<link xlink:href="https://github.com/Luzifer/nginx-sso/wiki/Main-Configuration">documentation</link>)
+ as a Nix attribute set.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.nginx-sso = {
+ description = "Nginx SSO Backend";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${pkg}/bin/nginx-sso \
+ --config ${configYml} \
+ --frontend-dir ${pkg}/share/frontend
+ '';
+ Restart = "always";
+ DynamicUser = true;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix b/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix
new file mode 100644
index 00000000000..bb03f7fc9e4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix
@@ -0,0 +1,566 @@
+# NixOS module for oauth2_proxy.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.oauth2_proxy;
+
+ # oauth2_proxy provides many options that are only relevant if you are using
+ # a certain provider. This set maps from provider name to a function that
+ # takes the configuration and returns a string that can be inserted into the
+ # command-line to launch oauth2_proxy.
+ providerSpecificOptions = {
+ azure = cfg: {
+ azure.tenant = cfg.azure.tenant;
+ resource = cfg.azure.resource;
+ };
+
+ github = cfg: { github = {
+ inherit (cfg.github) org team;
+ }; };
+
+ google = cfg: { google = with cfg.google; optionalAttrs (groups != []) {
+ admin-email = adminEmail;
+ service-account = serviceAccountJSON;
+ group = groups;
+ }; };
+ };
+
+ authenticatedEmailsFile = pkgs.writeText "authenticated-emails" cfg.email.addresses;
+
+ getProviderOptions = cfg: provider: providerSpecificOptions.${provider} or (_: {}) cfg;
+
+ allConfig = with cfg; {
+ inherit (cfg) provider scope upstream;
+ approval-prompt = approvalPrompt;
+ basic-auth-password = basicAuthPassword;
+ client-id = clientID;
+ client-secret = clientSecret;
+ custom-templates-dir = customTemplatesDir;
+ email-domain = email.domains;
+ http-address = httpAddress;
+ login-url = loginURL;
+ pass-access-token = passAccessToken;
+ pass-basic-auth = passBasicAuth;
+ pass-host-header = passHostHeader;
+ proxy-prefix = proxyPrefix;
+ profile-url = profileURL;
+ redeem-url = redeemURL;
+ redirect-url = redirectURL;
+ request-logging = requestLogging;
+ skip-auth-regex = skipAuthRegexes;
+ signature-key = signatureKey;
+ validate-url = validateURL;
+ htpasswd-file = htpasswd.file;
+ cookie = {
+ inherit (cookie) domain secure expire name secret refresh;
+ httponly = cookie.httpOnly;
+ };
+ set-xauthrequest = setXauthrequest;
+ } // lib.optionalAttrs (cfg.email.addresses != null) {
+ authenticated-emails-file = authenticatedEmailsFile;
+ } // lib.optionalAttrs (cfg.passBasicAuth) {
+ basic-auth-password = cfg.basicAuthPassword;
+ } // lib.optionalAttrs (cfg.htpasswd.file != null) {
+ display-htpasswd-file = cfg.htpasswd.displayForm;
+ } // lib.optionalAttrs tls.enable {
+ tls-cert = tls.certificate;
+ tls-key = tls.key;
+ https-address = tls.httpsAddress;
+ } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig;
+
+ mapConfig = key: attr:
+ if attr != null && attr != [] then (
+ if isDerivation attr then mapConfig key (toString attr) else
+ if (builtins.typeOf attr) == "set" then concatStringsSep " "
+ (mapAttrsToList (name: value: mapConfig (key + "-" + name) value) attr) else
+ if (builtins.typeOf attr) == "list" then concatMapStringsSep " " (mapConfig key) attr else
+ if (builtins.typeOf attr) == "bool" then "--${key}=${boolToString attr}" else
+ if (builtins.typeOf attr) == "string" then "--${key}='${attr}'" else
+ "--${key}=${toString attr}")
+ else "";
+
+ configString = concatStringsSep " " (mapAttrsToList mapConfig allConfig);
+in
+{
+ options.services.oauth2_proxy = {
+ enable = mkEnableOption "oauth2_proxy";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.oauth2_proxy;
+ defaultText = "pkgs.oauth2_proxy";
+ description = ''
+ The package that provides oauth2_proxy.
+ '';
+ };
+
+ ##############################################
+ # PROVIDER configuration
+ provider = mkOption {
+ type = types.enum [
+ "google"
+ "github"
+ "azure"
+ "gitlab"
+ "linkedin"
+ "myusa"
+ ];
+ default = "google";
+ description = ''
+ OAuth provider.
+ '';
+ };
+
+ approvalPrompt = mkOption {
+ type = types.enum ["force" "auto"];
+ default = "force";
+ description = ''
+ OAuth approval_prompt.
+ '';
+ };
+
+ clientID = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ The OAuth Client ID.
+ '';
+ example = "123456.apps.googleusercontent.com";
+ };
+
+ clientSecret = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ The OAuth Client Secret.
+ '';
+ };
+
+ skipAuthRegexes = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Skip authentication for requests matching any of these regular
+ expressions.
+ '';
+ };
+
+ # XXX: Not clear whether these two options are mutually exclusive or not.
+ email = {
+ domains = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Authenticate emails with the specified domains. Use
+ <literal>*</literal> to authenticate any email.
+ '';
+ };
+
+ addresses = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Line-separated email addresses that are allowed to authenticate.
+ '';
+ };
+ };
+
+ loginURL = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Authentication endpoint.
+
+ You only need to set this if you are using a self-hosted provider (e.g.
+ Github Enterprise). If you're using a publicly hosted provider
+ (e.g github.com), then the default works.
+ '';
+ example = "https://provider.example.com/oauth/authorize";
+ };
+
+ redeemURL = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Token redemption endpoint.
+
+ You only need to set this if you are using a self-hosted provider (e.g.
+ Github Enterprise). If you're using a publicly hosted provider
+ (e.g github.com), then the default works.
+ '';
+ example = "https://provider.example.com/oauth/token";
+ };
+
+ validateURL = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Access token validation endpoint.
+
+ You only need to set this if you are using a self-hosted provider (e.g.
+ Github Enterprise). If you're using a publicly hosted provider
+ (e.g github.com), then the default works.
+ '';
+ example = "https://provider.example.com/user/emails";
+ };
+
+ redirectURL = mkOption {
+ # XXX: jml suspects this is always necessary, but the command-line
+ # doesn't require it so making it optional.
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The OAuth2 redirect URL.
+ '';
+ example = "https://internalapp.yourcompany.com/oauth2/callback";
+ };
+
+ azure = {
+ tenant = mkOption {
+ type = types.str;
+ default = "common";
+ description = ''
+ Go to a tenant-specific or common (tenant-independent) endpoint.
+ '';
+ };
+
+ resource = mkOption {
+ type = types.str;
+ description = ''
+ The resource that is protected.
+ '';
+ };
+ };
+
+ google = {
+ adminEmail = mkOption {
+ type = types.str;
+ description = ''
+ The Google Admin to impersonate for API calls.
+
+ Only users with access to the Admin APIs can access the Admin SDK
+ Directory API, thus the service account needs to impersonate one of
+ those users to access the Admin SDK Directory API.
+
+ See <link xlink:href="https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account" />.
+ '';
+ };
+
+ groups = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Restrict logins to members of these Google groups.
+ '';
+ };
+
+ serviceAccountJSON = mkOption {
+ type = types.path;
+ description = ''
+ The path to the service account JSON credentials.
+ '';
+ };
+ };
+
+ github = {
+ org = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Restrict logins to members of this organisation.
+ '';
+ };
+
+ team = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Restrict logins to members of this team.
+ '';
+ };
+ };
+
+
+ ####################################################
+ # UPSTREAM Configuration
+ upstream = mkOption {
+ type = with types; coercedTo str (x: [x]) (listOf str);
+ default = [];
+ description = ''
+ The http url(s) of the upstream endpoint or <literal>file://</literal>
+ paths for static files. Routing is based on the path.
+ '';
+ };
+
+ passAccessToken = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Pass OAuth access_token to upstream via X-Forwarded-Access-Token header.
+ '';
+ };
+
+ passBasicAuth = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream.
+ '';
+ };
+
+ basicAuthPassword = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The password to set when passing the HTTP Basic Auth header.
+ '';
+ };
+
+ passHostHeader = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Pass the request Host Header to upstream.
+ '';
+ };
+
+ signatureKey = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ GAP-Signature request signature key.
+ '';
+ example = "sha1:secret0";
+ };
+
+ cookie = {
+ domain = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ An optional cookie domain to force cookies to.
+ '';
+ example = ".yourcompany.com";
+ };
+
+ expire = mkOption {
+ type = types.str;
+ default = "168h0m0s";
+ description = ''
+ Expire timeframe for cookie.
+ '';
+ };
+
+ httpOnly = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Set HttpOnly cookie flag.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "_oauth2_proxy";
+ description = ''
+ The name of the cookie that the oauth_proxy creates.
+ '';
+ };
+
+ refresh = mkOption {
+ # XXX: Unclear what the behavior is when this is not specified.
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Refresh the cookie after this duration; 0 to disable.
+ '';
+ example = "168h0m0s";
+ };
+
+ secret = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ The seed string for secure cookies.
+ '';
+ };
+
+ secure = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Set secure (HTTPS) cookie flag.
+ '';
+ };
+ };
+
+ ####################################################
+ # OAUTH2 PROXY configuration
+
+ httpAddress = mkOption {
+ type = types.str;
+ default = "http://127.0.0.1:4180";
+ description = ''
+ HTTPS listening address. This module does not expose the port by
+ default. If you want this URL to be accessible to other machines, please
+ add the port to <literal>networking.firewall.allowedTCPPorts</literal>.
+ '';
+ };
+
+ htpasswd = {
+ file = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Additionally authenticate against a htpasswd file. Entries must be
+ created with <literal>htpasswd -s</literal> for SHA encryption.
+ '';
+ };
+
+ displayForm = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Display username / password login form if an htpasswd file is provided.
+ '';
+ };
+ };
+
+ customTemplatesDir = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Path to custom HTML templates.
+ '';
+ };
+
+ proxyPrefix = mkOption {
+ type = types.str;
+ default = "/oauth2";
+ description = ''
+ The url root path that this proxy should be nested under.
+ '';
+ };
+
+ tls = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to serve over TLS.
+ '';
+ };
+
+ certificate = mkOption {
+ type = types.path;
+ description = ''
+ Path to certificate file.
+ '';
+ };
+
+ key = mkOption {
+ type = types.path;
+ description = ''
+ Path to private key file.
+ '';
+ };
+
+ httpsAddress = mkOption {
+ type = types.str;
+ default = ":443";
+ description = ''
+ <literal>addr:port</literal> to listen on for HTTPS clients.
+
+ Remember to add <literal>port</literal> to
+ <literal>allowedTCPPorts</literal> if you want other machines to be
+ able to connect to it.
+ '';
+ };
+ };
+
+ requestLogging = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Log requests to stdout.
+ '';
+ };
+
+ ####################################################
+ # UNKNOWN
+
+ # XXX: Is this mandatory? Is it part of another group? Is it part of the provider specification?
+ scope = mkOption {
+ # XXX: jml suspects this is always necessary, but the command-line
+ # doesn't require it so making it optional.
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ OAuth scope specification.
+ '';
+ };
+
+ profileURL = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Profile access endpoint.
+ '';
+ };
+
+ setXauthrequest = mkOption {
+ type = types.nullOr types.bool;
+ default = false;
+ description = ''
+ Set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode). Setting this to 'null' means using the upstream default (false).
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = {};
+ description = ''
+ Extra config to pass to oauth2_proxy.
+ '';
+ };
+
+ keyFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ oauth2_proxy allows passing sensitive configuration via environment variables.
+ Make a file that contains lines like
+ OAUTH2_PROXY_CLIENT_SECRET=asdfasdfasdf.apps.googleuserscontent.com
+ and specify the path here.
+ '';
+ example = "/run/keys/oauth2_proxy";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ services.oauth2_proxy = mkIf (cfg.keyFile != null) {
+ clientID = mkDefault null;
+ clientSecret = mkDefault null;
+ cookie.secret = mkDefault null;
+ };
+
+ users.users.oauth2_proxy = {
+ description = "OAuth2 Proxy";
+ };
+
+ systemd.services.oauth2_proxy = {
+ description = "OAuth2 Proxy";
+ path = [ cfg.package ];
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ User = "oauth2_proxy";
+ Restart = "always";
+ ExecStart = "${cfg.package.bin}/bin/oauth2_proxy ${configString}";
+ EnvironmentFile = mkIf (cfg.keyFile != null) cfg.keyFile;
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix b/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix
new file mode 100644
index 00000000000..be6734f439f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix
@@ -0,0 +1,64 @@
+{ config, lib, ... }:
+with lib;
+let
+ cfg = config.services.oauth2_proxy.nginx;
+in
+{
+ options.services.oauth2_proxy.nginx = {
+ proxy = mkOption {
+ type = types.str;
+ default = config.services.oauth2_proxy.httpAddress;
+ description = ''
+ The address of the reverse proxy endpoint for oauth2_proxy
+ '';
+ };
+ virtualHosts = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ A list of nginx virtual hosts to put behind the oauth2 proxy
+ '';
+ };
+ };
+ config.services.oauth2_proxy = mkIf (cfg.virtualHosts != [] && (hasPrefix "127.0.0.1:" cfg.proxy)) {
+ enable = true;
+ };
+ config.services.nginx = mkMerge ((optional (cfg.virtualHosts != []) {
+ recommendedProxySettings = true; # needed because duplicate headers
+ }) ++ (map (vhost: {
+ virtualHosts.${vhost} = {
+ locations."/oauth2/" = {
+ proxyPass = cfg.proxy;
+ extraConfig = ''
+ proxy_set_header X-Scheme $scheme;
+ proxy_set_header X-Auth-Request-Redirect $request_uri;
+ '';
+ };
+ locations."/oauth2/auth" = {
+ proxyPass = cfg.proxy;
+ extraConfig = ''
+ proxy_set_header X-Scheme $scheme;
+ # nginx auth_request includes headers but not body
+ proxy_set_header Content-Length "";
+ proxy_pass_request_body off;
+ '';
+ };
+ locations."/".extraConfig = ''
+ auth_request /oauth2/auth;
+ error_page 401 = /oauth2/sign_in;
+
+ # pass information via X-User and X-Email headers to backend,
+ # requires running with --set-xauthrequest flag
+ auth_request_set $user $upstream_http_x_auth_request_user;
+ auth_request_set $email $upstream_http_x_auth_request_email;
+ proxy_set_header X-User $user;
+ proxy_set_header X-Email $email;
+
+ # if you enabled --cookie-refresh, this is needed for it to work with auth_request
+ auth_request_set $auth_cookie $upstream_http_set_cookie;
+ add_header Set-Cookie $auth_cookie;
+ '';
+
+ };
+ }) cfg.virtualHosts));
+}
diff --git a/nixpkgs/nixos/modules/services/security/physlock.nix b/nixpkgs/nixos/modules/services/security/physlock.nix
new file mode 100644
index 00000000000..61bcd84f2e6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/physlock.nix
@@ -0,0 +1,128 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.physlock;
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.physlock = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the <command>physlock</command> screen locking mechanism.
+
+ Enable this and then run <command>systemctl start physlock</command>
+ to securely lock the screen.
+
+ This will switch to a new virtual terminal, turn off console
+ switching and disable SysRq mechanism (when
+ <option>services.physlock.disableSysRq</option> is set)
+ until the root or user password is given.
+ '';
+ };
+
+ allowAnyUser = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to allow any user to lock the screen. This will install a
+ setuid wrapper to allow any user to start physlock as root, which
+ is a minor security risk. Call the physlock binary to use this instead
+ of using the systemd service.
+
+ Note that you might need to relog to have the correct binary in your
+ PATH upon changing this option.
+ '';
+ };
+
+ disableSysRq = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to disable SysRq when locked with physlock.
+ '';
+ };
+
+ lockOn = {
+
+ suspend = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to lock screen with physlock just before suspend.
+ '';
+ };
+
+ hibernate = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to lock screen with physlock just before hibernate.
+ '';
+ };
+
+ extraTargets = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "display-manager.service" ];
+ description = ''
+ Other targets to lock the screen just before.
+
+ Useful if you want to e.g. both autologin to X11 so that
+ your <filename>~/.xsession</filename> gets executed and
+ still to have the screen locked so that the system can be
+ booted relatively unattended.
+ '';
+ };
+
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+
+ # for physlock -l and physlock -L
+ environment.systemPackages = [ pkgs.physlock ];
+
+ systemd.services.physlock = {
+ enable = true;
+ description = "Physlock";
+ wantedBy = optional cfg.lockOn.suspend "suspend.target"
+ ++ optional cfg.lockOn.hibernate "hibernate.target"
+ ++ cfg.lockOn.extraTargets;
+ before = optional cfg.lockOn.suspend "systemd-suspend.service"
+ ++ optional cfg.lockOn.hibernate "systemd-hibernate.service"
+ ++ cfg.lockOn.extraTargets;
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.physlock}/bin/physlock -d${optionalString cfg.disableSysRq "s"}";
+ };
+ };
+
+ security.pam.services.physlock = {};
+
+ }
+
+ (mkIf cfg.allowAnyUser {
+
+ security.wrappers.physlock = { source = "${pkgs.physlock}/bin/physlock"; user = "root"; };
+
+ })
+ ]);
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix b/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix
new file mode 100644
index 00000000000..5908f727d53
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix
@@ -0,0 +1,75 @@
+{pkgs, config, lib, ...}:
+
+with lib;
+let
+ cfg = config.services.shibboleth-sp;
+in {
+ options = {
+ services.shibboleth-sp = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the shibboleth service";
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ example = "${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml";
+ description = "Path to shibboleth config file";
+ };
+
+ fastcgi.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to include the shibauthorizer and shibresponder FastCGI processes";
+ };
+
+ fastcgi.shibAuthorizerPort = mkOption {
+ type = types.int;
+ default = 9100;
+ description = "Port for shibauthorizer FastCGI proccess to bind to";
+ };
+
+ fastcgi.shibResponderPort = mkOption {
+ type = types.int;
+ default = 9101;
+ description = "Port for shibauthorizer FastCGI proccess to bind to";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.shibboleth-sp = {
+ description = "Provides SSO and federation for web applications";
+ after = lib.optionals cfg.fastcgi.enable [ "shibresponder.service" "shibauthorizer.service" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.shibboleth-sp}/bin/shibd -F -d ${pkgs.shibboleth-sp} -c ${cfg.configFile}";
+ };
+ };
+
+ systemd.services.shibresponder = mkIf cfg.fastcgi.enable {
+ description = "Provides SSO through Shibboleth via FastCGI";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ "${pkgs.spawn_fcgi}" ];
+ environment.SHIBSP_CONFIG = "${cfg.configFile}";
+ serviceConfig = {
+ ExecStart = "${pkgs.spawn_fcgi}/bin/spawn-fcgi -n -p ${toString cfg.fastcgi.shibResponderPort} ${pkgs.shibboleth-sp}/lib/shibboleth/shibresponder";
+ };
+ };
+
+ systemd.services.shibauthorizer = mkIf cfg.fastcgi.enable {
+ description = "Provides SSO through Shibboleth via FastCGI";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ "${pkgs.spawn_fcgi}" ];
+ environment.SHIBSP_CONFIG = "${cfg.configFile}";
+ serviceConfig = {
+ ExecStart = "${pkgs.spawn_fcgi}/bin/spawn-fcgi -n -p ${toString cfg.fastcgi.shibAuthorizerPort} ${pkgs.shibboleth-sp}/lib/shibboleth/shibauthorizer";
+ };
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ jammerful ];
+}
diff --git a/nixpkgs/nixos/modules/services/security/sks.nix b/nixpkgs/nixos/modules/services/security/sks.nix
new file mode 100644
index 00000000000..a91060dc659
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/sks.nix
@@ -0,0 +1,146 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.sks;
+ sksPkg = cfg.package;
+ dbConfig = pkgs.writeText "DB_CONFIG" ''
+ ${cfg.extraDbConfig}
+ '';
+
+in {
+ meta.maintainers = with maintainers; [ primeos calbrecht jcumming ];
+
+ options = {
+
+ services.sks = {
+
+ enable = mkEnableOption ''
+ SKS (synchronizing key server for OpenPGP) and start the database
+ server. You need to create "''${dataDir}/dump/*.gpg" for the initial
+ import'';
+
+ package = mkOption {
+ default = pkgs.sks;
+ defaultText = "pkgs.sks";
+ type = types.package;
+ description = "Which SKS derivation to use.";
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/db/sks";
+ example = "/var/lib/sks";
+ # TODO: The default might change to "/var/lib/sks" as this is more
+ # common. There's also https://github.com/NixOS/nixpkgs/issues/26256
+ # and "/var/db" is not FHS compliant (seems to come from BSD).
+ description = ''
+ Data directory (-basedir) for SKS, where the database and all
+ configuration files are located (e.g. KDB, PTree, membership and
+ sksconf).
+ '';
+ };
+
+ extraDbConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Set contents of the files "KDB/DB_CONFIG" and "PTree/DB_CONFIG" within
+ the ''${dataDir} directory. This is used to configure options for the
+ database for the sks key server.
+
+ Documentation of available options are available in the file named
+ "sampleConfig/DB_CONFIG" in the following repository:
+ https://bitbucket.org/skskeyserver/sks-keyserver/src
+ '';
+ };
+
+ hkpAddress = mkOption {
+ default = [ "127.0.0.1" "::1" ];
+ type = types.listOf types.str;
+ description = ''
+ Domain names, IPv4 and/or IPv6 addresses to listen on for HKP
+ requests.
+ '';
+ };
+
+ hkpPort = mkOption {
+ default = 11371;
+ type = types.ints.u16;
+ description = "HKP port to listen on.";
+ };
+
+ webroot = mkOption {
+ type = types.nullOr types.path;
+ default = "${sksPkg.webSamples}/OpenPKG";
+ defaultText = "\${pkgs.sks.webSamples}/OpenPKG";
+ description = ''
+ Source directory (will be symlinked, if not null) for the files the
+ built-in webserver should serve. SKS (''${pkgs.sks.webSamples})
+ provides the following examples: "HTML5", "OpenPKG", and "XHTML+ES".
+ The index file can be named index.html, index.htm, index.xhtm, or
+ index.xhtml. Files with the extensions .css, .es, .js, .jpg, .jpeg,
+ .png, or .gif are supported. Subdirectories and filenames with
+ anything other than alphanumeric characters and the '.' character
+ will be ignored.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ users = {
+ users.sks = {
+ isSystemUser = true;
+ description = "SKS user";
+ home = cfg.dataDir;
+ createHome = true;
+ group = "sks";
+ useDefaultShell = true;
+ packages = [ sksPkg pkgs.db ];
+ };
+ groups.sks = { };
+ };
+
+ systemd.services = let
+ hkpAddress = "'" + (builtins.concatStringsSep " " cfg.hkpAddress) + "'" ;
+ hkpPort = builtins.toString cfg.hkpPort;
+ in {
+ sks-db = {
+ description = "SKS database server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ ${lib.optionalString (cfg.webroot != null)
+ "ln -sfT \"${cfg.webroot}\" web"}
+ mkdir -p dump
+ ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/
+ ${sksPkg}/bin/sks cleandb || true
+ ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
+ # Check that both database configs are symlinks before overwriting them
+ # TODO: The initial build will be without DB_CONFIG, but this will
+ # hopefully not cause any significant problems. It might be better to
+ # create both directories manually but we have to check that this does
+ # not affect the initial build of the DB.
+ for CONFIG_FILE in KDB/DB_CONFIG PTree/DB_CONFIG; do
+ if [ -e $CONFIG_FILE ] && [ ! -L $CONFIG_FILE ]; then
+ echo "$CONFIG_FILE exists but is not a symlink." >&2
+ echo "Please remove $PWD/$CONFIG_FILE manually to continue." >&2
+ exit 1
+ fi
+ ln -sf ${dbConfig} $CONFIG_FILE
+ done
+ '';
+ serviceConfig = {
+ WorkingDirectory = "~";
+ User = "sks";
+ Group = "sks";
+ Restart = "always";
+ ExecStart = "${sksPkg}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}";
+ };
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/sshguard.nix b/nixpkgs/nixos/modules/services/security/sshguard.nix
new file mode 100644
index 00000000000..4a174564dd2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/sshguard.nix
@@ -0,0 +1,150 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.sshguard;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.sshguard = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to enable the sshguard service.";
+ };
+
+ attack_threshold = mkOption {
+ default = 30;
+ type = types.int;
+ description = ''
+ Block attackers when their cumulative attack score exceeds threshold. Most attacks have a score of 10.
+ '';
+ };
+
+ blacklist_threshold = mkOption {
+ default = null;
+ example = 120;
+ type = types.nullOr types.int;
+ description = ''
+ Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
+ '';
+ };
+
+ blacklist_file = mkOption {
+ default = "/var/lib/sshguard/blacklist.db";
+ type = types.path;
+ description = ''
+ Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
+ '';
+ };
+
+ blocktime = mkOption {
+ default = 120;
+ type = types.int;
+ description = ''
+ Block attackers for initially blocktime seconds after exceeding threshold. Subsequent blocks increase by a factor of 1.5.
+
+ sshguard unblocks attacks at random intervals, so actual block times will be longer.
+ '';
+ };
+
+ detection_time = mkOption {
+ default = 1800;
+ type = types.int;
+ description = ''
+ Remember potential attackers for up to detection_time seconds before resetting their score.
+ '';
+ };
+
+ whitelist = mkOption {
+ default = [ ];
+ example = [ "198.51.100.56" "198.51.100.2" ];
+ type = types.listOf types.str;
+ description = ''
+ Whitelist a list of addresses, hostnames, or address blocks.
+ '';
+ };
+
+ services = mkOption {
+ default = [ "sshd" ];
+ example = [ "sshd" "exim" ];
+ type = types.listOf types.str;
+ description = ''
+ Systemd services sshguard should receive logs of.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.etc."sshguard.conf".text = let
+ args = lib.concatStringsSep " " ([
+ "-afb"
+ "-p info"
+ "-o cat"
+ "-n1"
+ ] ++ (map (name: "-t ${escapeShellArg name}") cfg.services));
+ 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 ];
+
+ # The sshguard ipsets must exist before we invoke
+ # iptables. sshguard creates the ipsets after startup if
+ # necessary, but if we let sshguard do it, we can't reliably add
+ # the iptables rules because postStart races with the creation
+ # of the ipsets. So instead, we create both the ipsets and
+ # firewall rules before sshguard starts.
+ preStart = ''
+ ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:net family inet
+ ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:net family inet6
+ ${pkgs.iptables}/bin/iptables -I INPUT -m set --match-set sshguard4 src -j DROP
+ ${pkgs.iptables}/bin/ip6tables -I INPUT -m set --match-set sshguard6 src -j DROP
+ '';
+
+ postStop = ''
+ ${pkgs.iptables}/bin/iptables -D INPUT -m set --match-set sshguard4 src -j DROP
+ ${pkgs.iptables}/bin/ip6tables -D INPUT -m set --match-set sshguard6 src -j DROP
+ ${pkgs.ipset}/bin/ipset -quiet destroy sshguard4
+ ${pkgs.ipset}/bin/ipset -quiet destroy sshguard6
+ '';
+
+ unitConfig.Documentation = "man:sshguard(8)";
+
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = let
+ args = lib.concatStringsSep " " ([
+ "-a ${toString cfg.attack_threshold}"
+ "-p ${toString cfg.blocktime}"
+ "-s ${toString cfg.detection_time}"
+ (optionalString (cfg.blacklist_threshold != null) "-b ${toString cfg.blacklist_threshold}:${cfg.blacklist_file}")
+ ] ++ (map (name: "-w ${escapeShellArg name}") cfg.whitelist));
+ in "${pkgs.sshguard}/bin/sshguard ${args}";
+ Restart = "always";
+ ProtectSystem = "strict";
+ ProtectHome = "tmpfs";
+ RuntimeDirectory = "sshguard";
+ StateDirectory = "sshguard";
+ CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/tor.nix b/nixpkgs/nixos/modules/services/security/tor.nix
new file mode 100644
index 00000000000..ed862387cce
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/tor.nix
@@ -0,0 +1,783 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.tor;
+ torDirectory = "/var/lib/tor";
+ torRunDirectory = "/run/tor";
+
+ opt = name: value: optionalString (value != null) "${name} ${value}";
+ optint = name: value: optionalString (value != null && value != 0) "${name} ${toString value}";
+
+ isolationOptions = {
+ type = types.listOf (types.enum [
+ "IsolateClientAddr"
+ "IsolateSOCKSAuth"
+ "IsolateClientProtocol"
+ "IsolateDestPort"
+ "IsolateDestAddr"
+ ]);
+ default = [];
+ example = [
+ "IsolateClientAddr"
+ "IsolateSOCKSAuth"
+ "IsolateClientProtocol"
+ "IsolateDestPort"
+ "IsolateDestAddr"
+ ];
+ description = "Tor isolation options";
+ };
+
+
+ torRc = ''
+ User tor
+ DataDirectory ${torDirectory}
+ ${optionalString cfg.enableGeoIP ''
+ GeoIPFile ${pkgs.tor.geoip}/share/tor/geoip
+ GeoIPv6File ${pkgs.tor.geoip}/share/tor/geoip6
+ ''}
+
+ ${optint "ControlPort" cfg.controlPort}
+ ${optionalString cfg.controlSocket.enable "ControlPort unix:${torRunDirectory}/control GroupWritable RelaxDirModeCheck"}
+ ''
+ # Client connection config
+ + optionalString cfg.client.enable ''
+ SOCKSPort ${cfg.client.socksListenAddress} ${toString cfg.client.socksIsolationOptions}
+ SOCKSPort ${cfg.client.socksListenAddressFaster}
+ ${opt "SocksPolicy" cfg.client.socksPolicy}
+
+ ${optionalString cfg.client.transparentProxy.enable ''
+ TransPort ${cfg.client.transparentProxy.listenAddress} ${toString cfg.client.transparentProxy.isolationOptions}
+ ''}
+
+ ${optionalString cfg.client.dns.enable ''
+ DNSPort ${cfg.client.dns.listenAddress} ${toString cfg.client.dns.isolationOptions}
+ AutomapHostsOnResolve 1
+ AutomapHostsSuffixes ${concatStringsSep "," cfg.client.dns.automapHostsSuffixes}
+ ''}
+ ''
+ # Explicitly disable the SOCKS server if the client is disabled. In
+ # particular, this makes non-anonymous hidden services possible.
+ + optionalString (! cfg.client.enable) ''
+ SOCKSPort 0
+ ''
+ # Relay config
+ + optionalString cfg.relay.enable ''
+ ORPort ${toString cfg.relay.port}
+ ${opt "Address" cfg.relay.address}
+ ${opt "Nickname" cfg.relay.nickname}
+ ${opt "ContactInfo" cfg.relay.contactInfo}
+
+ ${optint "RelayBandwidthRate" cfg.relay.bandwidthRate}
+ ${optint "RelayBandwidthBurst" cfg.relay.bandwidthBurst}
+ ${opt "AccountingMax" cfg.relay.accountingMax}
+ ${opt "AccountingStart" cfg.relay.accountingStart}
+
+ ${if (cfg.relay.role == "exit") then
+ opt "ExitPolicy" cfg.relay.exitPolicy
+ else
+ "ExitPolicy reject *:*"}
+
+ ${optionalString (elem cfg.relay.role ["bridge" "private-bridge"]) ''
+ BridgeRelay 1
+ ServerTransportPlugin ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${pkgs.obfs4}/bin/obfs4proxy managed
+ ExtORPort auto
+ ${optionalString (cfg.relay.role == "private-bridge") ''
+ ExtraInfoStatistics 0
+ PublishServerDescriptor 0
+ ''}
+ ''}
+ ''
+ # Hidden services
+ + concatStrings (flip mapAttrsToList cfg.hiddenServices (n: v: ''
+ HiddenServiceDir ${torDirectory}/onion/${v.name}
+ ${optionalString (v.version != null) "HiddenServiceVersion ${toString v.version}"}
+ ${flip concatMapStrings v.map (p: ''
+ HiddenServicePort ${toString p.port} ${p.destination}
+ '')}
+ ${optionalString (v.authorizeClient != null) ''
+ HiddenServiceAuthorizeClient ${v.authorizeClient.authType} ${concatStringsSep "," v.authorizeClient.clientNames}
+ ''}
+ ''))
+ + cfg.extraConfig;
+
+ torRcFile = pkgs.writeText "torrc" torRc;
+
+in
+{
+ options = {
+ services.tor = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the Tor daemon. By default, the daemon is run without
+ relay, exit, bridge or client connectivity.
+ '';
+ };
+
+ enableGeoIP = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whenever to configure Tor daemon to use GeoIP databases.
+
+ Disabling this will disable by-country statistics for
+ bridges and relays and some client and third-party software
+ functionality.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration. Contents will be added verbatim to the
+ configuration file at the end.
+ '';
+ };
+
+ controlPort = mkOption {
+ type = types.nullOr (types.either types.int types.str);
+ default = null;
+ example = 9051;
+ description = ''
+ If set, Tor will accept connections on the specified port
+ and allow them to control the tor process.
+ '';
+ };
+
+ controlSocket = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Wheter to enable Tor control socket. Control socket is created
+ in <literal>${torRunDirectory}/control</literal>
+ '';
+ };
+ };
+
+ client = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Tor daemon to route application
+ connections. You might want to disable this if you plan
+ running a dedicated Tor relay.
+ '';
+ };
+
+ socksListenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9050";
+ example = "192.168.0.1:9100";
+ description = ''
+ Bind to this address to listen for connections from
+ Socks-speaking applications. Provides strong circuit
+ isolation, separate circuit per IP address.
+ '';
+ };
+
+ socksListenAddressFaster = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9063";
+ example = "192.168.0.1:9101";
+ description = ''
+ Bind to this address to listen for connections from
+ Socks-speaking applications. Same as
+ <option>socksListenAddress</option> but uses weaker
+ circuit isolation to provide performance suitable for a
+ web browser.
+ '';
+ };
+
+ socksPolicy = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "accept 192.168.0.0/16, reject *";
+ description = ''
+ Entry policies to allow/deny SOCKS requests based on IP
+ address. First entry that matches wins. If no SocksPolicy
+ is set, we accept all (and only) requests from
+ <option>socksListenAddress</option>.
+ '';
+ };
+
+ socksIsolationOptions = mkOption (isolationOptions // {
+ default = ["IsolateDestAddr"];
+ });
+
+ transparentProxy = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable tor transparent proxy";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9040";
+ example = "192.168.0.1:9040";
+ description = ''
+ Bind transparent proxy to this address.
+ '';
+ };
+
+ isolationOptions = mkOption isolationOptions;
+ };
+
+ dns = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable tor dns resolver";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9053";
+ example = "192.168.0.1:9053";
+ description = ''
+ Bind tor dns to this address.
+ '';
+ };
+
+ isolationOptions = mkOption isolationOptions;
+
+ automapHostsSuffixes = mkOption {
+ type = types.listOf types.str;
+ default = [".onion" ".exit"];
+ example = [".onion"];
+ description = "List of suffixes to use with automapHostsOnResolve";
+ };
+ };
+
+ privoxy.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable and configure the system Privoxy to use Tor's
+ faster port, suitable for HTTP.
+
+ To have anonymity, protocols need to be scrubbed of identifying
+ information, and this can be accomplished for HTTP by Privoxy.
+
+ Privoxy can also be useful for KDE torification. A good setup would be:
+ setting SOCKS proxy to the default Tor port, providing maximum
+ circuit isolation where possible; and setting HTTP proxy to Privoxy
+ to route HTTP traffic over faster, but less isolated port.
+ '';
+ };
+ };
+
+ relay = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable relaying TOR traffic for others.
+
+ See <link xlink:href="https://www.torproject.org/docs/tor-doc-relay" />
+ for details.
+
+ Setting this to true requires setting
+ <option>services.tor.relay.role</option>
+ and
+ <option>services.tor.relay.port</option>
+ options.
+ '';
+ };
+
+ role = mkOption {
+ type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
+ description = ''
+ Your role in Tor network. There're several options:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>exit</literal></term>
+ <listitem>
+ <para>
+ An exit relay. This allows Tor users to access regular
+ Internet services through your public IP.
+ </para>
+
+ <important><para>
+ Running an exit relay may expose you to abuse
+ complaints. See
+ <link xlink:href="https://www.torproject.org/faq.html.en#ExitPolicies" />
+ for more info.
+ </para></important>
+
+ <para>
+ You can specify which services Tor users may access via
+ your exit relay using <option>exitPolicy</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>relay</literal></term>
+ <listitem>
+ <para>
+ Regular relay. This allows Tor users to relay onion
+ traffic to other Tor nodes, but not to public
+ Internet.
+ </para>
+
+ <important><para>
+ Note that some misconfigured and/or disrespectful
+ towards privacy sites will block you even if your
+ relay is not an exit relay. That is, just being listed
+ in a public relay directory can have unwanted
+ consequences.
+
+ Which means you might not want to use
+ this role if you browse public Internet from the same
+ network as your relay, unless you want to write
+ e-mails to those sites (you should!).
+ </para></important>
+
+ <para>
+ See
+ <link xlink:href="https://www.torproject.org/docs/tor-doc-relay.html.en" />
+ for more info.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>bridge</literal></term>
+ <listitem>
+ <para>
+ Regular bridge. Works like a regular relay, but
+ doesn't list you in the public relay directory and
+ hides your Tor node behind obfs4proxy.
+ </para>
+
+ <para>
+ Using this option will make Tor advertise your bridge
+ to users through various mechanisms like
+ <link xlink:href="https://bridges.torproject.org/" />, though.
+ </para>
+
+ <important>
+ <para>
+ WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE.
+ Consult with your lawer when in doubt.
+ </para>
+
+ <para>
+ This role should be safe to use in most situations
+ (unless the act of forwarding traffic for others is
+ a punishable offence under your local laws, which
+ would be pretty insane as it would make ISP
+ illegal).
+ </para>
+ </important>
+
+ <para>
+ See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
+ for more info.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>private-bridge</literal></term>
+ <listitem>
+ <para>
+ Private bridge. Works like regular bridge, but does
+ not advertise your node in any way.
+ </para>
+
+ <para>
+ Using this role means that you won't contribute to Tor
+ network in any way unless you advertise your node
+ yourself in some way.
+ </para>
+
+ <para>
+ Use this if you want to run a private bridge, for
+ example because you'll give out your bridge address
+ manually to your friends.
+ </para>
+
+ <para>
+ Switching to this role after measurable time in
+ "bridge" role is pretty useless as some Tor users
+ would have learned about your node already. In the
+ latter case you can still change
+ <option>port</option> option.
+ </para>
+
+ <para>
+ See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
+ for more info.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+
+ bridgeTransports = mkOption {
+ type = types.listOf types.str;
+ default = ["obfs4"];
+ example = ["obfs2" "obfs3" "obfs4" "scramblesuit"];
+ description = "List of pluggable transports";
+ };
+
+ nickname = mkOption {
+ type = types.str;
+ default = "anonymous";
+ description = ''
+ A unique handle for your TOR relay.
+ '';
+ };
+
+ contactInfo = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "admin@relay.com";
+ description = ''
+ Contact information for the relay owner (e.g. a mail
+ address and GPG key ID).
+ '';
+ };
+
+ accountingMax = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "450 GBytes";
+ description = ''
+ Specify maximum bandwidth allowed during an accounting period. This
+ allows you to limit overall tor bandwidth over some time period.
+ See the <literal>AccountingMax</literal> option by looking at the
+ tor manual <citerefentry><refentrytitle>tor</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> for more.
+
+ Note this limit applies individually to upload and
+ download; if you specify <literal>"500 GBytes"</literal>
+ here, then you may transfer up to 1 TBytes of overall
+ bandwidth (500 GB upload, 500 GB download).
+ '';
+ };
+
+ accountingStart = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "month 1 1:00";
+ description = ''
+ Specify length of an accounting period. This allows you to limit
+ overall tor bandwidth over some time period. See the
+ <literal>AccountingStart</literal> option by looking at the tor
+ manual <citerefentry><refentrytitle>tor</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> for more.
+ '';
+ };
+
+ bandwidthRate = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 100;
+ description = ''
+ Specify this to limit the bandwidth usage of relayed (server)
+ traffic. Your own traffic is still unthrottled. Units: bytes/second.
+ '';
+ };
+
+ bandwidthBurst = mkOption {
+ type = types.nullOr types.int;
+ default = cfg.relay.bandwidthRate;
+ example = 200;
+ description = ''
+ Specify this to allow bursts of the bandwidth usage of relayed (server)
+ traffic. The average usage will still be as specified in relayBandwidthRate.
+ Your own traffic is still unthrottled. Units: bytes/second.
+ '';
+ };
+
+ address = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "noname.example.com";
+ description = ''
+ The IP address or full DNS name for advertised address of your relay.
+ Leave unset and Tor will guess.
+ '';
+ };
+
+ port = mkOption {
+ type = types.either types.int types.str;
+ example = 143;
+ description = ''
+ What port to advertise for Tor connections. This corresponds to the
+ <literal>ORPort</literal> section in the Tor manual; see
+ <citerefentry><refentrytitle>tor</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> for more details.
+
+ At a minimum, you should just specify the port for the
+ relay to listen on; a common one like 143, 22, 80, or 443
+ to help Tor users who may have very restrictive port-based
+ firewalls.
+ '';
+ };
+
+ exitPolicy = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "accept *:6660-6667,reject *:*";
+ description = ''
+ A comma-separated list of exit policies. They're
+ considered first to last, and the first match wins. If you
+ want to _replace_ the default exit policy, end this with
+ either a reject *:* or an accept *:*. Otherwise, you're
+ _augmenting_ (prepending to) the default exit policy.
+ Leave commented to just use the default, which is
+ available in the man page or at
+ <link xlink:href="https://www.torproject.org/documentation.html" />.
+
+ Look at
+ <link xlink:href="https://www.torproject.org/faq-abuse.html#TypicalAbuses" />
+ for issues you might encounter if you use the default
+ exit policy.
+
+ If certain IPs and ports are blocked externally, e.g. by
+ your firewall, you should update your exit policy to
+ reflect this -- otherwise Tor users will be told that
+ those destinations are down.
+ '';
+ };
+ };
+
+ hiddenServices = mkOption {
+ description = ''
+ A set of static hidden services that terminate their Tor
+ circuits at this node.
+
+ Every element in this set declares a virtual onion host.
+
+ You can specify your onion address by putting corresponding
+ private key to an appropriate place in ${torDirectory}.
+
+ For services without private keys in ${torDirectory} Tor
+ daemon will generate random key pairs (which implies random
+ onion addresses) on restart. The latter could take a while,
+ please be patient.
+
+ <note><para>
+ Hidden services can be useful even if you don't intend to
+ actually <emphasis>hide</emphasis> them, since they can
+ also be seen as a kind of NAT traversal mechanism.
+
+ E.g. the example will make your sshd, whatever runs on
+ "8080" and your mail server available from anywhere where
+ the Tor network is available (which, with the help from
+ bridges, is pretty much everywhere), even if both client
+ and server machines are behind NAT you have no control
+ over.
+ </para></note>
+ '';
+ default = {};
+ example = literalExample ''
+ { "my-hidden-service-example".map = [
+ { port = 22; } # map ssh port to this machine's ssh
+ { port = 80; toPort = 8080; } # map http port to whatever runs on 8080
+ { port = "sip"; toHost = "mail.example.com"; toPort = "imap"; } # because we can
+ ];
+ }
+ '';
+ type = types.loaOf (types.submodule ({name, ...}: {
+ options = {
+
+ name = mkOption {
+ type = types.str;
+ description = ''
+ Name of this tor hidden service.
+
+ This is purely descriptive.
+
+ After restarting Tor daemon you should be able to
+ find your .onion address in
+ <literal>${torDirectory}/onion/$name/hostname</literal>.
+ '';
+ };
+
+ map = mkOption {
+ default = [];
+ description = "Port mapping for this hidden service.";
+ type = types.listOf (types.submodule ({config, ...}: {
+ options = {
+
+ port = mkOption {
+ type = types.either types.int types.str;
+ example = 80;
+ description = ''
+ Hidden service port to "bind to".
+ '';
+ };
+
+ destination = mkOption {
+ internal = true;
+ type = types.str;
+ description = "Forward these connections where?";
+ };
+
+ toHost = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Mapping destination host.";
+ };
+
+ toPort = mkOption {
+ type = types.either types.int types.str;
+ example = 8080;
+ description = "Mapping destination port.";
+ };
+
+ };
+
+ config = {
+ toPort = mkDefault config.port;
+ destination = mkDefault "${config.toHost}:${toString config.toPort}";
+ };
+ }));
+ };
+
+ authorizeClient = mkOption {
+ default = null;
+ description = "If configured, the hidden service is accessible for authorized clients only.";
+ type = types.nullOr (types.submodule ({...}: {
+
+ options = {
+
+ authType = mkOption {
+ type = types.enum [ "basic" "stealth" ];
+ description = ''
+ Either <literal>"basic"</literal> for a general-purpose authorization protocol
+ or <literal>"stealth"</literal> for a less scalable protocol
+ that also hides service activity from unauthorized clients.
+ '';
+ };
+
+ clientNames = mkOption {
+ type = types.nonEmptyListOf (types.strMatching "[A-Za-z0-9+-_]+");
+ description = ''
+ Only clients that are listed here are authorized to access the hidden service.
+ Generated authorization data can be found in <filename>${torDirectory}/onion/$name/hostname</filename>.
+ Clients need to put this authorization data in their configuration file using <literal>HidServAuth</literal>.
+ '';
+ };
+ };
+ }));
+ };
+
+ version = mkOption {
+ default = null;
+ description = "Rendezvous service descriptor version to publish for the hidden service. Currently, versions 2 and 3 are supported. (Default: 2)";
+ type = types.nullOr (types.enum [ 2 3 ]);
+ };
+ };
+
+ config = {
+ name = mkDefault name;
+ };
+ }));
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # Not sure if `cfg.relay.role == "private-bridge"` helps as tor
+ # sends a lot of stats
+ warnings = optional (cfg.relay.enable && cfg.hiddenServices != {})
+ ''
+ Running Tor hidden services on a public relay makes the
+ presence of hidden services visible through simple statistical
+ analysis of publicly available data.
+
+ You can safely ignore this warning if you don't intend to
+ actually hide your hidden services. In either case, you can
+ always create a container/VM with a separate Tor daemon instance.
+ '';
+
+ users.groups.tor.gid = config.ids.gids.tor;
+ users.users.tor =
+ { description = "Tor Daemon User";
+ createHome = true;
+ home = torDirectory;
+ group = "tor";
+ uid = config.ids.uids.tor;
+ };
+
+ # We have to do this instead of using RuntimeDirectory option in
+ # the service below because systemd has no way to set owners of
+ # RuntimeDirectory and putting this into the service below
+ # requires that service to relax it's sandbox since this needs
+ # writable /run
+ systemd.services.tor-init =
+ { description = "Tor Daemon Init";
+ wantedBy = [ "tor.service" ];
+ script = ''
+ install -m 0700 -o tor -g tor -d ${torDirectory} ${torDirectory}/onion
+ install -m 0750 -o tor -g tor -d ${torRunDirectory}
+ '';
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ };
+
+ systemd.services.tor =
+ { description = "Tor Daemon";
+ path = [ pkgs.tor ];
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "tor-init.service" "network.target" ];
+ restartTriggers = [ torRcFile ];
+
+ serviceConfig =
+ { Type = "simple";
+ # Translated from the upstream contrib/dist/tor.service.in
+ ExecStartPre = "${pkgs.tor}/bin/tor -f ${torRcFile} --verify-config";
+ ExecStart = "${pkgs.tor}/bin/tor -f ${torRcFile}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ KillSignal = "SIGINT";
+ TimeoutSec = 30;
+ Restart = "on-failure";
+ LimitNOFILE = 32768;
+
+ # Hardening
+ # this seems to unshare /run despite what systemd.exec(5) says
+ PrivateTmp = mkIf (!cfg.controlSocket.enable) "yes";
+ PrivateDevices = "yes";
+ ProtectHome = "yes";
+ ProtectSystem = "strict";
+ InaccessiblePaths = "/home";
+ ReadOnlyPaths = "/";
+ ReadWritePaths = [ torDirectory torRunDirectory ];
+ NoNewPrivileges = "yes";
+
+ # tor.service.in has this in, but this line it fails to spawn a namespace when using hidden services
+ #CapabilityBoundingSet = "CAP_SETUID CAP_SETGID CAP_NET_BIND_SERVICE";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.tor ];
+
+ services.privoxy = mkIf (cfg.client.enable && cfg.client.privoxy.enable) {
+ enable = true;
+ extraConfig = ''
+ forward-socks4a / ${cfg.client.socksListenAddressFaster} .
+ toggle 1
+ enable-remote-toggle 0
+ enable-edit-actions 0
+ enable-remote-http-toggle 0
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/torify.nix b/nixpkgs/nixos/modules/services/security/torify.nix
new file mode 100644
index 00000000000..08da726437e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/torify.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+
+ cfg = config.services.tor;
+
+ torify = pkgs.writeTextFile {
+ name = "tsocks";
+ text = ''
+ #!${pkgs.runtimeShell}
+ TSOCKS_CONF_FILE=${pkgs.writeText "tsocks.conf" cfg.tsocks.config} LD_PRELOAD="${pkgs.tsocks}/lib/libtsocks.so $LD_PRELOAD" "$@"
+ '';
+ executable = true;
+ destination = "/bin/tsocks";
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.tor.tsocks = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to build tsocks wrapper script to relay application traffic via Tor.
+
+ <important>
+ <para>You shouldn't use this unless you know what you're
+ doing because your installation of Tor already comes with
+ its own superior (doesn't leak DNS queries)
+ <literal>torsocks</literal> wrapper which does pretty much
+ exactly the same thing as this.</para>
+ </important>
+ '';
+ };
+
+ server = mkOption {
+ default = "localhost:9050";
+ example = "192.168.0.20";
+ description = ''
+ IP address of TOR client to use.
+ '';
+ };
+
+ config = mkOption {
+ default = "";
+ description = ''
+ Extra configuration. Contents will be added verbatim to TSocks
+ configuration file.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.tsocks.enable {
+
+ environment.systemPackages = [ torify ]; # expose it to the users
+
+ services.tor.tsocks.config = ''
+ server = ${toString(head (splitString ":" cfg.tsocks.server))}
+ server_port = ${toString(tail (splitString ":" cfg.tsocks.server))}
+
+ local = 127.0.0.0/255.128.0.0
+ local = 127.128.0.0/255.192.0.0
+ '';
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/torsocks.nix b/nixpkgs/nixos/modules/services/security/torsocks.nix
new file mode 100644
index 00000000000..c60c745443b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/torsocks.nix
@@ -0,0 +1,121 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.tor.torsocks;
+ optionalNullStr = b: v: optionalString (b != null) v;
+
+ configFile = server: ''
+ TorAddress ${toString (head (splitString ":" server))}
+ TorPort ${toString (tail (splitString ":" server))}
+
+ OnionAddrRange ${cfg.onionAddrRange}
+
+ ${optionalNullStr cfg.socks5Username
+ "SOCKS5Username ${cfg.socks5Username}"}
+ ${optionalNullStr cfg.socks5Password
+ "SOCKS5Password ${cfg.socks5Password}"}
+
+ AllowInbound ${if cfg.allowInbound then "1" else "0"}
+ '';
+
+ wrapTorsocks = name: server: pkgs.writeTextFile {
+ name = name;
+ text = ''
+ #!${pkgs.runtimeShell}
+ TORSOCKS_CONF_FILE=${pkgs.writeText "torsocks.conf" (configFile server)} ${pkgs.torsocks}/bin/torsocks "$@"
+ '';
+ executable = true;
+ destination = "/bin/${name}";
+ };
+
+in
+{
+ options = {
+ services.tor.torsocks = {
+ enable = mkOption {
+ type = types.bool;
+ default = config.services.tor.enable && config.services.tor.client.enable;
+ description = ''
+ Whether to build <literal>/etc/tor/torsocks.conf</literal>
+ containing the specified global torsocks configuration.
+ '';
+ };
+
+ server = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9050";
+ example = "192.168.0.20:1234";
+ description = ''
+ IP/Port of the Tor SOCKS server. Currently, hostnames are
+ NOT supported by torsocks.
+ '';
+ };
+
+ fasterServer = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9063";
+ example = "192.168.0.20:1234";
+ description = ''
+ IP/Port of the Tor SOCKS server for torsocks-faster wrapper suitable for HTTP.
+ Currently, hostnames are NOT supported by torsocks.
+ '';
+ };
+
+ onionAddrRange = mkOption {
+ type = types.str;
+ default = "127.42.42.0/24";
+ description = ''
+ Tor hidden sites do not have real IP addresses. This
+ specifies what range of IP addresses will be handed to the
+ application as "cookies" for .onion names. Of course, you
+ should pick a block of addresses which you aren't going to
+ ever need to actually connect to. This is similar to the
+ MapAddress feature of the main tor daemon.
+ '';
+ };
+
+ socks5Username = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "bob";
+ description = ''
+ SOCKS5 username. The <literal>TORSOCKS_USERNAME</literal>
+ environment variable overrides this option if it is set.
+ '';
+ };
+
+ socks5Password = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "sekret";
+ description = ''
+ SOCKS5 password. The <literal>TORSOCKS_PASSWORD</literal>
+ environment variable overrides this option if it is set.
+ '';
+ };
+
+ allowInbound = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Set Torsocks to accept inbound connections. If set to
+ <literal>true</literal>, listen() and accept() will be
+ allowed to be used with non localhost address.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.torsocks (wrapTorsocks "torsocks-faster" cfg.fasterServer) ];
+
+ environment.etc =
+ [ { source = pkgs.writeText "torsocks.conf" (configFile cfg.server);
+ target = "tor/torsocks.conf";
+ }
+ ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/usbguard.nix b/nixpkgs/nixos/modules/services/security/usbguard.nix
new file mode 100644
index 00000000000..4ced5acd9bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/usbguard.nix
@@ -0,0 +1,213 @@
+{config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.usbguard;
+
+ # valid policy options
+ policy = (types.enum [ "allow" "block" "reject" "keep" "apply-policy" ]);
+
+ # decide what file to use for rules
+ ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else cfg.ruleFile;
+
+ daemonConf = ''
+ # generated by nixos/modules/services/security/usbguard.nix
+ RuleFile=${ruleFile}
+ ImplicitPolicyTarget=${cfg.implictPolicyTarget}
+ PresentDevicePolicy=${cfg.presentDevicePolicy}
+ PresentControllerPolicy=${cfg.presentControllerPolicy}
+ InsertedDevicePolicy=${cfg.insertedDevicePolicy}
+ RestoreControllerDeviceState=${if cfg.restoreControllerDeviceState then "true" else "false"}
+ # this does not seem useful for endusers to change
+ DeviceManagerBackend=uevent
+ IPCAllowedUsers=${concatStringsSep " " cfg.IPCAllowedUsers}
+ IPCAllowedGroups=${concatStringsSep " " cfg.IPCAllowedGroups}
+ IPCAccessControlFiles=${cfg.IPCAccessControlFiles}
+ DeviceRulesWithPort=${if cfg.deviceRulesWithPort then "true" else "false"}
+ AuditFilePath=${cfg.auditFilePath}
+ '';
+
+ daemonConfFile = pkgs.writeText "usbguard-daemon-conf" daemonConf;
+
+in {
+
+ ###### interface
+
+ options = {
+ services.usbguard = {
+ enable = mkEnableOption "USBGuard daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.usbguard;
+ defaultText = "pkgs.usbguard";
+ description = ''
+ The usbguard package to use. If you do not need the Qt GUI, use
+ <literal>pkgs.usbguard-nox</literal> to save disk space.
+ '';
+ };
+
+ ruleFile = mkOption {
+ type = types.path;
+ default = "/var/lib/usbguard/rules.conf";
+ description = ''
+ The USBGuard daemon will use this file to load the policy rule set
+ from it and to write new rules received via the IPC interface.
+
+ Running the command <literal>usbguard generate-policy</literal> as
+ root will generate a config for your currently plugged in devices.
+ For a in depth guide consult the official documentation.
+
+ Setting the <literal>rules</literal> option will ignore the
+ <literal>ruleFile</literal> option.
+ '';
+ };
+
+ rules = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ example = ''
+ allow with-interface equals { 08:*:* }
+ '';
+ description = ''
+ The USBGuard daemon will load this policy rule set. Modifying it via
+ the IPC interface won't work if you use this option, since the
+ contents of this option will be written into the nix-store it will be
+ read-only.
+
+ You can still use <literal> usbguard generate-policy</literal> to
+ generate rules, but you would have to insert them here.
+
+ Setting the <literal>rules</literal> option will ignore the
+ <literal>ruleFile</literal> option.
+ '';
+ };
+
+ implictPolicyTarget = mkOption {
+ type = policy;
+ default = "block";
+ description = ''
+ How to treat USB devices that don't match any rule in the policy.
+ Target should be one of allow, block or reject (logically remove the
+ device node from the system).
+ '';
+ };
+
+ presentDevicePolicy = mkOption {
+ type = policy;
+ default = "apply-policy";
+ description = ''
+ How to treat USB devices that are already connected when the daemon
+ starts. Policy should be one of allow, block, reject, keep (keep
+ whatever state the device is currently in) or apply-policy (evaluate
+ the rule set for every present device).
+ '';
+ };
+
+ presentControllerPolicy = mkOption {
+ type = policy;
+ default = "keep";
+ description = ''
+ How to treat USB controller devices that are already connected when
+ the daemon starts. One of allow, block, reject, keep or apply-policy.
+ '';
+ };
+
+ insertedDevicePolicy = mkOption {
+ type = policy;
+ default = "apply-policy";
+ description = ''
+ How to treat USB devices that are already connected after the daemon
+ starts. One of block, reject, apply-policy.
+ '';
+ };
+
+ restoreControllerDeviceState = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ The USBGuard daemon modifies some attributes of controller
+ devices like the default authorization state of new child device
+ instances. Using this setting, you can controll whether the daemon
+ will try to restore the attribute values to the state before
+ modificaton on shutdown.
+ '';
+ };
+
+ IPCAllowedUsers = mkOption {
+ type = types.listOf types.str;
+ default = [ "root" ];
+ example = [ "root" "yourusername" ];
+ description = ''
+ A list of usernames that the daemon will accept IPC connections from.
+ '';
+ };
+
+ IPCAllowedGroups = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "wheel" ];
+ description = ''
+ A list of groupnames that the daemon will accept IPC connections
+ from.
+ '';
+ };
+
+ IPCAccessControlFiles = mkOption {
+ type = types.path;
+ default = "/var/lib/usbguard/IPCAccessControl.d/";
+ description = ''
+ The files at this location will be interpreted by the daemon as IPC
+ access control definition files. See the IPC ACCESS CONTROL section
+ in <citerefentry><refentrytitle>usbguard-daemon.conf</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for more details.
+ '';
+ };
+
+ deviceRulesWithPort = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Generate device specific rules including the "via-port" attribute.
+ '';
+ };
+
+ auditFilePath = mkOption {
+ type = types.path;
+ default = "/var/log/usbguard/usbguard-audit.log";
+ description = ''
+ USBGuard audit events log file path.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services.usbguard = {
+ description = "USBGuard daemon";
+
+ wantedBy = [ "basic.target" ];
+ wants = [ "systemd-udevd.service" ];
+
+ # make sure an empty rule file and required directories exist
+ preStart = ''
+ mkdir -p $(dirname "${cfg.ruleFile}") $(dirname "${cfg.auditFilePath}") "${cfg.IPCAccessControlFiles}" \
+ && ([ -f "${cfg.ruleFile}" ] || touch ${cfg.ruleFile})
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = ''${cfg.package}/bin/usbguard-daemon -P -k -c ${daemonConfFile}'';
+ Restart = "on-failure";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/vault.nix b/nixpkgs/nixos/modules/services/security/vault.nix
new file mode 100644
index 00000000000..d5962ba9af9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/vault.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.vault;
+
+ configFile = pkgs.writeText "vault.hcl" ''
+ listener "tcp" {
+ address = "${cfg.address}"
+ ${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then ''
+ tls_disable = "true"
+ '' else ''
+ tls_cert_file = "${cfg.tlsCertFile}"
+ tls_key_file = "${cfg.tlsKeyFile}"
+ ''}
+ ${cfg.listenerExtraConfig}
+ }
+ storage "${cfg.storageBackend}" {
+ ${optionalString (cfg.storagePath != null) ''path = "${cfg.storagePath}"''}
+ ${optionalString (cfg.storageConfig != null) cfg.storageConfig}
+ }
+ ${optionalString (cfg.telemetryConfig != "") ''
+ telemetry {
+ ${cfg.telemetryConfig}
+ }
+ ''}
+ ${cfg.extraConfig}
+ '';
+in
+
+{
+ options = {
+ services.vault = {
+ enable = mkEnableOption "Vault daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.vault;
+ defaultText = "pkgs.vault";
+ description = "This option specifies the vault package to use.";
+ };
+
+ address = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8200";
+ description = "The name of the ip interface to listen to";
+ };
+
+ tlsCertFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/path/to/your/cert.pem";
+ description = "TLS certificate file. TLS will be disabled unless this option is set";
+ };
+
+ tlsKeyFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/path/to/your/key.pem";
+ description = "TLS private key file. TLS will be disabled unless this option is set";
+ };
+
+ listenerExtraConfig = mkOption {
+ type = types.lines;
+ default = ''
+ tls_min_version = "tls12"
+ '';
+ description = "Extra text appended to the listener section.";
+ };
+
+ storageBackend = mkOption {
+ type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" "raft" ];
+ default = "inmem";
+ description = "The name of the type of storage backend";
+ };
+
+ storagePath = mkOption {
+ type = types.nullOr types.path;
+ default = if cfg.storageBackend == "file" then "/var/lib/vault" else null;
+ description = "Data directory for file backend";
+ };
+
+ storageConfig = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = "Storage configuration";
+ };
+
+ telemetryConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Telemetry configuration";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra text appended to <filename>vault.hcl</filename>.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ { assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
+ message = ''The "inmem" storage expects no services.vault.storagePath nor services.vault.storageConfig'';
+ }
+ { assertion = (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) && (cfg.storagePath != null -> cfg.storageBackend == "file");
+ message = ''You must set services.vault.storagePath only when using the "file" backend'';
+ }
+ ];
+
+ users.users.vault = {
+ name = "vault";
+ group = "vault";
+ uid = config.ids.uids.vault;
+ description = "Vault daemon user";
+ };
+ users.groups.vault.gid = config.ids.gids.vault;
+
+ systemd.tmpfiles.rules = optional (cfg.storagePath != null) [
+ "d '${cfg.storagePath}' 0700 vault vault - -"
+ ];
+
+ systemd.services.vault = {
+ description = "Vault server daemon";
+
+ wantedBy = ["multi-user.target"];
+ after = [ "network.target" ]
+ ++ optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";
+
+ restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
+
+ serviceConfig = {
+ User = "vault";
+ Group = "vault";
+ ExecStart = "${cfg.package}/bin/vault server -config ${configFile}";
+ PrivateDevices = true;
+ PrivateTmp = true;
+ ProtectSystem = "full";
+ ProtectHome = "read-only";
+ AmbientCapabilities = "cap_ipc_lock";
+ NoNewPrivileges = true;
+ KillSignal = "SIGINT";
+ TimeoutStopSec = "30s";
+ Restart = "on-failure";
+ StartLimitInterval = "60s";
+ StartLimitBurst = 3;
+ };
+
+ unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath;
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/system/cgmanager.nix b/nixpkgs/nixos/modules/services/system/cgmanager.nix
new file mode 100644
index 00000000000..d3d57aa7692
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/cgmanager.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cgmanager;
+in {
+ meta.maintainers = [ maintainers.mic92 ];
+
+ ###### interface
+ options.services.cgmanager.enable = mkEnableOption "cgmanager";
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ systemd.services.cgmanager = {
+ wantedBy = [ "multi-user.target" ];
+ description = "Cgroup management daemon";
+ restartIfChanged = false;
+ serviceConfig = {
+ ExecStart = "${pkgs.cgmanager}/bin/cgmanager -m name=systemd";
+ KillMode = "process";
+ Restart = "on-failure";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/system/cloud-init.nix b/nixpkgs/nixos/modules/services/system/cloud-init.nix
new file mode 100644
index 00000000000..15fe822aec6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/cloud-init.nix
@@ -0,0 +1,180 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.cloud-init;
+ path = with pkgs; [
+ cloud-init
+ iproute
+ nettools
+ openssh
+ shadow
+ utillinux
+ ] ++ optional cfg.btrfs.enable btrfs-progs
+ ++ optional cfg.ext4.enable e2fsprogs
+ ;
+in
+{
+ options = {
+ services.cloud-init = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the cloud-init service. This services reads
+ configuration metadata in a cloud environment and configures
+ the machine according to this metadata.
+
+ This configuration is not completely compatible with the
+ NixOS way of doing configuration, as configuration done by
+ cloud-init might be overriden by a subsequent nixos-rebuild
+ call. However, some parts of cloud-init fall outside of
+ NixOS's responsibility, like filesystem resizing and ssh
+ public key provisioning, and cloud-init is useful for that
+ parts. Thus, be wary that using cloud-init in NixOS might
+ come as some cost.
+ '';
+ };
+
+ btrfs.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow the cloud-init service to operate `btrfs` filesystem.
+ '';
+ };
+
+ ext4.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Allow the cloud-init service to operate `ext4` filesystem.
+ '';
+ };
+
+ config = mkOption {
+ type = types.str;
+ default = ''
+ system_info:
+ distro: nixos
+ users:
+ - root
+
+ disable_root: false
+ preserve_hostname: false
+
+ cloud_init_modules:
+ - migrator
+ - seed_random
+ - bootcmd
+ - write-files
+ - growpart
+ - resizefs
+ - update_etc_hosts
+ - ca-certs
+ - rsyslog
+ - users-groups
+
+ cloud_config_modules:
+ - disk_setup
+ - mounts
+ - ssh-import-id
+ - set-passwords
+ - timezone
+ - disable-ec2-metadata
+ - runcmd
+ - ssh
+
+ cloud_final_modules:
+ - rightscale_userdata
+ - scripts-vendor
+ - scripts-per-once
+ - scripts-per-boot
+ - scripts-per-instance
+ - scripts-user
+ - ssh-authkey-fingerprints
+ - keys-to-console
+ - phone-home
+ - final-message
+ - power-state-change
+ '';
+ description = ''cloud-init configuration.'';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.etc."cloud/cloud.cfg".text = cfg.config;
+
+ systemd.services.cloud-init-local =
+ { description = "Initial cloud-init job (pre-networking)";
+ wantedBy = [ "multi-user.target" ];
+ path = path;
+ serviceConfig =
+ { Type = "oneshot";
+ ExecStart = "${pkgs.cloud-init}/bin/cloud-init init --local";
+ RemainAfterExit = "yes";
+ TimeoutSec = "infinity";
+ StandardOutput = "journal+console";
+ };
+ };
+
+ systemd.services.cloud-init =
+ { description = "Initial cloud-init job (metadata service crawler)";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" "cloud-init-local.service"
+ "sshd.service" "sshd-keygen.service" ];
+ after = [ "network-online.target" "cloud-init-local.service" ];
+ before = [ "sshd.service" "sshd-keygen.service" ];
+ requires = [ "network.target "];
+ path = path;
+ serviceConfig =
+ { Type = "oneshot";
+ ExecStart = "${pkgs.cloud-init}/bin/cloud-init init";
+ RemainAfterExit = "yes";
+ TimeoutSec = "infinity";
+ StandardOutput = "journal+console";
+ };
+ };
+
+ systemd.services.cloud-config =
+ { description = "Apply the settings specified in cloud-config";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" ];
+ after = [ "network-online.target" "syslog.target" "cloud-config.target" ];
+
+ path = path;
+ serviceConfig =
+ { Type = "oneshot";
+ ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=config";
+ RemainAfterExit = "yes";
+ TimeoutSec = "infinity";
+ StandardOutput = "journal+console";
+ };
+ };
+
+ systemd.services.cloud-final =
+ { description = "Execute cloud user/final scripts";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" ];
+ after = [ "network-online.target" "syslog.target" "cloud-config.service" "rc-local.service" ];
+ requires = [ "cloud-config.target" ];
+ path = path;
+ serviceConfig =
+ { Type = "oneshot";
+ ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=final";
+ RemainAfterExit = "yes";
+ TimeoutSec = "infinity";
+ StandardOutput = "journal+console";
+ };
+ };
+
+ systemd.targets.cloud-config =
+ { description = "Cloud-config availability";
+ requires = [ "cloud-init-local.service" "cloud-init.service" ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/system/dbus.nix b/nixpkgs/nixos/modules/services/system/dbus.nix
new file mode 100644
index 00000000000..936646a5fd7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/dbus.nix
@@ -0,0 +1,119 @@
+# D-Bus configuration and system bus daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.dbus;
+
+ homeDir = "/run/dbus";
+
+ configDir = pkgs.makeDBusConf {
+ suidHelper = "${config.security.wrapperDir}/dbus-daemon-launch-helper";
+ serviceDirectories = cfg.packages;
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.dbus = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ internal = true;
+ description = ''
+ Whether to start the D-Bus message bus daemon, which is
+ required by many other system services and applications.
+ '';
+ };
+
+ packages = mkOption {
+ type = types.listOf types.path;
+ default = [ ];
+ description = ''
+ Packages whose D-Bus configuration files should be included in
+ the configuration of the D-Bus system-wide or session-wide
+ message bus. Specifically, files in the following directories
+ will be included into their respective DBus configuration paths:
+ <filename><replaceable>pkg</replaceable>/etc/dbus-1/system.d</filename>
+ <filename><replaceable>pkg</replaceable>/share/dbus-1/system.d</filename>
+ <filename><replaceable>pkg</replaceable>/share/dbus-1/system-services</filename>
+ <filename><replaceable>pkg</replaceable>/etc/dbus-1/session.d</filename>
+ <filename><replaceable>pkg</replaceable>/share/dbus-1/session.d</filename>
+ <filename><replaceable>pkg</replaceable>/share/dbus-1/services</filename>
+ '';
+ };
+
+ socketActivated = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Make the user instance socket activated.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.dbus.daemon pkgs.dbus ];
+
+ environment.etc = singleton
+ { source = configDir;
+ target = "dbus-1";
+ };
+
+ users.users.messagebus = {
+ uid = config.ids.uids.messagebus;
+ description = "D-Bus system message bus daemon user";
+ home = homeDir;
+ group = "messagebus";
+ };
+
+ users.groups.messagebus.gid = config.ids.gids.messagebus;
+
+ systemd.packages = [ pkgs.dbus.daemon ];
+
+ security.wrappers.dbus-daemon-launch-helper = {
+ source = "${pkgs.dbus.daemon}/libexec/dbus-daemon-launch-helper";
+ owner = "root";
+ group = "messagebus";
+ setuid = true;
+ setgid = false;
+ permissions = "u+rx,g+rx,o-rx";
+ };
+
+ services.dbus.packages = [
+ pkgs.dbus.out
+ config.system.path
+ ];
+
+ systemd.services.dbus = {
+ # Don't restart dbus-daemon. Bad things tend to happen if we do.
+ reloadIfChanged = true;
+ restartTriggers = [ configDir ];
+ environment = { LD_LIBRARY_PATH = config.system.nssModules.path; };
+ };
+
+ systemd.user = {
+ services.dbus = {
+ # Don't restart dbus-daemon. Bad things tend to happen if we do.
+ reloadIfChanged = true;
+ restartTriggers = [ configDir ];
+ };
+ sockets.dbus.wantedBy = mkIf cfg.socketActivated [ "sockets.target" ];
+ };
+
+ environment.pathsToLink = [ "/etc/dbus-1" "/share/dbus-1" ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/system/earlyoom.nix b/nixpkgs/nixos/modules/services/system/earlyoom.nix
new file mode 100644
index 00000000000..39d1bf274bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/earlyoom.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ ecfg = config.services.earlyoom;
+in
+{
+ options = {
+ services.earlyoom = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable early out of memory killing.
+ '';
+ };
+
+ freeMemThreshold = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ Minimum of availabe memory (in percent).
+ If the free memory falls below this threshold and the analog is true for
+ <option>services.earlyoom.freeSwapThreshold</option>
+ the killing begins.
+ '';
+ };
+
+ freeSwapThreshold = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ Minimum of availabe swap space (in percent).
+ If the available swap space falls below this threshold and the analog
+ is true for <option>services.earlyoom.freeMemThreshold</option>
+ the killing begins.
+ '';
+ };
+
+ useKernelOOMKiller= mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Use kernel OOM killer instead of own user-space implementation.
+ '';
+ };
+
+ ignoreOOMScoreAdjust = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Ignore oom_score_adjust values of processes.
+ User-space implementation only.
+ '';
+ };
+
+ enableDebugInfo = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable debugging messages.
+ '';
+ };
+
+ notificationsCommand = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "sudo -u example_user DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send";
+ description = ''
+ Command used to send notifications.
+
+ See <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> for details.
+ '';
+ };
+ };
+ };
+
+ config = mkIf ecfg.enable {
+ assertions = [
+ { assertion = ecfg.freeMemThreshold > 0 && ecfg.freeMemThreshold <= 100;
+ message = "Needs to be a positive percentage"; }
+ { assertion = ecfg.freeSwapThreshold > 0 && ecfg.freeSwapThreshold <= 100;
+ message = "Needs to be a positive percentage"; }
+ { assertion = !ecfg.useKernelOOMKiller || !ecfg.ignoreOOMScoreAdjust;
+ message = "Both options in conjunction do not make sense"; }
+ ];
+
+ systemd.services.earlyoom = {
+ description = "Early OOM Daemon for Linux";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ StandardOutput = "null";
+ StandardError = "syslog";
+ ExecStart = ''
+ ${pkgs.earlyoom}/bin/earlyoom \
+ -m ${toString ecfg.freeMemThreshold} \
+ -s ${toString ecfg.freeSwapThreshold} \
+ ${optionalString ecfg.useKernelOOMKiller "-k"} \
+ ${optionalString ecfg.ignoreOOMScoreAdjust "-i"} \
+ ${optionalString ecfg.enableDebugInfo "-d"} \
+ ${optionalString (ecfg.notificationsCommand != null)
+ "-N ${escapeShellArg ecfg.notificationsCommand}"}
+ '';
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/system/kerberos/default.nix b/nixpkgs/nixos/modules/services/system/kerberos/default.nix
new file mode 100644
index 00000000000..c55241c4cff
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/kerberos/default.nix
@@ -0,0 +1,80 @@
+{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/nixpkgs/nixos/modules/services/system/kerberos/heimdal.nix b/nixpkgs/nixos/modules/services/system/kerberos/heimdal.nix
new file mode 100644
index 00000000000..f0e56c7951a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/kerberos/heimdal.nix
@@ -0,0 +1,68 @@
+{ pkgs, config, lib, ... } :
+
+let
+ inherit (lib) mkIf concatStringsSep concatMapStrings toList mapAttrs
+ mapAttrsToList;
+ cfg = config.services.kerberos_server;
+ kerberos = config.krb5.kerberos;
+ stateDir = "/var/heimdal";
+ aclFiles = mapAttrs
+ (name: {acl, ...}: pkgs.writeText "${name}.acl" (concatMapStrings ((
+ {principal, access, target, ...} :
+ "${principal}\t${concatStringsSep "," (toList access)}\t${target}\n"
+ )) acl)) cfg.realms;
+
+ kdcConfigs = mapAttrsToList (name: value: ''
+ database = {
+ dbname = ${stateDir}/heimdal
+ acl_file = ${value}
+ }
+ '') aclFiles;
+ kdcConfFile = pkgs.writeText "kdc.conf" ''
+ [kdc]
+ ${concatStringsSep "\n" kdcConfigs}
+ '';
+in
+
+{
+ # No documentation about correct triggers, so guessing at them.
+
+ config = mkIf (cfg.enable && kerberos == pkgs.heimdalFull) {
+ systemd.services.kadmind = {
+ description = "Kerberos Administration Daemon";
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ '';
+ serviceConfig.ExecStart =
+ "${kerberos}/libexec/heimdal/kadmind --config-file=/etc/heimdal-kdc/kdc.conf";
+ restartTriggers = [ kdcConfFile ];
+ };
+
+ systemd.services.kdc = {
+ description = "Key Distribution Center daemon";
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ '';
+ serviceConfig.ExecStart =
+ "${kerberos}/libexec/heimdal/kdc --config-file=/etc/heimdal-kdc/kdc.conf";
+ restartTriggers = [ kdcConfFile ];
+ };
+
+ systemd.services.kpasswdd = {
+ description = "Kerberos Password Changing daemon";
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ '';
+ serviceConfig.ExecStart = "${kerberos}/libexec/heimdal/kpasswdd";
+ restartTriggers = [ kdcConfFile ];
+ };
+
+ environment.etc = {
+ # Can be set via the --config-file option to KDC
+ "heimdal-kdc/kdc.conf".source = kdcConfFile;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/system/kerberos/mit.nix b/nixpkgs/nixos/modules/services/system/kerberos/mit.nix
new file mode 100644
index 00000000000..25d7d51e808
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/kerberos/mit.nix
@@ -0,0 +1,68 @@
+{ pkgs, config, lib, ... } :
+
+let
+ inherit (lib) mkIf concatStrings concatStringsSep concatMapStrings toList
+ mapAttrs mapAttrsToList;
+ cfg = config.services.kerberos_server;
+ kerberos = config.krb5.kerberos;
+ stateDir = "/var/lib/krb5kdc";
+ PIDFile = "/run/kdc.pid";
+ aclMap = {
+ add = "a"; cpw = "c"; delete = "d"; get = "i"; list = "l"; modify = "m";
+ all = "*";
+ };
+ aclFiles = mapAttrs
+ (name: {acl, ...}: (pkgs.writeText "${name}.acl" (concatMapStrings (
+ {principal, access, target, ...} :
+ let access_code = map (a: aclMap.${a}) (toList access); in
+ "${principal} ${concatStrings access_code} ${target}\n"
+ ) acl))) cfg.realms;
+ kdcConfigs = mapAttrsToList (name: value: ''
+ ${name} = {
+ acl_file = ${value}
+ }
+ '') aclFiles;
+ kdcConfFile = pkgs.writeText "kdc.conf" ''
+ [realms]
+ ${concatStringsSep "\n" kdcConfigs}
+ '';
+ env = {
+ # What Debian uses, could possibly link directly to Nix store?
+ KRB5_KDC_PROFILE = "/etc/krb5kdc/kdc.conf";
+ };
+in
+
+{
+ config = mkIf (cfg.enable && kerberos == pkgs.krb5Full) {
+ systemd.services.kadmind = {
+ description = "Kerberos Administration Daemon";
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ '';
+ serviceConfig.ExecStart = "${kerberos}/bin/kadmind -nofork";
+ restartTriggers = [ kdcConfFile ];
+ environment = env;
+ };
+
+ systemd.services.kdc = {
+ description = "Key Distribution Center daemon";
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ '';
+ serviceConfig = {
+ Type = "forking";
+ PIDFile = PIDFile;
+ ExecStart = "${kerberos}/bin/krb5kdc -P ${PIDFile}";
+ };
+ restartTriggers = [ kdcConfFile ];
+ environment = env;
+ };
+
+ environment.etc = {
+ "krb5kdc/kdc.conf".source = kdcConfFile;
+ };
+ environment.variables = env;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/system/localtime.nix b/nixpkgs/nixos/modules/services/system/localtime.nix
new file mode 100644
index 00000000000..c3c0b432b49
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/localtime.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.localtime;
+in {
+ options = {
+ services.localtime = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Enable <literal>localtime</literal>, simple daemon for keeping the system
+ timezone up-to-date based on the current location. It uses geoclue2 to
+ determine the current location and systemd-timedated to actually set
+ the timezone.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.geoclue2 = {
+ enable = true;
+ appConfig.localtime = {
+ isAllowed = true;
+ isSystem = true;
+ };
+ };
+
+ # We use the 'out' output, since localtime has its 'bin' output
+ # first, so that is what we get if we use the derivation bare.
+ # Install the polkit rules.
+ environment.systemPackages = [ pkgs.localtime.out ];
+ # Install the systemd unit.
+ systemd.packages = [ pkgs.localtime.out ];
+
+ systemd.services.localtime = {
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.Restart = "on-failure";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/system/nscd.conf b/nixpkgs/nixos/modules/services/system/nscd.conf
new file mode 100644
index 00000000000..2b7523a7346
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/nscd.conf
@@ -0,0 +1,34 @@
+# We basically use nscd as a proxy for forwarding nss requests to appropriate
+# nss modules, as we run nscd with LD_LIBRARY_PATH set to the directory
+# containing all such modules
+# Note that we can not use `enable-cache no` As this will actually cause nscd
+# to just reject the nss requests it receives, which then causes glibc to
+# fallback to trying to handle the request by itself. Which won't work as glibc
+# is not aware of the path in which the nss modules live. As a workaround, we
+# have `enable-cache yes` with an explicit ttl of 0
+server-user nscd
+
+enable-cache passwd yes
+positive-time-to-live passwd 0
+negative-time-to-live passwd 0
+shared passwd yes
+
+enable-cache group yes
+positive-time-to-live group 0
+negative-time-to-live group 0
+shared group yes
+
+enable-cache netgroup yes
+positive-time-to-live netgroup 0
+negative-time-to-live netgroup 0
+shared netgroup yes
+
+enable-cache hosts yes
+positive-time-to-live hosts 600
+negative-time-to-live hosts 0
+shared hosts yes
+
+enable-cache services yes
+positive-time-to-live services 0
+negative-time-to-live services 0
+shared services yes
diff --git a/nixpkgs/nixos/modules/services/system/nscd.nix b/nixpkgs/nixos/modules/services/system/nscd.nix
new file mode 100644
index 00000000000..e11f7e049d8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/nscd.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ nssModulesPath = config.system.nssModules.path;
+ cfg = config.services.nscd;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.nscd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to enable the Name Service Cache Daemon.";
+ };
+
+ config = mkOption {
+ type = types.lines;
+ default = builtins.readFile ./nscd.conf;
+ description = "Configuration to use for Name Service Cache Daemon.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ environment.etc."nscd.conf".text = cfg.config;
+
+ systemd.services.nscd =
+ { description = "Name Service Cache Daemon";
+
+ wantedBy = [ "nss-lookup.target" "nss-user-lookup.target" ];
+
+ environment = { LD_LIBRARY_PATH = nssModulesPath; };
+
+ restartTriggers = [
+ config.environment.etc.hosts.source
+ config.environment.etc."nsswitch.conf".source
+ config.environment.etc."nscd.conf".source
+ ];
+
+ # We use DynamicUser because in default configurations nscd doesn't
+ # create any files that need to survive restarts. However, in some
+ # configurations, nscd needs to be started as root; it will drop
+ # privileges after all the NSS modules have read their configuration
+ # files. So prefix the ExecStart command with "!" to prevent systemd
+ # from dropping privileges early. See ExecStart in systemd.service(5).
+ serviceConfig =
+ { ExecStart = "!@${pkgs.glibc.bin}/sbin/nscd nscd";
+ Type = "forking";
+ DynamicUser = true;
+ RuntimeDirectory = "nscd";
+ PIDFile = "/run/nscd/nscd.pid";
+ Restart = "always";
+ ExecReload =
+ [ "${pkgs.glibc.bin}/sbin/nscd --invalidate passwd"
+ "${pkgs.glibc.bin}/sbin/nscd --invalidate group"
+ "${pkgs.glibc.bin}/sbin/nscd --invalidate hosts"
+ ];
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/system/saslauthd.nix b/nixpkgs/nixos/modules/services/system/saslauthd.nix
new file mode 100644
index 00000000000..8fcf4fb91fc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/saslauthd.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.saslauthd;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.saslauthd = {
+
+ enable = mkEnableOption "saslauthd, the Cyrus SASL authentication daemon";
+
+ package = mkOption {
+ default = pkgs.cyrus_sasl.bin;
+ defaultText = "pkgs.cyrus_sasl.bin";
+ type = types.package;
+ description = "Cyrus SASL package to use.";
+ };
+
+ mechanism = mkOption {
+ type = types.str;
+ default = "pam";
+ description = "Auth mechanism to use";
+ };
+
+ config = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Configuration to use for Cyrus SASL authentication daemon.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.saslauthd = {
+ description = "Cyrus SASL authentication daemon";
+
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "@${cfg.package}/sbin/saslauthd saslauthd -a ${cfg.mechanism} -O ${pkgs.writeText "saslauthd.conf" cfg.config}";
+ Type = "forking";
+ PIDFile = "/run/saslauthd/saslauthd.pid";
+ Restart = "always";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/system/uptimed.nix b/nixpkgs/nixos/modules/services/system/uptimed.nix
new file mode 100644
index 00000000000..3c9978ab226
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/uptimed.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.uptimed;
+ stateDir = "/var/spool/uptimed";
+in
+{
+ options = {
+ services.uptimed = {
+ enable = mkOption {
+ default = false;
+ description = ''
+ Enable <literal>uptimed</literal>, allowing you to track
+ your highest uptimes.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.uptimed = {
+ description = "Uptimed daemon user";
+ home = stateDir;
+ createHome = true;
+ uid = config.ids.uids.uptimed;
+ };
+
+ systemd.services.uptimed = {
+ unitConfig.Documentation = "man:uptimed(8) man:uprecords(1)";
+ description = "uptimed service";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Restart = "on-failure";
+ User = "uptimed";
+ Nice = 19;
+ IOSchedulingClass = "idle";
+ PrivateTmp = "yes";
+ PrivateNetwork = "yes";
+ NoNewPrivileges = "yes";
+ ReadWriteDirectories = stateDir;
+ InaccessibleDirectories = "/home";
+ ExecStart = "${pkgs.uptimed}/sbin/uptimed -f -p ${stateDir}/pid";
+ };
+
+ preStart = ''
+ if ! test -f ${stateDir}/bootid ; then
+ ${pkgs.uptimed}/sbin/uptimed -b
+ fi
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/deluge.nix b/nixpkgs/nixos/modules/services/torrent/deluge.nix
new file mode 100644
index 00000000000..0c72505395d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/deluge.nix
@@ -0,0 +1,254 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.deluge;
+ cfg_web = config.services.deluge.web;
+
+ openFilesLimit = 4096;
+ listenPortsDefault = [ 6881 6889 ];
+
+ listToRange = x: { from = elemAt x 0; to = elemAt x 1; };
+
+ configDir = "${cfg.dataDir}/.config/deluge";
+ configFile = pkgs.writeText "core.conf" (builtins.toJSON cfg.config);
+ declarativeLockFile = "${configDir}/.declarative";
+
+ preStart = if cfg.declarative then ''
+ if [ -e ${declarativeLockFile} ]; then
+ # Was declarative before, no need to back up anything
+ ln -sf ${configFile} ${configDir}/core.conf
+ ln -sf ${cfg.authFile} ${configDir}/auth
+ else
+ # Declarative for the first time, backup stateful files
+ ln -sb --suffix=.stateful ${configFile} ${configDir}/core.conf
+ ln -sb --suffix=.stateful ${cfg.authFile} ${configDir}/auth
+ echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
+ > ${declarativeLockFile}
+ fi
+ '' else ''
+ if [ -e ${declarativeLockFile} ]; then
+ rm ${declarativeLockFile}
+ fi
+ '';
+in {
+ options = {
+ services = {
+ deluge = {
+ enable = mkEnableOption "Deluge daemon";
+
+ openFilesLimit = mkOption {
+ default = openFilesLimit;
+ description = ''
+ Number of files to allow deluged to open.
+ '';
+ };
+
+ config = mkOption {
+ type = types.attrs;
+ default = {};
+ example = literalExample ''
+ {
+ download_location = "/srv/torrents/";
+ max_upload_speed = "1000.0";
+ share_ratio_limit = "2.0";
+ allow_remote = true;
+ daemon_port = 58846;
+ listen_ports = [ ${toString listenPortsDefault} ];
+ }
+ '';
+ description = ''
+ Deluge core configuration for the core.conf file. Only has an effect
+ when <option>services.deluge.declarative</option> is set to
+ <literal>true</literal>. String values must be quoted, integer and
+ boolean values must not. See
+ <link xlink:href="https://git.deluge-torrent.org/deluge/tree/deluge/core/preferencesmanager.py#n41"/>
+ for the availaible options.
+ '';
+ };
+
+ declarative = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use a declarative deluge configuration.
+ Only if set to <literal>true</literal>, the options
+ <option>services.deluge.config</option>,
+ <option>services.deluge.openFirewall</option> and
+ <option>services.deluge.authFile</option> will be
+ applied.
+ '';
+ };
+
+ openFirewall = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to open the firewall for the ports in
+ <option>services.deluge.config.listen_ports</option>. It only takes effet if
+ <option>services.deluge.declarative</option> is set to
+ <literal>true</literal>.
+
+ It does NOT apply to the daemon port nor the web UI port. To access those
+ ports secuerly check the documentation
+ <link xlink:href="https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient#CreateSSHTunnel"/>
+ or use a VPN or configure certificates for deluge.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/deluge";
+ description = ''
+ The directory where deluge will create files.
+ '';
+ };
+
+ authFile = mkOption {
+ type = types.path;
+ example = "/run/keys/deluge-auth";
+ description = ''
+ The file managing the authentication for deluge, the format of this
+ file is straightforward, each line contains a
+ username:password:level tuple in plaintext. It only has an effect
+ when <option>services.deluge.declarative</option> is set to
+ <literal>true</literal>.
+ See <link xlink:href="https://dev.deluge-torrent.org/wiki/UserGuide/Authentication"/> for
+ more informations.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "deluge";
+ description = ''
+ User account under which deluge runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "deluge";
+ description = ''
+ Group under which deluge runs.
+ '';
+ };
+
+ extraPackages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = ''
+ Extra packages available at runtime to enable Deluge's plugins. For example,
+ extraction utilities are required for the built-in "Extractor" plugin.
+ This always contains unzip, gnutar, xz, p7zip and bzip2.
+ '';
+ };
+ };
+
+ deluge.web = {
+ enable = mkEnableOption "Deluge Web daemon";
+
+ port = mkOption {
+ type = types.port;
+ default = 8112;
+ description = ''
+ Deluge web UI port.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for deluge web daemon
+ '';
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ # Provide a default set of `extraPackages`.
+ services.deluge.extraPackages = with pkgs; [ unzip gnutar xz p7zip bzip2 ];
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group}"
+ "d '${cfg.dataDir}/.config' 0770 ${cfg.user} ${cfg.group}"
+ "d '${cfg.dataDir}/.config/deluge' 0770 ${cfg.user} ${cfg.group}"
+ ]
+ ++ optional (cfg.config ? download_location)
+ "d '${cfg.config.download_location}' 0770 ${cfg.user} ${cfg.group}"
+ ++ optional (cfg.config ? torrentfiles_location)
+ "d '${cfg.config.torrentfiles_location}' 0770 ${cfg.user} ${cfg.group}"
+ ++ optional (cfg.config ? move_completed_path)
+ "d '${cfg.config.move_completed_path}' 0770 ${cfg.user} ${cfg.group}";
+
+ systemd.services.deluged = {
+ after = [ "network.target" ];
+ description = "Deluge BitTorrent Daemon";
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.deluge ] ++ cfg.extraPackages;
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.deluge}/bin/deluged \
+ --do-not-daemonize \
+ --config ${configDir}
+ '';
+ # To prevent "Quit & shutdown daemon" from working; we want systemd to
+ # manage it!
+ Restart = "on-success";
+ User = cfg.user;
+ Group = cfg.group;
+ UMask = "0002";
+ LimitNOFILE = cfg.openFilesLimit;
+ };
+ preStart = preStart;
+ };
+
+ systemd.services.delugeweb = mkIf cfg_web.enable {
+ after = [ "network.target" "deluged.service"];
+ requires = [ "deluged.service" ];
+ description = "Deluge BitTorrent WebUI";
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.deluge ];
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.deluge}/bin/deluge-web \
+ --config ${configDir} \
+ --port ${toString cfg.web.port}
+ '';
+ User = cfg.user;
+ Group = cfg.group;
+ };
+ };
+
+ networking.firewall = mkMerge [
+ (mkIf (cfg.declarative && cfg.openFirewall && !(cfg.config.random_port or true)) {
+ allowedTCPPortRanges = singleton (listToRange (cfg.config.listen_ports or listenPortsDefault));
+ allowedUDPPortRanges = singleton (listToRange (cfg.config.listen_ports or listenPortsDefault));
+ })
+ (mkIf (cfg.web.openFirewall) {
+ allowedTCPPorts = [ cfg.web.port ];
+ })
+ ];
+
+ environment.systemPackages = [ pkgs.deluge ];
+
+ users.users = mkIf (cfg.user == "deluge") {
+ deluge = {
+ group = cfg.group;
+ uid = config.ids.uids.deluge;
+ home = cfg.dataDir;
+ description = "Deluge Daemon user";
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "deluge") {
+ deluge = {
+ gid = config.ids.gids.deluge;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/flexget.nix b/nixpkgs/nixos/modules/services/torrent/flexget.nix
new file mode 100644
index 00000000000..6ac85f8fa17
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/flexget.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.flexget;
+ pkg = pkgs.flexget;
+ ymlFile = pkgs.writeText "flexget.yml" ''
+ ${cfg.config}
+
+ ${optionalString cfg.systemScheduler "schedules: no"}
+'';
+ configFile = "${toString cfg.homeDir}/flexget.yml";
+in {
+ options = {
+ services.flexget = {
+ enable = mkEnableOption "Run FlexGet Daemon";
+
+ user = mkOption {
+ default = "deluge";
+ example = "some_user";
+ type = types.str;
+ description = "The user under which to run flexget.";
+ };
+
+ homeDir = mkOption {
+ default = "/var/lib/deluge";
+ example = "/home/flexget";
+ type = types.path;
+ description = "Where files live.";
+ };
+
+ interval = mkOption {
+ default = "10m";
+ example = "1h";
+ type = types.str;
+ description = "When to perform a <command>flexget</command> run. See <command>man 7 systemd.time</command> for the format.";
+ };
+
+ systemScheduler = mkOption {
+ default = true;
+ example = "false";
+ type = types.bool;
+ description = "When true, execute the runs via the flexget-runner.timer. If false, you have to specify the settings yourself in the YML file.";
+ };
+
+ config = mkOption {
+ default = "";
+ type = types.lines;
+ description = "The YAML configuration for FlexGet.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkg ];
+
+ systemd.services = {
+ flexget = {
+ description = "FlexGet Daemon";
+ path = [ pkg ];
+ serviceConfig = {
+ User = cfg.user;
+ Environment = "TZ=${config.time.timeZone}";
+ ExecStartPre = "${pkgs.coreutils}/bin/install -m644 ${ymlFile} ${configFile}";
+ ExecStart = "${pkg}/bin/flexget -c ${configFile} daemon start";
+ ExecStop = "${pkg}/bin/flexget -c ${configFile} daemon stop";
+ ExecReload = "${pkg}/bin/flexget -c ${configFile} daemon reload";
+ Restart = "on-failure";
+ PrivateTmp = true;
+ WorkingDirectory = toString cfg.homeDir;
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ flexget-runner = mkIf cfg.systemScheduler {
+ description = "FlexGet Runner";
+ after = [ "flexget.service" ];
+ wants = [ "flexget.service" ];
+ serviceConfig = {
+ User = cfg.user;
+ ExecStart = "${pkg}/bin/flexget -c ${configFile} execute";
+ PrivateTmp = true;
+ WorkingDirectory = toString cfg.homeDir;
+ };
+ };
+ };
+
+ systemd.timers.flexget-runner = mkIf cfg.systemScheduler {
+ description = "Run FlexGet every ${cfg.interval}";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnBootSec = "5m";
+ OnUnitInactiveSec = cfg.interval;
+ Unit = "flexget-runner.service";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/magnetico.nix b/nixpkgs/nixos/modules/services/torrent/magnetico.nix
new file mode 100644
index 00000000000..02fa2ac0750
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/magnetico.nix
@@ -0,0 +1,214 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.magnetico;
+
+ dataDir = "/var/lib/magnetico";
+
+ credFile = with cfg.web;
+ if credentialsFile != null
+ then credentialsFile
+ else pkgs.writeText "magnetico-credentials"
+ (concatStrings (mapAttrsToList
+ (user: hash: "${user}:${hash}\n")
+ cfg.web.credentials));
+
+ # default options in magneticod/main.go
+ dbURI = concatStrings
+ [ "sqlite3://${dataDir}/database.sqlite3"
+ "?_journal_mode=WAL"
+ "&_busy_timeout=3000"
+ "&_foreign_keys=true"
+ ];
+
+ crawlerArgs = with cfg.crawler; escapeShellArgs
+ ([ "--database=${dbURI}"
+ "--indexer-addr=${address}:${toString port}"
+ "--indexer-max-neighbors=${toString maxNeighbors}"
+ "--leech-max-n=${toString maxLeeches}"
+ ] ++ extraOptions);
+
+ webArgs = with cfg.web; escapeShellArgs
+ ([ "--database=${dbURI}"
+ (if (cfg.web.credentialsFile != null || cfg.web.credentials != { })
+ then "--credentials=${toString credFile}"
+ else "--no-auth")
+ ] ++ extraOptions);
+
+in {
+
+ ###### interface
+
+ options.services.magnetico = {
+ enable = mkEnableOption "Magnetico, Bittorrent DHT crawler";
+
+ crawler.address = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ example = "1.2.3.4";
+ description = ''
+ Address to be used for indexing DHT nodes.
+ '';
+ };
+
+ crawler.port = mkOption {
+ type = types.port;
+ default = 0;
+ description = ''
+ Port to be used for indexing DHT nodes.
+ This port should be added to
+ <option>networking.firewall.allowedTCPPorts</option>.
+ '';
+ };
+
+ crawler.maxNeighbors = mkOption {
+ type = types.ints.positive;
+ default = 1000;
+ description = ''
+ Maximum number of simultaneous neighbors of an indexer.
+ Be careful changing this number: high values can very
+ easily cause your network to be congested or even crash
+ your router.
+ '';
+ };
+
+ crawler.maxLeeches = mkOption {
+ type = types.ints.positive;
+ default = 200;
+ description = ''
+ Maximum number of simultaneous leeches.
+ '';
+ };
+
+ crawler.extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Extra command line arguments to pass to magneticod.
+ '';
+ };
+
+ web.address = mkOption {
+ type = types.str;
+ default = "localhost";
+ example = "1.2.3.4";
+ description = ''
+ Address the web interface will listen to.
+ '';
+ };
+
+ web.port = mkOption {
+ type = types.port;
+ default = 8080;
+ description = ''
+ Port the web interface will listen to.
+ '';
+ };
+
+ web.credentials = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = lib.literalExample ''
+ {
+ myuser = "$2y$12$YE01LZ8jrbQbx6c0s2hdZO71dSjn2p/O9XsYJpz.5968yCysUgiaG";
+ }
+ '';
+ description = ''
+ The credentials to access the web interface, in case authentication is
+ enabled, in the format <literal>username:hash</literal>. If unset no
+ authentication will be required.
+
+ Usernames must start with a lowercase ([a-z]) ASCII character, might
+ contain non-consecutive underscores except at the end, and consists of
+ small-case a-z characters and digits 0-9. The
+ <command>htpasswd</command> tool from the <package>apacheHttpd
+ </package> package may be used to generate the hash: <command>htpasswd
+ -bnBC 12 username password</command>
+
+ <warning>
+ <para>
+ The hashes will be stored world-readable in the nix store.
+ Consider using the <literal>credentialsFile</literal> option if you
+ don't want this.
+ </para>
+ </warning>
+ '';
+ };
+
+ web.credentialsFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The path to the file holding the credentials to access the web
+ interface. If unset no authentication will be required.
+
+ The file must constain user names and password hashes in the format
+ <literal>username:hash </literal>, one for each line. Usernames must
+ start with a lowecase ([a-z]) ASCII character, might contain
+ non-consecutive underscores except at the end, and consists of
+ small-case a-z characters and digits 0-9.
+ The <command>htpasswd</command> tool from the <package>apacheHttpd
+ </package> package may be used to generate the hash:
+ <command>htpasswd -bnBC 12 username password</command>
+ '';
+ };
+
+ web.extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Extra command line arguments to pass to magneticow.
+ '';
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users.magnetico = {
+ description = "Magnetico daemons user";
+ };
+
+ systemd.services.magneticod = {
+ description = "Magnetico DHT crawler";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target" ];
+
+ serviceConfig = {
+ User = "magnetico";
+ Restart = "on-failure";
+ ExecStart = "${pkgs.magnetico}/bin/magneticod ${crawlerArgs}";
+ };
+ };
+
+ systemd.services.magneticow = {
+ description = "Magnetico web interface";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target" "magneticod.service"];
+
+ serviceConfig = {
+ User = "magnetico";
+ StateDirectory = "magnetico";
+ Restart = "on-failure";
+ ExecStart = "${pkgs.magnetico}/bin/magneticow ${webArgs}";
+ };
+ };
+
+ assertions =
+ [
+ {
+ assertion = cfg.web.credentialsFile != null || cfg.web.credentials != { };
+ message = ''
+ The options services.magnetico.web.credentialsFile and
+ services.magnetico.web.credentials are mutually exclusives.
+ '';
+ }
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/opentracker.nix b/nixpkgs/nixos/modules/services/torrent/opentracker.nix
new file mode 100644
index 00000000000..74f443381d9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/opentracker.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.opentracker;
+in {
+ options.services.opentracker = {
+ enable = mkEnableOption "opentracker";
+
+ package = mkOption {
+ type = types.package;
+ description = ''
+ opentracker package to use
+ '';
+ default = pkgs.opentracker;
+ defaultText = "pkgs.opentracker";
+ };
+
+ extraOptions = mkOption {
+ type = types.separatedString " ";
+ description = ''
+ Configuration Arguments for opentracker
+ See https://erdgeist.org/arts/software/opentracker/ for all params
+ '';
+ default = "";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+
+ systemd.services.opentracker = {
+ description = "opentracker server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ restartIfChanged = true;
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/opentracker ${cfg.extraOptions}";
+ PrivateTmp = true;
+ WorkingDirectory = "/var/empty";
+ # By default opentracker drops all privileges and runs in chroot after starting up as root.
+ };
+ };
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/torrent/peerflix.nix b/nixpkgs/nixos/modules/services/torrent/peerflix.nix
new file mode 100644
index 00000000000..a74f6598432
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/peerflix.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.peerflix;
+
+ configFile = pkgs.writeText "peerflix-config.json" ''
+ {
+ "connections": 50,
+ "tmp": "${cfg.downloadDir}"
+ }
+ '';
+
+in {
+
+ ###### interface
+
+ options.services.peerflix = {
+ enable = mkOption {
+ description = "Whether to enable peerflix service.";
+ default = false;
+ type = types.bool;
+ };
+
+ stateDir = mkOption {
+ description = "Peerflix state directory.";
+ default = "/var/lib/peerflix";
+ type = types.path;
+ };
+
+ downloadDir = mkOption {
+ description = "Peerflix temporary download directory.";
+ default = "${cfg.stateDir}/torrents";
+ type = types.path;
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.stateDir}' - peerflix - - -"
+ ];
+
+ systemd.services.peerflix = {
+ description = "Peerflix Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment.HOME = cfg.stateDir;
+
+ preStart = ''
+ mkdir -p "${cfg.stateDir}"/{torrents,.config/peerflix-server}
+ ln -fs "${configFile}" "${cfg.stateDir}/.config/peerflix-server/config.json"
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkgs.nodePackages.peerflix-server}/bin/peerflix-server";
+ User = "peerflix";
+ };
+ };
+
+ users.users.peerflix.uid = config.ids.uids.peerflix;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/transmission.nix b/nixpkgs/nixos/modules/services/torrent/transmission.nix
new file mode 100644
index 00000000000..7409eb8cdcb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/transmission.nix
@@ -0,0 +1,185 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.transmission;
+ apparmor = config.security.apparmor.enable;
+
+ homeDir = cfg.home;
+ downloadDir = "${homeDir}/Downloads";
+ incompleteDir = "${homeDir}/.incomplete";
+
+ settingsDir = "${homeDir}/.config/transmission-daemon";
+ settingsFile = pkgs.writeText "settings.json" (builtins.toJSON fullSettings);
+
+ # for users in group "transmission" to have access to torrents
+ fullSettings = { umask = 2; download-dir = downloadDir; incomplete-dir = incompleteDir; } // cfg.settings;
+
+ # Directories transmission expects to exist and be ug+rwx.
+ directoriesToManage = [ homeDir settingsDir fullSettings.download-dir fullSettings.incomplete-dir ];
+
+ preStart = pkgs.writeScript "transmission-pre-start" ''
+ #!${pkgs.runtimeShell}
+ set -ex
+ for DIR in ${escapeShellArgs directoriesToManage}; do
+ mkdir -p "$DIR"
+ chmod 770 "$DIR"
+ done
+ cp -f ${settingsFile} ${settingsDir}/settings.json
+ '';
+in
+{
+ options = {
+ services.transmission = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether or not to enable the headless Transmission BitTorrent daemon.
+
+ Transmission daemon can be controlled via the RPC interface using
+ transmission-remote or the WebUI (http://localhost:9091/ by default).
+
+ Torrents are downloaded to ${downloadDir} by default and are
+ accessible to users in the "transmission" group.
+ '';
+ };
+
+ settings = mkOption {
+ type = types.attrs;
+ default =
+ {
+ download-dir = downloadDir;
+ incomplete-dir = incompleteDir;
+ incomplete-dir-enabled = true;
+ };
+ example =
+ {
+ download-dir = "/srv/torrents/";
+ incomplete-dir = "/srv/torrents/.incomplete/";
+ incomplete-dir-enabled = true;
+ rpc-whitelist = "127.0.0.1,192.168.*.*";
+ };
+ description = ''
+ Attribute set whos fields overwrites fields in settings.json (each
+ time the service starts). String values must be quoted, integer and
+ boolean values must not.
+
+ See https://github.com/transmission/transmission/wiki/Editing-Configuration-Files
+ for documentation.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 9091;
+ description = "TCP port number to run the RPC/web interface.";
+ };
+
+ home = mkOption {
+ type = types.path;
+ default = "/var/lib/transmission";
+ description = ''
+ The directory where transmission will create files.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "transmission";
+ description = "User account under which Transmission runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "transmission";
+ description = "Group account under which Transmission runs.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.transmission = {
+ description = "Transmission BitTorrent Service";
+ after = [ "network.target" ] ++ optional apparmor "apparmor.service";
+ requires = mkIf apparmor [ "apparmor.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ # 1) Only the "transmission" user and group have access to torrents.
+ # 2) Optionally update/force specific fields into the configuration file.
+ serviceConfig.ExecStartPre = preStart;
+ serviceConfig.ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --port ${toString config.services.transmission.port}";
+ serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ serviceConfig.User = cfg.user;
+ serviceConfig.Group = cfg.group;
+ # NOTE: transmission has an internal umask that also must be set (in settings.json)
+ serviceConfig.UMask = "0002";
+ };
+
+ # It's useful to have transmission in path, e.g. for remote control
+ environment.systemPackages = [ pkgs.transmission ];
+
+ users.users = optionalAttrs (cfg.user == "transmission") (singleton
+ { name = "transmission";
+ group = cfg.group;
+ uid = config.ids.uids.transmission;
+ description = "Transmission BitTorrent user";
+ home = homeDir;
+ createHome = true;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "transmission") (singleton
+ { name = "transmission";
+ gid = config.ids.gids.transmission;
+ });
+
+ # AppArmor profile
+ security.apparmor.profiles = mkIf apparmor [
+ (pkgs.writeText "apparmor-transmission-daemon" ''
+ #include <tunables/global>
+
+ ${pkgs.transmission}/bin/transmission-daemon {
+ #include <abstractions/base>
+ #include <abstractions/nameservice>
+
+ ${getLib pkgs.glibc}/lib/*.so mr,
+ ${getLib pkgs.libevent}/lib/libevent*.so* mr,
+ ${getLib pkgs.curl}/lib/libcurl*.so* mr,
+ ${getLib pkgs.openssl}/lib/libssl*.so* mr,
+ ${getLib pkgs.openssl}/lib/libcrypto*.so* mr,
+ ${getLib pkgs.zlib}/lib/libz*.so* mr,
+ ${getLib pkgs.libssh2}/lib/libssh2*.so* mr,
+ ${getLib pkgs.systemd}/lib/libsystemd*.so* mr,
+ ${getLib pkgs.xz}/lib/liblzma*.so* mr,
+ ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so* mr,
+ ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so* mr,
+ ${getLib pkgs.nghttp2}/lib/libnghttp2*.so* mr,
+ ${getLib pkgs.c-ares}/lib/libcares*.so* mr,
+ ${getLib pkgs.libcap}/lib/libcap*.so* mr,
+ ${getLib pkgs.attr}/lib/libattr*.so* mr,
+ ${getLib pkgs.lz4}/lib/liblz4*.so* mr,
+ ${getLib pkgs.libkrb5}/lib/lib*.so* mr,
+ ${getLib pkgs.keyutils}/lib/libkeyutils*.so* mr,
+ ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr,
+ ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr,
+ ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr,
+
+ @{PROC}/sys/kernel/random/uuid r,
+ @{PROC}/sys/vm/overcommit_memory r,
+
+ ${pkgs.openssl.out}/etc/** r,
+ ${pkgs.transmission}/share/transmission/** r,
+
+ owner ${settingsDir}/** rw,
+
+ ${fullSettings.download-dir}/** rw,
+ ${optionalString fullSettings.incomplete-dir-enabled ''
+ ${fullSettings.incomplete-dir}/** rw,
+ ''}
+ }
+ '')
+ ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/ttys/agetty.nix b/nixpkgs/nixos/modules/services/ttys/agetty.nix
new file mode 100644
index 00000000000..f127d8a0276
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/ttys/agetty.nix
@@ -0,0 +1,118 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ autologinArg = optionalString (config.services.mingetty.autologinUser != null) "--autologin ${config.services.mingetty.autologinUser}";
+ gettyCmd = extraArgs: "@${pkgs.utillinux}/sbin/agetty agetty --login-program ${pkgs.shadow}/bin/login ${autologinArg} ${extraArgs}";
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.mingetty = {
+
+ autologinUser = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Username of the account that will be automatically logged in at the console.
+ If unspecified, a login prompt is shown as usual.
+ '';
+ };
+
+ greetingLine = mkOption {
+ type = types.str;
+ description = ''
+ Welcome line printed by mingetty.
+ The default shows current NixOS version label, machine type and tty.
+ '';
+ };
+
+ helpLine = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Help line printed by mingetty below the welcome line.
+ Used by the installation CD to give some hints on
+ how to proceed.
+ '';
+ };
+
+ serialSpeed = mkOption {
+ type = types.listOf types.int;
+ default = [ 115200 57600 38400 9600 ];
+ example = [ 38400 9600 ];
+ description = ''
+ Bitrates to allow for agetty's listening on serial ports. Listing more
+ bitrates gives more interoperability but at the cost of long delays
+ for getting a sync on the line.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+ # Note: this is set here rather than up there so that changing
+ # nixos.label would not rebuild manual pages
+ services.mingetty.greetingLine = mkDefault ''<<< Welcome to NixOS ${config.system.nixos.label} (\m) - \l >>>'';
+
+ systemd.services."getty@" =
+ { serviceConfig.ExecStart = [
+ "" # override upstream default with an empty ExecStart
+ (gettyCmd "--noclear --keep-baud %I 115200,38400,9600 $TERM")
+ ];
+ restartIfChanged = false;
+ };
+
+ systemd.services."serial-getty@" =
+ let speeds = concatStringsSep "," (map toString config.services.mingetty.serialSpeed); in
+ { serviceConfig.ExecStart = [
+ "" # override upstream default with an empty ExecStart
+ (gettyCmd "%I ${speeds} $TERM")
+ ];
+ restartIfChanged = false;
+ };
+
+ systemd.services."container-getty@" =
+ { serviceConfig.ExecStart = [
+ "" # override upstream default with an empty ExecStart
+ (gettyCmd "--noclear --keep-baud pts/%I 115200,38400,9600 $TERM")
+ ];
+ restartIfChanged = false;
+ };
+
+ systemd.services.console-getty =
+ { serviceConfig.ExecStart = [
+ "" # override upstream default with an empty ExecStart
+ (gettyCmd "--noclear --keep-baud console 115200,38400,9600 $TERM")
+ ];
+ serviceConfig.Restart = "always";
+ restartIfChanged = false;
+ enable = mkDefault config.boot.isContainer;
+ };
+
+ environment.etc = singleton
+ { # Friendly greeting on the virtual consoles.
+ source = pkgs.writeText "issue" ''
+
+ ${config.services.mingetty.greetingLine}
+ ${config.services.mingetty.helpLine}
+
+ '';
+ target = "issue";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/ttys/gpm.nix b/nixpkgs/nixos/modules/services/ttys/gpm.nix
new file mode 100644
index 00000000000..308a6d3643a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/ttys/gpm.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.gpm;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.gpm = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable GPM, the General Purpose Mouse daemon,
+ which enables mouse support in virtual consoles.
+ '';
+ };
+
+ protocol = mkOption {
+ type = types.str;
+ default = "ps/2";
+ description = "Mouse protocol to use.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.gpm =
+ { description = "Console Mouse Daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "dev-input-mice.device" ];
+ after = [ "dev-input-mice.device" ];
+
+ serviceConfig.ExecStart = "@${pkgs.gpm}/sbin/gpm gpm -m /dev/input/mice -t ${cfg.protocol}";
+ serviceConfig.Type = "forking";
+ serviceConfig.PIDFile = "/run/gpm.pid";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/ttys/kmscon.nix b/nixpkgs/nixos/modules/services/ttys/kmscon.nix
new file mode 100644
index 00000000000..dc37f9bee4b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/ttys/kmscon.nix
@@ -0,0 +1,100 @@
+{ config, pkgs, lib, ... }:
+let
+ inherit (lib) mkOption types mkIf;
+
+ cfg = config.services.kmscon;
+
+ autologinArg = lib.optionalString (cfg.autologinUser != null) "-f ${cfg.autologinUser}";
+
+ configDir = pkgs.writeTextFile { name = "kmscon-config"; destination = "/kmscon.conf"; text = cfg.extraConfig; };
+in {
+ options = {
+ services.kmscon = {
+ enable = mkOption {
+ description = ''
+ Use kmscon as the virtual console instead of gettys.
+ kmscon is a kms/dri-based userspace virtual terminal implementation.
+ It supports a richer feature set than the standard linux console VT,
+ including full unicode support, and when the video card supports drm
+ should be much faster.
+ '';
+ type = types.bool;
+ default = false;
+ };
+
+ hwRender = mkOption {
+ description = "Whether to use 3D hardware acceleration to render the console.";
+ type = types.bool;
+ default = false;
+ };
+
+ extraConfig = mkOption {
+ description = "Extra contents of the kmscon.conf file.";
+ type = types.lines;
+ default = "";
+ example = "font-size=14";
+ };
+
+ extraOptions = mkOption {
+ description = "Extra flags to pass to kmscon.";
+ type = types.separatedString " ";
+ default = "";
+ example = "--term xterm-256color";
+ };
+
+ autologinUser = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Username of the account that will be automatically logged in at the console.
+ If unspecified, a login prompt is shown as usual.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # Largely copied from unit provided with kmscon source
+ systemd.units."kmsconvt@.service".text = ''
+ [Unit]
+ Description=KMS System Console on %I
+ Documentation=man:kmscon(1)
+ After=systemd-user-sessions.service
+ After=plymouth-quit-wait.service
+ After=systemd-logind.service
+ After=systemd-vconsole-setup.service
+ Requires=systemd-logind.service
+ Before=getty.target
+ Conflicts=getty@%i.service
+ OnFailure=getty@%i.service
+ IgnoreOnIsolate=yes
+ ConditionPathExists=/dev/tty0
+
+ [Service]
+ ExecStart=
+ ExecStart=${pkgs.kmscon}/bin/kmscon "--vt=%I" ${cfg.extraOptions} --seats=seat0 --no-switchvt --configdir ${configDir} --login -- ${pkgs.shadow}/bin/login -p ${autologinArg}
+ UtmpIdentifier=%I
+ TTYPath=/dev/%I
+ TTYReset=yes
+ TTYVHangup=yes
+ TTYVTDisallocate=yes
+
+ X-RestartIfChanged=false
+ '';
+
+ systemd.units."autovt@.service".unit = pkgs.runCommand "unit" { preferLocalBuild = true; }
+ ''
+ mkdir -p $out
+ ln -s ${config.systemd.units."kmsconvt@.service".unit}/kmsconvt@.service $out/autovt@.service
+ '';
+
+ systemd.services.systemd-vconsole-setup.enable = false;
+
+ services.kmscon.extraConfig = mkIf cfg.hwRender ''
+ drm
+ hwaccel
+ '';
+
+ hardware.opengl.enable = mkIf cfg.hwRender true;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix
new file mode 100644
index 00000000000..59185fdbd36
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -0,0 +1,197 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.confluence;
+
+ pkg = cfg.package.override (optionalAttrs cfg.sso.enable {
+ enableSSO = cfg.sso.enable;
+ crowdProperties = ''
+ application.name ${cfg.sso.applicationName}
+ application.password ${cfg.sso.applicationPassword}
+ application.login.url ${cfg.sso.crowd}/console/
+
+ crowd.server.url ${cfg.sso.crowd}/services/
+ crowd.base.url ${cfg.sso.crowd}/
+
+ session.isauthenticated session.isauthenticated
+ session.tokenkey session.tokenkey
+ session.validationinterval ${toString cfg.sso.validationInterval}
+ session.lastvalidation session.lastvalidation
+ '';
+ });
+
+in
+
+{
+ options = {
+ services.confluence = {
+ enable = mkEnableOption "Atlassian Confluence service";
+
+ user = mkOption {
+ type = types.str;
+ default = "confluence";
+ description = "User which runs confluence.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "confluence";
+ description = "Group which runs confluence.";
+ };
+
+ home = mkOption {
+ type = types.str;
+ default = "/var/lib/confluence";
+ description = "Home directory of the confluence instance.";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Address to listen on.";
+ };
+
+ listenPort = mkOption {
+ type = types.int;
+ default = 8090;
+ description = "Port to listen on.";
+ };
+
+ catalinaOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "-Xms1024m" "-Xmx2048m" "-Dconfluence.disable.peopledirectory.all=true" ];
+ description = "Java options to pass to catalina/tomcat.";
+ };
+
+ proxy = {
+ enable = mkEnableOption "proxy support";
+
+ name = mkOption {
+ type = types.str;
+ example = "confluence.example.com";
+ description = "Virtual hostname at the proxy";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 443;
+ example = 80;
+ description = "Port used at the proxy";
+ };
+
+ scheme = mkOption {
+ type = types.str;
+ default = "https";
+ example = "http";
+ description = "Protocol used at the proxy.";
+ };
+ };
+
+ sso = {
+ enable = mkEnableOption "SSO with Atlassian Crowd";
+
+ crowd = mkOption {
+ type = types.str;
+ example = "http://localhost:8095/crowd";
+ description = "Crowd Base URL without trailing slash";
+ };
+
+ applicationName = mkOption {
+ type = types.str;
+ example = "jira";
+ description = "Exact name of this Confluence instance in Crowd";
+ };
+
+ applicationPassword = mkOption {
+ type = types.str;
+ description = "Application password of this Confluence instance in Crowd";
+ };
+
+ validationInterval = mkOption {
+ type = types.int;
+ default = 2;
+ example = 0;
+ description = ''
+ Set to 0, if you want authentication checks to occur on each
+ request. Otherwise set to the number of minutes between request
+ to validate if the user is logged in or out of the Crowd SSO
+ server. Setting this value to 1 or higher will increase the
+ performance of Crowd's integration.
+ '';
+ };
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.atlassian-confluence;
+ defaultText = "pkgs.atlassian-confluence";
+ description = "Atlassian Confluence package to use.";
+ };
+
+ jrePackage = mkOption {
+ type = types.package;
+ default = pkgs.oraclejre8;
+ defaultText = "pkgs.oraclejre8";
+ description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.${cfg.user} = {
+ isSystemUser = true;
+ group = cfg.group;
+ };
+
+ users.groups.${cfg.group} = {};
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.home}' - ${cfg.user} - - -"
+ "d /run/confluence - - - - -"
+
+ "L+ /run/confluence/home - - - - ${cfg.home}"
+ "L+ /run/confluence/logs - - - - ${cfg.home}/logs"
+ "L+ /run/confluence/temp - - - - ${cfg.home}/temp"
+ "L+ /run/confluence/work - - - - ${cfg.home}/work"
+ "L+ /run/confluence/server.xml - - - - ${cfg.home}/server.xml"
+ ];
+
+ systemd.services.confluence = {
+ description = "Atlassian Confluence";
+
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+
+ path = [ cfg.jrePackage pkgs.bash ];
+
+ environment = {
+ CONF_USER = cfg.user;
+ JAVA_HOME = "${cfg.jrePackage}";
+ CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
+ };
+
+ preStart = ''
+ mkdir -p ${cfg.home}/{logs,work,temp,deploy}
+
+ sed -e 's,port="8090",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
+ '' + (lib.optionalString cfg.proxy.enable ''
+ -e 's,protocol="org.apache.coyote.http11.Http11NioProtocol",protocol="org.apache.coyote.http11.Http11NioProtocol" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}",' \
+ '') + ''
+ ${pkg}/conf/server.xml.dist > ${cfg.home}/server.xml
+ '';
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ PrivateTmp = true;
+ ExecStart = "${pkg}/bin/start-confluence.sh -fg";
+ ExecStop = "${pkg}/bin/stop-confluence.sh";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix
new file mode 100644
index 00000000000..ceab656b15e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -0,0 +1,164 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.crowd;
+
+ pkg = cfg.package.override {
+ home = cfg.home;
+ port = cfg.listenPort;
+ openidPassword = cfg.openidPassword;
+ } // (optionalAttrs cfg.proxy.enable {
+ proxyUrl = "${cfg.proxy.scheme}://${cfg.proxy.name}:${toString cfg.proxy.port}";
+ });
+
+in
+
+{
+ options = {
+ services.crowd = {
+ enable = mkEnableOption "Atlassian Crowd service";
+
+ user = mkOption {
+ type = types.str;
+ default = "crowd";
+ description = "User which runs Crowd.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "crowd";
+ description = "Group which runs Crowd.";
+ };
+
+ home = mkOption {
+ type = types.str;
+ default = "/var/lib/crowd";
+ description = "Home directory of the Crowd instance.";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Address to listen on.";
+ };
+
+ listenPort = mkOption {
+ type = types.int;
+ default = 8092;
+ description = "Port to listen on.";
+ };
+
+ openidPassword = mkOption {
+ type = types.str;
+ description = "Application password for OpenID server.";
+ };
+
+ catalinaOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "-Xms1024m" "-Xmx2048m" ];
+ description = "Java options to pass to catalina/tomcat.";
+ };
+
+ proxy = {
+ enable = mkEnableOption "reverse proxy support";
+
+ name = mkOption {
+ type = types.str;
+ example = "crowd.example.com";
+ description = "Virtual hostname at the proxy";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 443;
+ example = 80;
+ description = "Port used at the proxy";
+ };
+
+ scheme = mkOption {
+ type = types.str;
+ default = "https";
+ example = "http";
+ description = "Protocol used at the proxy.";
+ };
+
+ secure = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether the connections to the proxy should be considered secure.";
+ };
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.atlassian-crowd;
+ defaultText = "pkgs.atlassian-crowd";
+ description = "Atlassian Crowd package to use.";
+ };
+
+ jrePackage = mkOption {
+ type = types.package;
+ default = pkgs.oraclejre8;
+ defaultText = "pkgs.oraclejre8";
+ description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.${cfg.user} = {
+ isSystemUser = true;
+ group = cfg.group;
+ };
+
+ users.groups.${cfg.group} = {};
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.home}' - ${cfg.user} ${cfg.group} - -"
+ "d /run/atlassian-crowd - - - - -"
+
+ "L+ /run/atlassian-crowd/database - - - - ${cfg.home}/database"
+ "L+ /run/atlassian-crowd/logs - - - - ${cfg.home}/logs"
+ "L+ /run/atlassian-crowd/work - - - - ${cfg.home}/work"
+ "L+ /run/atlassian-crowd/server.xml - - - - ${cfg.home}/server.xml"
+ ];
+
+ systemd.services.atlassian-crowd = {
+ description = "Atlassian Crowd";
+
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+
+ path = [ cfg.jrePackage ];
+
+ environment = {
+ JAVA_HOME = "${cfg.jrePackage}";
+ CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
+ CATALINA_TMPDIR = "/tmp";
+ };
+
+ preStart = ''
+ rm -rf ${cfg.home}/work
+ mkdir -p ${cfg.home}/{logs,database,work}
+
+ sed -e 's,port="8095",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
+ '' + (lib.optionalString cfg.proxy.enable ''
+ -e 's,compression="on",compression="off" protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${boolToString cfg.proxy.secure}",' \
+ '') + ''
+ ${pkg}/apache-tomcat/conf/server.xml.dist > ${cfg.home}/server.xml
+ '';
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ PrivateTmp = true;
+ ExecStart = "${pkg}/start_crowd.sh -fg";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix b/nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix
new file mode 100644
index 00000000000..ce04982e8a9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -0,0 +1,204 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.jira;
+
+ pkg = cfg.package.override (optionalAttrs cfg.sso.enable {
+ enableSSO = cfg.sso.enable;
+ crowdProperties = ''
+ application.name ${cfg.sso.applicationName}
+ application.password ${cfg.sso.applicationPassword}
+ application.login.url ${cfg.sso.crowd}/console/
+
+ crowd.server.url ${cfg.sso.crowd}/services/
+ crowd.base.url ${cfg.sso.crowd}/
+
+ session.isauthenticated session.isauthenticated
+ session.tokenkey session.tokenkey
+ session.validationinterval ${toString cfg.sso.validationInterval}
+ session.lastvalidation session.lastvalidation
+ '';
+ });
+
+in
+
+{
+ options = {
+ services.jira = {
+ enable = mkEnableOption "Atlassian JIRA service";
+
+ user = mkOption {
+ type = types.str;
+ default = "jira";
+ description = "User which runs JIRA.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "jira";
+ description = "Group which runs JIRA.";
+ };
+
+ home = mkOption {
+ type = types.str;
+ default = "/var/lib/jira";
+ description = "Home directory of the JIRA instance.";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Address to listen on.";
+ };
+
+ listenPort = mkOption {
+ type = types.int;
+ default = 8091;
+ description = "Port to listen on.";
+ };
+
+ catalinaOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "-Xms1024m" "-Xmx2048m" ];
+ description = "Java options to pass to catalina/tomcat.";
+ };
+
+ proxy = {
+ enable = mkEnableOption "reverse proxy support";
+
+ name = mkOption {
+ type = types.str;
+ example = "jira.example.com";
+ description = "Virtual hostname at the proxy";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 443;
+ example = 80;
+ description = "Port used at the proxy";
+ };
+
+ scheme = mkOption {
+ type = types.str;
+ default = "https";
+ example = "http";
+ description = "Protocol used at the proxy.";
+ };
+
+ secure = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether the connections to the proxy should be considered secure.";
+ };
+ };
+
+ sso = {
+ enable = mkEnableOption "SSO with Atlassian Crowd";
+
+ crowd = mkOption {
+ type = types.str;
+ example = "http://localhost:8095/crowd";
+ description = "Crowd Base URL without trailing slash";
+ };
+
+ applicationName = mkOption {
+ type = types.str;
+ example = "jira";
+ description = "Exact name of this JIRA instance in Crowd";
+ };
+
+ applicationPassword = mkOption {
+ type = types.str;
+ description = "Application password of this JIRA instance in Crowd";
+ };
+
+ validationInterval = mkOption {
+ type = types.int;
+ default = 2;
+ example = 0;
+ description = ''
+ Set to 0, if you want authentication checks to occur on each
+ request. Otherwise set to the number of minutes between request
+ to validate if the user is logged in or out of the Crowd SSO
+ server. Setting this value to 1 or higher will increase the
+ performance of Crowd's integration.
+ '';
+ };
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.atlassian-jira;
+ defaultText = "pkgs.atlassian-jira";
+ description = "Atlassian JIRA package to use.";
+ };
+
+ jrePackage = mkOption {
+ type = types.package;
+ default = pkgs.oraclejre8;
+ defaultText = "pkgs.oraclejre8";
+ description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.${cfg.user} = {
+ isSystemUser = true;
+ group = cfg.group;
+ };
+
+ users.groups.${cfg.group} = {};
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.home}' - ${cfg.user} - - -"
+ "d /run/atlassian-jira - - - - -"
+
+ "L+ /run/atlassian-jira/home - - - - ${cfg.home}"
+ "L+ /run/atlassian-jira/logs - - - - ${cfg.home}/logs"
+ "L+ /run/atlassian-jira/work - - - - ${cfg.home}/work"
+ "L+ /run/atlassian-jira/temp - - - - ${cfg.home}/temp"
+ "L+ /run/atlassian-jira/server.xml - - - - ${cfg.home}/server.xml"
+ ];
+
+ systemd.services.atlassian-jira = {
+ description = "Atlassian JIRA";
+
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+
+ path = [ cfg.jrePackage pkgs.bash ];
+
+ environment = {
+ JIRA_USER = cfg.user;
+ JIRA_HOME = cfg.home;
+ JAVA_HOME = "${cfg.jrePackage}";
+ CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
+ };
+
+ preStart = ''
+ mkdir -p ${cfg.home}/{logs,work,temp,deploy}
+
+ sed -e 's,port="8080",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
+ '' + (lib.optionalString cfg.proxy.enable ''
+ -e 's,protocol="HTTP/1.1",protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${toString cfg.proxy.secure}",' \
+ '') + ''
+ ${pkg}/conf/server.xml.dist > ${cfg.home}/server.xml
+ '';
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ PrivateTmp = true;
+ ExecStart = "${pkg}/bin/start-jira.sh -fg";
+ ExecStop = "${pkg}/bin/stop-jira.sh";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/codimd.nix b/nixpkgs/nixos/modules/services/web-apps/codimd.nix
new file mode 100644
index 00000000000..7ae7cd9c52d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/codimd.nix
@@ -0,0 +1,915 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.codimd;
+
+ prettyJSON = conf:
+ pkgs.runCommand "codimd-config.json" { preferLocalBuild = true; } ''
+ echo '${builtins.toJSON conf}' | ${pkgs.jq}/bin/jq \
+ '{production:del(.[]|nulls)|del(.[][]?|nulls)}' > $out
+ '';
+in
+{
+ options.services.codimd = {
+ enable = mkEnableOption "the CodiMD Markdown Editor";
+
+ groups = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Groups to which the codimd user should be added.
+ '';
+ };
+
+ workDir = mkOption {
+ type = types.path;
+ default = "/var/lib/codimd";
+ description = ''
+ Working directory for the CodiMD service.
+ '';
+ };
+
+ configuration = {
+ debug = mkEnableOption "debug mode";
+ domain = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "codimd.org";
+ description = ''
+ Domain name for the CodiMD instance.
+ '';
+ };
+ urlPath = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/url/path/to/codimd";
+ description = ''
+ Path under which CodiMD is accessible.
+ '';
+ };
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = ''
+ Address to listen on.
+ '';
+ };
+ port = mkOption {
+ type = types.int;
+ default = 3000;
+ example = "80";
+ description = ''
+ Port to listen on.
+ '';
+ };
+ path = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/run/codimd.sock";
+ description = ''
+ Specify where a UNIX domain socket should be placed.
+ '';
+ };
+ allowOrigin = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "localhost" "codimd.org" ];
+ description = ''
+ List of domains to whitelist.
+ '';
+ };
+ useSSL = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable to use SSL server. This will also enable
+ <option>protocolUseSSL</option>.
+ '';
+ };
+ hsts = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Wheter to enable HSTS if HTTPS is also enabled.
+ '';
+ };
+ maxAgeSeconds = mkOption {
+ type = types.int;
+ default = 31536000;
+ description = ''
+ Max duration for clients to keep the HSTS status.
+ '';
+ };
+ includeSubdomains = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to include subdomains in HSTS.
+ '';
+ };
+ preload = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to allow preloading of the site's HSTS status.
+ '';
+ };
+ };
+ csp = mkOption {
+ type = types.nullOr types.attrs;
+ default = null;
+ example = literalExample ''
+ {
+ enable = true;
+ directives = {
+ scriptSrc = "trustworthy.scripts.example.com";
+ };
+ upgradeInsecureRequest = "auto";
+ addDefaults = true;
+ }
+ '';
+ description = ''
+ Specify the Content Security Policy which is passed to Helmet.
+ For configuration details see <link xlink:href="https://helmetjs.github.io/docs/csp/"
+ >https://helmetjs.github.io/docs/csp/</link>.
+ '';
+ };
+ protocolUseSSL = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable to use TLS for resource paths.
+ This only applies when <option>domain</option> is set.
+ '';
+ };
+ urlAddPort = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable to add the port to callback URLs.
+ This only applies when <option>domain</option> is set
+ and only for ports other than 80 and 443.
+ '';
+ };
+ useCDN = mkOption {
+ type = types.bool;
+ default = 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" ];
+ serviceConfig = {
+ WorkingDirectory = cfg.workDir;
+ ExecStart = "${pkgs.codimd}/bin/codimd";
+ Environment = [
+ "CMD_CONFIG_FILE=${prettyJSON cfg.configuration}"
+ "NODE_ENV=production"
+ ];
+ Restart = "always";
+ User = "codimd";
+ PrivateTmp = true;
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/cryptpad.nix b/nixpkgs/nixos/modules/services/web-apps/cryptpad.nix
new file mode 100644
index 00000000000..69a89107d31
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/cryptpad.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cryptpad;
+in
+{
+ options.services.cryptpad = {
+ enable = mkEnableOption "the Cryptpad service";
+
+ package = mkOption {
+ default = pkgs.cryptpad;
+ defaultText = "pkgs.cryptpad";
+ type = types.package;
+ description = "
+ Cryptpad package to use.
+ ";
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ default = "${cfg.package}/lib/node_modules/cryptpad/config/config.example.js";
+ defaultText = "\${cfg.package}/lib/node_modules/cryptpad/config/config.example.js";
+ description = ''
+ Path to the JavaScript configuration file.
+
+ See <link
+ xlink:href="https://github.com/xwiki-labs/cryptpad/blob/master/config/config.example.js"/>
+ for a configuration example.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.cryptpad = {
+ description = "Cryptpad Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "networking.target" ];
+ serviceConfig = {
+ DynamicUser = true;
+ Environment = [
+ "CRYPTPAD_CONFIG=${cfg.configFile}"
+ "HOME=%S/cryptpad"
+ ];
+ ExecStart = "${cfg.package}/bin/cryptpad";
+ PrivateTmp = true;
+ Restart = "always";
+ StateDirectory = "cryptpad";
+ WorkingDirectory = "%S/cryptpad";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/documize.nix b/nixpkgs/nixos/modules/services/web-apps/documize.nix
new file mode 100644
index 00000000000..37359869cb6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/documize.nix
@@ -0,0 +1,138 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+ cfg = config.services.documize;
+
+ mkParams = optional: concatMapStrings (name: let
+ predicate = optional -> cfg.${name} != null;
+ template = " -${name} '${toString cfg.${name}}'";
+ in optionalString predicate template);
+
+in {
+ options.services.documize = {
+ enable = mkEnableOption "Documize Wiki";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.documize-community;
+ description = ''
+ Which package to use for documize.
+ '';
+ };
+
+ salt = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "3edIYV6c8B28b19fh";
+ description = ''
+ The salt string used to encode JWT tokens, if not set a random value will be generated.
+ '';
+ };
+
+ cert = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The <filename>cert.pem</filename> file used for https.
+ '';
+ };
+
+ key = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The <filename>key.pem</filename> file used for https.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 5001;
+ description = ''
+ The http/https port number.
+ '';
+ };
+
+ forcesslport = mkOption {
+ type = types.nullOr types.port;
+ default = null;
+ description = ''
+ Redirect given http port number to TLS.
+ '';
+ };
+
+ offline = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Set <literal>true</literal> for offline mode.
+ '';
+ apply = v: if true == v then 1 else 0;
+ };
+
+ dbtype = mkOption {
+ type = types.enum [ "mysql" "percona" "mariadb" "postgresql" "sqlserver" ];
+ default = "postgresql";
+ description = ''
+ Specify the database provider:
+ <simplelist type='inline'>
+ <member><literal>mysql</literal></member>
+ <member><literal>percona</literal></member>
+ <member><literal>mariadb</literal></member>
+ <member><literal>postgresql</literal></member>
+ <member><literal>sqlserver</literal></member>
+ </simplelist>
+ '';
+ };
+
+ db = mkOption {
+ type = types.str;
+ description = ''
+ Database specific connection string for example:
+ <itemizedlist>
+ <listitem><para>MySQL/Percona/MariaDB:
+ <literal>user:password@tcp(host:3306)/documize</literal>
+ </para></listitem>
+ <listitem><para>MySQLv8+:
+ <literal>user:password@tcp(host:3306)/documize?allowNativePasswords=true</literal>
+ </para></listitem>
+ <listitem><para>PostgreSQL:
+ <literal>host=localhost port=5432 dbname=documize user=admin password=secret sslmode=disable</literal>
+ </para></listitem>
+ <listitem><para>MSSQL:
+ <literal>sqlserver://username:password@localhost:1433?database=Documize</literal> or
+ <literal>sqlserver://sa@localhost/SQLExpress?database=Documize</literal>
+ </para></listitem>
+ </itemizedlist>
+ '';
+ };
+
+ location = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ reserved
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.documize-server = {
+ description = "Documize Wiki";
+ documentation = [ https://documize.com/ ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = concatStringsSep " " [
+ "${cfg.package}/bin/documize"
+ (mkParams false [ "db" "dbtype" "port" ])
+ (mkParams true [ "offline" "location" "forcesslport" "key" "cert" "salt" ])
+ ];
+ Restart = "always";
+ DynamicUser = "yes";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/frab.nix b/nixpkgs/nixos/modules/services/web-apps/frab.nix
new file mode 100644
index 00000000000..7914e5cc0ee
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/frab.nix
@@ -0,0 +1,223 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.frab;
+
+ package = pkgs.frab;
+
+ databaseConfig = builtins.toJSON { production = cfg.database; };
+
+ frabEnv = {
+ RAILS_ENV = "production";
+ RACK_ENV = "production";
+ SECRET_KEY_BASE = cfg.secretKeyBase;
+ FRAB_HOST = cfg.host;
+ FRAB_PROTOCOL = cfg.protocol;
+ FROM_EMAIL = cfg.fromEmail;
+ RAILS_SERVE_STATIC_FILES = "1";
+ } // cfg.extraEnvironment;
+
+ frab-rake = pkgs.stdenv.mkDerivation {
+ name = "frab-rake";
+ buildInputs = [ package.env pkgs.makeWrapper ];
+ phases = "installPhase fixupPhase";
+ installPhase = ''
+ mkdir -p $out/bin
+ makeWrapper ${package.env}/bin/bundle $out/bin/frab-bundle \
+ ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") frabEnv)} \
+ --set PATH '${lib.makeBinPath (with pkgs; [ nodejs file imagemagick ])}:$PATH' \
+ --set RAKEOPT '-f ${package}/share/frab/Rakefile' \
+ --run 'cd ${package}/share/frab'
+ makeWrapper $out/bin/frab-bundle $out/bin/frab-rake \
+ --add-flags "exec rake"
+ '';
+ };
+
+in
+
+{
+ options = {
+ services.frab = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the frab service.
+ '';
+ };
+
+ host = mkOption {
+ type = types.str;
+ example = "frab.example.com";
+ description = ''
+ Hostname under which this frab instance can be reached.
+ '';
+ };
+
+ protocol = mkOption {
+ type = types.str;
+ default = "https";
+ example = "http";
+ description = ''
+ Either http or https, depending on how your Frab instance
+ will be exposed to the public.
+ '';
+ };
+
+ fromEmail = mkOption {
+ type = types.str;
+ default = "frab@localhost";
+ description = ''
+ Email address used by frab.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = ''
+ Address or hostname frab should listen on.
+ '';
+ };
+
+ listenPort = mkOption {
+ type = types.int;
+ default = 3000;
+ description = ''
+ Port frab should listen on.
+ '';
+ };
+
+ statePath = mkOption {
+ type = types.str;
+ default = "/var/lib/frab";
+ description = ''
+ Directory where frab keeps its state.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "frab";
+ description = ''
+ User to run frab.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "frab";
+ description = ''
+ Group to run frab.
+ '';
+ };
+
+ secretKeyBase = mkOption {
+ type = types.str;
+ description = ''
+ Your secret key is used for verifying the integrity of signed cookies.
+ If you change this key, all old signed cookies will become invalid!
+
+ Make sure the secret is at least 30 characters and all random,
+ no regular words or you'll be exposed to dictionary attacks.
+ '';
+ };
+
+ database = mkOption {
+ type = types.attrs;
+ default = {
+ adapter = "sqlite3";
+ database = "/var/lib/frab/db.sqlite3";
+ pool = 5;
+ timeout = 5000;
+ };
+ example = {
+ adapter = "postgresql";
+ database = "frab";
+ host = "localhost";
+ username = "frabuser";
+ password = "supersecret";
+ encoding = "utf8";
+ pool = 5;
+ };
+ description = ''
+ Rails database configuration for Frab as Nix attribute set.
+ '';
+ };
+
+ extraEnvironment = mkOption {
+ type = types.attrs;
+ default = {};
+ example = {
+ FRAB_CURRENCY_UNIT = "€";
+ FRAB_CURRENCY_FORMAT = "%n%u";
+ EXCEPTION_EMAIL = "frab-owner@example.com";
+ SMTP_ADDRESS = "localhost";
+ SMTP_PORT = "587";
+ SMTP_DOMAIN = "localdomain";
+ SMTP_USER_NAME = "root";
+ SMTP_PASSWORD = "toor";
+ SMTP_AUTHENTICATION = "1";
+ SMTP_NOTLS = "1";
+ };
+ description = ''
+ Additional environment variables to set for frab for further
+ configuration. See the frab documentation for more information.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ frab-rake ];
+
+ users.users = [
+ { name = cfg.user;
+ group = cfg.group;
+ home = "${cfg.statePath}";
+ }
+ ];
+
+ users.groups = [ { name = cfg.group; } ];
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.statePath}/system/attachments' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.frab = {
+ after = [ "network.target" "gitlab.service" ];
+ wantedBy = [ "multi-user.target" ];
+ environment = frabEnv;
+
+ preStart = ''
+ ln -sf ${pkgs.writeText "frab-database.yml" databaseConfig} /run/frab/database.yml
+ ln -sf ${cfg.statePath}/system /run/frab/system
+
+ if ! test -e "${cfg.statePath}/db-setup-done"; then
+ ${frab-rake}/bin/frab-rake db:setup
+ touch ${cfg.statePath}/db-setup-done
+ else
+ ${frab-rake}/bin/frab-rake db:migrate
+ fi
+ '';
+
+ serviceConfig = {
+ PrivateTmp = true;
+ PrivateDevices = true;
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ TimeoutSec = "300s";
+ Restart = "on-failure";
+ RestartSec = "10s";
+ RuntimeDirectory = "frab";
+ WorkingDirectory = "${package}/share/frab";
+ ExecStart = "${frab-rake}/bin/frab-bundle exec rails server " +
+ "--binding=${cfg.listenAddress} --port=${toString cfg.listenPort}";
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
new file mode 100644
index 00000000000..d9ad7e9e3d3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
@@ -0,0 +1,244 @@
+{ config, lib, pkgs, ... }: with lib; let
+ cfg = config.services.icingaweb2;
+ fpm = config.services.phpfpm.pools.${poolName};
+ poolName = "icingaweb2";
+
+ defaultConfig = {
+ global = {
+ module_path = "${pkgs.icingaweb2}/modules";
+ };
+ };
+in {
+ meta.maintainers = with maintainers; [ das_j ];
+
+ options.services.icingaweb2 = with types; {
+ enable = mkEnableOption "the icingaweb2 web interface";
+
+ pool = mkOption {
+ type = str;
+ default = poolName;
+ description = ''
+ Name of existing PHP-FPM pool that is used to run Icingaweb2.
+ If not specified, a pool will automatically created with default values.
+ '';
+ };
+
+ virtualHost = mkOption {
+ type = nullOr str;
+ default = "icingaweb2";
+ description = ''
+ Name of the nginx virtualhost to use and setup. If null, no virtualhost is set up.
+ '';
+ };
+
+ timezone = mkOption {
+ type = str;
+ default = "UTC";
+ example = "Europe/Berlin";
+ description = "PHP-compliant timezone specification";
+ };
+
+ modules = {
+ doc.enable = mkEnableOption "the icingaweb2 doc module";
+ migrate.enable = mkEnableOption "the icingaweb2 migrate module";
+ setup.enable = mkEnableOption "the icingaweb2 setup module";
+ test.enable = mkEnableOption "the icingaweb2 test module";
+ translation.enable = mkEnableOption "the icingaweb2 translation module";
+ };
+
+ modulePackages = mkOption {
+ type = attrsOf package;
+ default = {};
+ example = literalExample ''
+ {
+ "snow" = icingaweb2Modules.theme-snow;
+ }
+ '';
+ description = ''
+ Name-package attrset of Icingaweb 2 modules packages to enable.
+
+ If you enable modules manually (e.g. via the web ui), they will not be touched.
+ '';
+ };
+
+ generalConfig = mkOption {
+ type = nullOr attrs;
+ default = null;
+ example = {
+ general = {
+ showStacktraces = 1;
+ config_resource = "icingaweb_db";
+ };
+ logging = {
+ log = "syslog";
+ level = "CRITICAL";
+ };
+ };
+ description = ''
+ config.ini contents.
+ Will automatically be converted to a .ini file.
+ If you don't set global.module_path, the module will take care of it.
+
+ If the value is null, no config.ini is created and you can
+ modify it manually (e.g. via the web interface).
+ Note that you need to update module_path manually.
+ '';
+ };
+
+ resources = mkOption {
+ type = nullOr attrs;
+ default = null;
+ example = {
+ icingaweb_db = {
+ type = "db";
+ db = "mysql";
+ host = "localhost";
+ username = "icingaweb2";
+ password = "icingaweb2";
+ dbname = "icingaweb2";
+ };
+ };
+ description = ''
+ resources.ini contents.
+ Will automatically be converted to a .ini file.
+
+ If the value is null, no resources.ini is created and you can
+ modify it manually (e.g. via the web interface).
+ Note that if you set passwords here, they will go into the nix store.
+ '';
+ };
+
+ authentications = mkOption {
+ type = nullOr attrs;
+ default = null;
+ example = {
+ icingaweb = {
+ backend = "db";
+ resource = "icingaweb_db";
+ };
+ };
+ description = ''
+ authentication.ini contents.
+ Will automatically be converted to a .ini file.
+
+ If the value is null, no authentication.ini is created and you can
+ modify it manually (e.g. via the web interface).
+ '';
+ };
+
+ groupBackends = mkOption {
+ type = nullOr attrs;
+ default = null;
+ example = {
+ icingaweb = {
+ backend = "db";
+ resource = "icingaweb_db";
+ };
+ };
+ description = ''
+ groups.ini contents.
+ Will automatically be converted to a .ini file.
+
+ If the value is null, no groups.ini is created and you can
+ modify it manually (e.g. via the web interface).
+ '';
+ };
+
+ roles = mkOption {
+ type = nullOr attrs;
+ default = null;
+ example = {
+ Administrators = {
+ users = "admin";
+ permissions = "*";
+ };
+ };
+ description = ''
+ roles.ini contents.
+ Will automatically be converted to a .ini file.
+
+ If the value is null, no roles.ini is created and you can
+ modify it manually (e.g. via the web interface).
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
+ ${poolName} = {
+ user = "icingaweb2";
+ phpOptions = ''
+ extension = ${pkgs.phpPackages.imagick}/lib/php/extensions/imagick.so
+ date.timezone = "${cfg.timezone}"
+ '';
+ settings = mapAttrs (name: mkDefault) {
+ "listen.owner" = "nginx";
+ "listen.group" = "nginx";
+ "listen.mode" = "0600";
+ "pm" = "dynamic";
+ "pm.max_children" = 75;
+ "pm.start_servers" = 2;
+ "pm.min_spare_servers" = 2;
+ "pm.max_spare_servers" = 10;
+ };
+ };
+ };
+
+ systemd.services."phpfpm-${poolName}".serviceConfig.ReadWritePaths = [ "/etc/icingaweb2" ];
+
+ services.nginx = {
+ enable = true;
+ virtualHosts = mkIf (cfg.virtualHost != null) {
+ ${cfg.virtualHost} = {
+ root = "${pkgs.icingaweb2}/public";
+
+ extraConfig = ''
+ index index.php;
+ try_files $1 $uri $uri/ /index.php$is_args$args;
+ '';
+
+ locations."~ ..*/.*.php$".extraConfig = ''
+ return 403;
+ '';
+
+ locations."~ ^/index.php(.*)$".extraConfig = ''
+ fastcgi_intercept_errors on;
+ fastcgi_index index.php;
+ include ${config.services.nginx.package}/conf/fastcgi.conf;
+ try_files $uri =404;
+ fastcgi_split_path_info ^(.+\.php)(/.+)$;
+ fastcgi_pass unix:${fpm.socket};
+ fastcgi_param SCRIPT_FILENAME ${pkgs.icingaweb2}/public/index.php;
+ '';
+ };
+ };
+ };
+
+ # /etc/icingaweb2
+ environment.etc = let
+ doModule = name: optionalAttrs (cfg.modules.${name}.enable) { "icingaweb2/enabledModules/${name}".source = "${pkgs.icingaweb2}/modules/${name}"; };
+ in {}
+ # Module packages
+ // (mapAttrs' (k: v: nameValuePair "icingaweb2/enabledModules/${k}" { source = v; }) cfg.modulePackages)
+ # Built-in modules
+ // doModule "doc"
+ // doModule "migrate"
+ // doModule "setup"
+ // doModule "test"
+ // doModule "translation"
+ # Configs
+ // optionalAttrs (cfg.generalConfig != null) { "icingaweb2/config.ini".text = generators.toINI {} (defaultConfig // cfg.generalConfig); }
+ // optionalAttrs (cfg.resources != null) { "icingaweb2/resources.ini".text = generators.toINI {} cfg.resources; }
+ // optionalAttrs (cfg.authentications != null) { "icingaweb2/authentication.ini".text = generators.toINI {} cfg.authentications; }
+ // optionalAttrs (cfg.groupBackends != null) { "icingaweb2/groups.ini".text = generators.toINI {} cfg.groupBackends; }
+ // optionalAttrs (cfg.roles != null) { "icingaweb2/roles.ini".text = generators.toINI {} cfg.roles; };
+
+ # User and group
+ users.groups.icingaweb2 = {};
+ users.users.icingaweb2 = {
+ description = "Icingaweb2 service user";
+ group = "icingaweb2";
+ isSystemUser = true;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
new file mode 100644
index 00000000000..e9c1d4ffe5e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
@@ -0,0 +1,157 @@
+{ config, lib, pkgs, ... }: with lib; let
+ cfg = config.services.icingaweb2.modules.monitoring;
+
+ configIni = ''
+ [security]
+ protected_customvars = "${concatStringsSep "," cfg.generalConfig.protectedVars}"
+ '';
+
+ backendsIni = let
+ formatBool = b: if b then "1" else "0";
+ in concatStringsSep "\n" (mapAttrsToList (name: config: ''
+ [${name}]
+ type = "ido"
+ resource = "${config.resource}"
+ disabled = "${formatBool config.disabled}"
+ '') cfg.backends);
+
+ transportsIni = concatStringsSep "\n" (mapAttrsToList (name: config: ''
+ [${name}]
+ type = "${config.type}"
+ ${optionalString (config.instance != null) ''instance = "${config.instance}"''}
+ ${optionalString (config.type == "local" || config.type == "remote") ''path = "${config.path}"''}
+ ${optionalString (config.type != "local") ''
+ host = "${config.host}"
+ ${optionalString (config.port != null) ''port = "${toString config.port}"''}
+ user${optionalString (config.type == "api") "name"} = "${config.username}"
+ ''}
+ ${optionalString (config.type == "api") ''password = "${config.password}"''}
+ ${optionalString (config.type == "remote") ''resource = "${config.resource}"''}
+ '') cfg.transports);
+
+in {
+ options.services.icingaweb2.modules.monitoring = with types; {
+ enable = mkOption {
+ type = bool;
+ default = true;
+ description = "Whether to enable the icingaweb2 monitoring module.";
+ };
+
+ generalConfig = {
+ mutable = mkOption {
+ type = bool;
+ default = false;
+ description = "Make config.ini of the monitoring module mutable (e.g. via the web interface).";
+ };
+
+ protectedVars = mkOption {
+ type = listOf str;
+ default = [ "*pw*" "*pass*" "community" ];
+ description = "List of string patterns for custom variables which should be excluded from user’s view.";
+ };
+ };
+
+ mutableBackends = mkOption {
+ type = bool;
+ default = false;
+ description = "Make backends.ini of the monitoring module mutable (e.g. via the web interface).";
+ };
+
+ backends = mkOption {
+ default = { icinga = { resource = "icinga_ido"; }; };
+ description = "Monitoring backends to define";
+ type = attrsOf (submodule ({ name, ... }: {
+ options = {
+ name = mkOption {
+ visible = false;
+ default = name;
+ type = str;
+ description = "Name of this backend";
+ };
+
+ resource = mkOption {
+ type = str;
+ description = "Name of the IDO resource";
+ };
+
+ disabled = mkOption {
+ type = bool;
+ default = false;
+ description = "Disable this backend";
+ };
+ };
+ }));
+ };
+
+ mutableTransports = mkOption {
+ type = bool;
+ default = true;
+ description = "Make commandtransports.ini of the monitoring module mutable (e.g. via the web interface).";
+ };
+
+ transports = mkOption {
+ default = {};
+ description = "Command transports to define";
+ type = attrsOf (submodule ({ name, ... }: {
+ options = {
+ name = mkOption {
+ visible = false;
+ default = name;
+ type = str;
+ description = "Name of this transport";
+ };
+
+ type = mkOption {
+ type = enum [ "api" "local" "remote" ];
+ default = "api";
+ description = "Type of this transport";
+ };
+
+ instance = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Assign a icinga instance to this transport";
+ };
+
+ path = mkOption {
+ type = str;
+ description = "Path to the socket for local or remote transports";
+ };
+
+ host = mkOption {
+ type = str;
+ description = "Host for the api or remote transport";
+ };
+
+ port = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Port to connect to for the api or remote transport";
+ };
+
+ username = mkOption {
+ type = str;
+ description = "Username for the api or remote transport";
+ };
+
+ password = mkOption {
+ type = str;
+ description = "Password for the api transport";
+ };
+
+ resource = mkOption {
+ type = str;
+ description = "SSH identity resource for the remote transport";
+ };
+ };
+ }));
+ };
+ };
+
+ config = mkIf (config.services.icingaweb2.enable && cfg.enable) {
+ environment.etc = { "icingaweb2/enabledModules/monitoring" = { source = "${pkgs.icingaweb2}/modules/monitoring"; }; }
+ // optionalAttrs (!cfg.generalConfig.mutable) { "icingaweb2/modules/monitoring/config.ini".text = configIni; }
+ // optionalAttrs (!cfg.mutableBackends) { "icingaweb2/modules/monitoring/backends.ini".text = backendsIni; }
+ // optionalAttrs (!cfg.mutableTransports) { "icingaweb2/modules/monitoring/commandtransports.ini".text = transportsIni; };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/limesurvey.nix b/nixpkgs/nixos/modules/services/web-apps/limesurvey.nix
new file mode 100644
index 00000000000..68b57a9b90d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/limesurvey.nix
@@ -0,0 +1,283 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+ inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
+ inherit (lib) mapAttrs optional optionalString types;
+
+ cfg = config.services.limesurvey;
+ fpm = config.services.phpfpm.pools.limesurvey;
+
+ user = "limesurvey";
+ group = config.services.httpd.group;
+ stateDir = "/var/lib/limesurvey";
+
+ pkg = pkgs.limesurvey;
+
+ configType = with types; oneOf [ (attrsOf configType) str int bool ] // {
+ description = "limesurvey config type (str, int, bool or attribute set thereof)";
+ };
+
+ limesurveyConfig = pkgs.writeText "config.php" ''
+ <?php
+ return json_decode('${builtins.toJSON cfg.config}', true);
+ ?>
+ '';
+
+ mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+ pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+in
+{
+ # interface
+
+ options.services.limesurvey = {
+ enable = mkEnableOption "Limesurvey web application.";
+
+ database = {
+ type = mkOption {
+ type = types.enum [ "mysql" "pgsql" "odbc" "mssql" ];
+ example = "pgsql";
+ default = "mysql";
+ description = "Database engine to use.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = if cfg.database.type == "pgsql" then 5442 else 3306;
+ defaultText = "3306";
+ description = "Database host port.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "limesurvey";
+ description = "Database name.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "limesurvey";
+ description = "Database user.";
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/limesurvey-dbpassword";
+ description = ''
+ A file containing the password corresponding to
+ <option>database.user</option>.
+ '';
+ };
+
+ socket = mkOption {
+ type = types.nullOr types.path;
+ default =
+ if mysqlLocal then "/run/mysqld/mysqld.sock"
+ else if pgsqlLocal then "/run/postgresql"
+ else null
+ ;
+ defaultText = "/run/mysqld/mysqld.sock";
+ description = "Path to the unix socket file to use for authentication.";
+ };
+
+ createLocally = mkOption {
+ type = types.bool;
+ default = cfg.database.type == "mysql";
+ defaultText = "true";
+ description = ''
+ Create the database and database user locally.
+ This currently only applies if database type "mysql" is selected.
+ '';
+ };
+ };
+
+ virtualHost = mkOption {
+ type = types.submodule ({
+ options = import ../web-servers/apache-httpd/per-server-options.nix {
+ inherit lib;
+ forMainServer = false;
+ };
+ });
+ example = {
+ hostName = "survey.example.org";
+ enableSSL = true;
+ adminAddr = "webmaster@example.org";
+ sslServerCert = "/var/lib/acme/survey.example.org/full.pem";
+ sslServerKey = "/var/lib/acme/survey.example.org/key.pem";
+ };
+ description = ''
+ Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
+ See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+ '';
+ };
+
+ poolConfig = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool ]);
+ default = {
+ "pm" = "dynamic";
+ "pm.max_children" = 32;
+ "pm.start_servers" = 2;
+ "pm.min_spare_servers" = 2;
+ "pm.max_spare_servers" = 4;
+ "pm.max_requests" = 500;
+ };
+ description = ''
+ Options for the LimeSurvey PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+ for details on configuration directives.
+ '';
+ };
+
+ config = mkOption {
+ type = configType;
+ default = {};
+ description = ''
+ LimeSurvey configuration. Refer to
+ <link xlink:href="https://manual.limesurvey.org/Optional_settings"/>
+ for details on supported values.
+ '';
+ };
+ };
+
+ # implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.database.createLocally -> cfg.database.type == "mysql";
+ message = "services.limesurvey.createLocally is currently only supported for database type 'mysql'";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.user == user;
+ message = "services.limesurvey.database.user must be set to ${user} if services.limesurvey.database.createLocally is set true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.socket != null;
+ message = "services.limesurvey.database.socket must be set if services.limesurvey.database.createLocally is set to true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+ message = "a password cannot be specified if services.limesurvey.database.createLocally is set to true";
+ }
+ ];
+
+ services.limesurvey.config = mapAttrs (name: mkDefault) {
+ runtimePath = "${stateDir}/tmp/runtime";
+ components = {
+ db = {
+ connectionString = "${cfg.database.type}:dbname=${cfg.database.name};host=${if pgsqlLocal then cfg.database.socket else cfg.database.host};port=${toString cfg.database.port}" +
+ optionalString mysqlLocal ";socket=${cfg.database.socket}";
+ username = cfg.database.user;
+ password = mkIf (cfg.database.passwordFile != null) "file_get_contents(\"${toString cfg.database.passwordFile}\");";
+ tablePrefix = "limesurvey_";
+ };
+ assetManager.basePath = "${stateDir}/tmp/assets";
+ urlManager = {
+ urlFormat = "path";
+ showScriptName = false;
+ };
+ };
+ config = {
+ tempdir = "${stateDir}/tmp";
+ uploaddir = "${stateDir}/upload";
+ force_ssl = mkIf cfg.virtualHost.enableSSL "on";
+ config.defaultlang = "en";
+ };
+ };
+
+ services.mysql = mkIf mysqlLocal {
+ enable = true;
+ package = mkDefault pkgs.mariadb;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = {
+ "${cfg.database.name}.*" = "SELECT, CREATE, INSERT, UPDATE, DELETE, ALTER, DROP, INDEX";
+ };
+ }
+ ];
+ };
+
+ services.phpfpm.pools.limesurvey = {
+ inherit user group;
+ phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}";
+ settings = {
+ "listen.owner" = config.services.httpd.user;
+ "listen.group" = config.services.httpd.group;
+ } // cfg.poolConfig;
+ };
+
+ services.httpd = {
+ enable = true;
+ adminAddr = mkDefault cfg.virtualHost.adminAddr;
+ extraModules = [ "proxy_fcgi" ];
+ virtualHosts = [ (mkMerge [
+ cfg.virtualHost {
+ documentRoot = mkForce "${pkg}/share/limesurvey";
+ extraConfig = ''
+ Alias "/tmp" "${stateDir}/tmp"
+ <Directory "${stateDir}">
+ AllowOverride all
+ Require all granted
+ Options -Indexes +FollowSymlinks
+ </Directory>
+
+ Alias "/upload" "${stateDir}/upload"
+ <Directory "${stateDir}/upload">
+ AllowOverride all
+ Require all granted
+ Options -Indexes
+ </Directory>
+
+ <Directory "${pkg}/share/limesurvey">
+ <FilesMatch "\.php$">
+ <If "-f %{REQUEST_FILENAME}">
+ SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+ </If>
+ </FilesMatch>
+
+ AllowOverride all
+ Options -Indexes
+ DirectoryIndex index.php
+ </Directory>
+ '';
+ }
+ ]) ];
+ };
+
+ systemd.tmpfiles.rules = [
+ "d ${stateDir} 0750 ${user} ${group} - -"
+ "d ${stateDir}/tmp 0750 ${user} ${group} - -"
+ "d ${stateDir}/tmp/assets 0750 ${user} ${group} - -"
+ "d ${stateDir}/tmp/runtime 0750 ${user} ${group} - -"
+ "d ${stateDir}/tmp/upload 0750 ${user} ${group} - -"
+ "C ${stateDir}/upload 0750 ${user} ${group} - ${pkg}/share/limesurvey/upload"
+ ];
+
+ systemd.services.limesurvey-init = {
+ wantedBy = [ "multi-user.target" ];
+ before = [ "phpfpm-limesurvey.service" ];
+ after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+ environment.LIMESURVEY_CONFIG = limesurveyConfig;
+ script = ''
+ # update or install the database as required
+ ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
+ ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
+ '';
+ serviceConfig = {
+ User = user;
+ Group = group;
+ Type = "oneshot";
+ };
+ };
+
+ systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+ users.users.${user}.group = group;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml b/nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml
new file mode 100644
index 00000000000..8485492c51c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml
@@ -0,0 +1,113 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="module-services-matomo">
+ <title>Matomo</title>
+ <para>
+ Matomo is a real-time web analytics application. This module configures
+ php-fpm as backend for Matomo, optionally configuring an nginx vhost as well.
+ </para>
+ <para>
+ An automatic setup is not suported by Matomo, so you need to configure Matomo
+ itself in the browser-based Matomo setup.
+ </para>
+ <section xml:id="module-services-matomo-database-setup">
+ <title>Database Setup</title>
+
+ <para>
+ You also need to configure a MariaDB or MySQL database and -user for Matomo
+ yourself, and enter those credentials in your browser. You can use
+ passwordless database authentication via the UNIX_SOCKET authentication
+ plugin with the following SQL commands:
+<programlisting>
+# For MariaDB
+INSTALL PLUGIN unix_socket SONAME 'auth_socket';
+CREATE DATABASE matomo;
+CREATE USER 'matomo'@'localhost' IDENTIFIED WITH unix_socket;
+GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
+
+# For MySQL
+INSTALL PLUGIN auth_socket SONAME 'auth_socket.so';
+CREATE DATABASE matomo;
+CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket;
+GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
+</programlisting>
+ Then fill in <literal>matomo</literal> as database user and database name,
+ and leave the password field blank. This authentication works by allowing
+ only the <literal>matomo</literal> unix user to authenticate as the
+ <literal>matomo</literal> database user (without needing a password), but no
+ other users. For more information on passwordless login, see
+ <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />.
+ </para>
+
+ <para>
+ Of course, you can use password based authentication as well, e.g. when the
+ database is not on the same host.
+ </para>
+ </section>
+ <section xml:id="module-services-matomo-archive-processing">
+ <title>Archive Processing</title>
+
+ <para>
+ This module comes with the systemd service
+ <literal>matomo-archive-processing.service</literal> and a timer that
+ automatically triggers archive processing every hour. This means that you
+ can safely
+ <link xlink:href="https://matomo.org/docs/setup-auto-archiving/#disable-browser-triggers-for-matomo-archiving-and-limit-matomo-reports-to-updating-every-hour">
+ disable browser triggers for Matomo archiving </link> at
+ <literal>Administration > System > General Settings</literal>.
+ </para>
+
+ <para>
+ With automatic archive processing, you can now also enable to
+ <link xlink:href="https://matomo.org/docs/privacy/#step-2-delete-old-visitors-logs">
+ delete old visitor logs </link> at <literal>Administration > System >
+ Privacy</literal>, but make sure that you run <literal>systemctl start
+ matomo-archive-processing.service</literal> at least once without errors if
+ you have already collected data before, so that the reports get archived
+ before the source data gets deleted.
+ </para>
+ </section>
+ <section xml:id="module-services-matomo-backups">
+ <title>Backup</title>
+
+ <para>
+ You only need to take backups of your MySQL database and the
+ <filename>/var/lib/matomo/config/config.ini.php</filename> file. Use a user
+ in the <literal>matomo</literal> group or root to access the file. For more
+ information, see
+ <link xlink:href="https://matomo.org/faq/how-to-install/faq_138/" />.
+ </para>
+ </section>
+ <section xml:id="module-services-matomo-issues">
+ <title>Issues</title>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Matomo'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>
+
+ <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/nixpkgs/nixos/modules/services/web-apps/matomo.nix b/nixpkgs/nixos/modules/services/web-apps/matomo.nix
new file mode 100644
index 00000000000..1e34aff8d17
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/matomo.nix
@@ -0,0 +1,310 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.matomo;
+
+ user = "matomo";
+ dataDir = "/var/lib/${user}";
+ deprecatedDataDir = "/var/lib/piwik";
+
+ pool = user;
+ # it's not possible to use /run/phpfpm/${pool}.sock because /run/phpfpm/ is root:root 0770,
+ # and therefore is not accessible by the web server.
+ phpSocket = "/run/phpfpm-${pool}.sock";
+ phpExecutionUnit = "phpfpm-${pool}";
+ databaseService = "mysql.service";
+
+ fqdn =
+ let
+ join = hostName: domain: hostName + optionalString (domain != null) ".${domain}";
+ in join config.networking.hostName config.networking.domain;
+
+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.
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable Matomo web analytics with php-fpm backend.
+ Either the nginx option or the webServerUser option is mandatory.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ description = ''
+ Matomo package for the service to use.
+ This can be used to point to newer releases from nixos-unstable,
+ as they don't get backported if they are not security-relevant.
+ '';
+ default = pkgs.matomo;
+ defaultText = "pkgs.matomo";
+ };
+
+ webServerUser = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "lighttpd";
+ description = ''
+ Name of the web server user that forwards requests to 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`, `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.
+ '';
+ };
+
+ phpfpmProcessManagerConfig = mkOption {
+ type = types.str;
+ default = ''
+ ; default phpfpm process manager settings
+ pm = dynamic
+ pm.max_children = 75
+ pm.start_servers = 10
+ pm.min_spare_servers = 5
+ pm.max_spare_servers = 20
+ pm.max_requests = 500
+
+ ; log worker's stdout, but this has a performance hit
+ catch_workers_output = yes
+ '';
+ description = ''
+ Settings for phpfpm's process manager. You might need to change this depending on the load for Matomo.
+ '';
+ };
+
+ nginx = mkOption {
+ type = types.nullOr (types.submodule (
+ recursiveUpdate
+ (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
+ {
+ # enable encryption by default,
+ # as sensitive login and Matomo data should not be transmitted in clear text.
+ options.forceSSL.default = true;
+ options.enableACME.default = true;
+ }
+ )
+ );
+ default = null;
+ example = {
+ serverAliases = [
+ "matomo.\${config.networking.domain}"
+ "stats.\${config.networking.domain}"
+ ];
+ enableACME = false;
+ };
+ description = ''
+ With this option, you can customize an nginx virtualHost which already has sensible defaults for Matomo.
+ Either this option or the webServerUser option is mandatory.
+ Set this to {} to just enable the virtualHost if you don't need any customization.
+ If enabled, then by default, the <option>serverName</option> is
+ <literal>''${user}.''${config.networking.hostName}.''${config.networking.domain}</literal>,
+ SSL is active, and certificates are acquired via ACME.
+ If this is set to null (the default), no nginx virtualHost will be configured.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ warnings = mkIf (cfg.nginx != null && cfg.webServerUser != null) [
+ "If services.matomo.nginx is set, services.matomo.nginx.webServerUser is ignored and should be removed."
+ ];
+
+ assertions = [ {
+ assertion = cfg.nginx != null || cfg.webServerUser != null;
+ message = "Either services.matomo.nginx or services.matomo.nginx.webServerUser is mandatory";
+ }];
+
+ users.users.${user} = {
+ isSystemUser = true;
+ createHome = true;
+ home = dataDir;
+ group = user;
+ };
+ users.groups.${user} = {};
+
+ systemd.services.matomo-setup-update = {
+ # everything needs to set up and up to date before Matomo php files are executed
+ requiredBy = [ "${phpExecutionUnit}.service" ];
+ before = [ "${phpExecutionUnit}.service" ];
+ # the update part of the script can only work if the database is already up and running
+ requires = [ databaseService ];
+ after = [ databaseService ];
+ path = [ cfg.package ];
+ environment.PIWIK_USER_PATH = dataDir;
+ serviceConfig = {
+ Type = "oneshot";
+ User = user;
+ # hide especially config.ini.php from other
+ UMask = "0007";
+ # TODO: might get renamed to MATOMO_USER_PATH in future versions
+ # chown + chmod in preStart needs root
+ PermissionsStartOnly = true;
+ };
+
+ # correct ownership and permissions in case they're not correct anymore,
+ # e.g. after restoring from backup or moving from another system.
+ # Note that ${dataDir}/config/config.ini.php might contain the MySQL password.
+ preStart = ''
+ # migrate data from piwik to Matomo folder
+ if [ -d ${deprecatedDataDir} ]; then
+ echo "Migrating from ${deprecatedDataDir} to ${dataDir}"
+ mv -T ${deprecatedDataDir} ${dataDir}
+ fi
+ chown -R ${user}:${user} ${dataDir}
+ chmod -R ug+rwX,o-rwx ${dataDir}
+ '';
+ script = ''
+ # Use User-Private Group scheme to protect Matomo data, but allow administration / backup via 'matomo' group
+ # Copy config folder
+ chmod g+s "${dataDir}"
+ cp -r "${cfg.package}/share/config" "${dataDir}/"
+ chmod -R u+rwX,g+rwX,o-rwx "${dataDir}"
+
+ # check whether user setup has already been done
+ if test -f "${dataDir}/config/config.ini.php"; then
+ # then execute possibly pending database upgrade
+ matomo-console core:update --yes
+ fi
+ '';
+ };
+
+ # If this is run regularly via the timer,
+ # 'Browser trigger archiving' can be disabled in Matomo UI > Settings > General Settings.
+ systemd.services.matomo-archive-processing = {
+ description = "Archive Matomo reports";
+ # the archiving can only work if the database is already up and running
+ requires = [ databaseService ];
+ after = [ databaseService ];
+
+ # TODO: might get renamed to MATOMO_USER_PATH in future versions
+ environment.PIWIK_USER_PATH = dataDir;
+ serviceConfig = {
+ Type = "oneshot";
+ User = user;
+ UMask = "0007";
+ CPUSchedulingPolicy = "idle";
+ IOSchedulingClass = "idle";
+ ExecStart = "${cfg.package}/bin/matomo-console core:archive --url=https://${user}.${fqdn}";
+ };
+ };
+
+ systemd.timers.matomo-archive-processing = mkIf cfg.periodicArchiveProcessing {
+ description = "Automatically archive Matomo reports every hour";
+
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = "hourly";
+ Persistent = "yes";
+ AccuracySec = "10m";
+ };
+ };
+
+ systemd.services.${phpExecutionUnit} = {
+ # stop phpfpm on package upgrade, do database upgrade via matomo-setup-update, and then restart
+ restartTriggers = [ cfg.package ];
+ # stop config.ini.php from getting written with read permission for others
+ serviceConfig.UMask = "0007";
+ };
+
+ services.phpfpm.pools = let
+ # workaround for when both are null and need to generate a string,
+ # which is illegal, but as assertions apparently are being triggered *after* config generation,
+ # we have to avoid already throwing errors at this previous stage.
+ socketOwner = if (cfg.nginx != null) then config.services.nginx.user
+ else if (cfg.webServerUser != null) then cfg.webServerUser else "";
+ in {
+ ${pool} = {
+ listen = phpSocket;
+ extraConfig = ''
+ listen.owner = ${socketOwner}
+ listen.group = root
+ listen.mode = 0600
+ user = ${user}
+ env[PIWIK_USER_PATH] = ${dataDir}
+ ${cfg.phpfpmProcessManagerConfig}
+ '';
+ };
+ };
+
+
+ services.nginx.virtualHosts = mkIf (cfg.nginx != null) {
+ # References:
+ # https://fralef.me/piwik-hardening-with-nginx-and-php-fpm.html
+ # https://github.com/perusio/piwik-nginx
+ "${user}.${fqdn}" = mkMerge [ cfg.nginx {
+ # don't allow to override the root easily, as it will almost certainly break Matomo.
+ # disadvantage: not shown as default in docs.
+ root = mkForce "${cfg.package}/share";
+
+ # define locations here instead of as the submodule option's default
+ # so that they can easily be extended with additional locations if required
+ # without needing to redefine the Matomo ones.
+ # disadvantage: not shown as default in docs.
+ locations."/" = {
+ index = "index.php";
+ };
+ # allow index.php for webinterface
+ locations."= /index.php".extraConfig = ''
+ fastcgi_pass unix:${phpSocket};
+ '';
+ # 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};
+ '';
+ # Any other attempt to access any php files is forbidden
+ locations."~* ^.+\\.php$".extraConfig = ''
+ return 403;
+ '';
+ # Disallow access to unneeded directories
+ # config and tmp are already removed
+ locations."~ ^/(?:core|lang|misc)/".extraConfig = ''
+ return 403;
+ '';
+ # Disallow access to several helper files
+ locations."~* \\.(?:bat|git|ini|sh|txt|tpl|xml|md)$".extraConfig = ''
+ return 403;
+ '';
+ # No crawling of this site for bots that obey robots.txt - no useful information here.
+ locations."= /robots.txt".extraConfig = ''
+ return 200 "User-agent: *\nDisallow: /\n";
+ '';
+ # let browsers cache matomo.js
+ locations."= /matomo.js".extraConfig = ''
+ expires 1M;
+ '';
+ # let browsers cache piwik.js (deprecated name)
+ locations."= /piwik.js".extraConfig = ''
+ expires 1M;
+ '';
+ }];
+ };
+ };
+
+ meta = {
+ doc = ./matomo-doc.xml;
+ maintainers = with lib.maintainers; [ florianjacob ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/mattermost.nix b/nixpkgs/nixos/modules/services/web-apps/mattermost.nix
new file mode 100644
index 00000000000..8c7fc4056ad
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/mattermost.nix
@@ -0,0 +1,230 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.mattermost;
+
+ defaultConfig = builtins.fromJSON (readFile "${pkgs.mattermost}/config/config.json");
+
+ mattermostConf = foldl recursiveUpdate defaultConfig
+ [ { ServiceSettings.SiteURL = cfg.siteUrl;
+ ServiceSettings.ListenAddress = cfg.listenAddress;
+ TeamSettings.SiteName = cfg.siteName;
+ SqlSettings.DriverName = "postgres";
+ SqlSettings.DataSource = "postgres://${cfg.localDatabaseUser}:${cfg.localDatabasePassword}@localhost:5432/${cfg.localDatabaseName}?sslmode=disable&connect_timeout=10";
+ }
+ cfg.extraConfig
+ ];
+
+ mattermostConfJSON = pkgs.writeText "mattermost-config-raw.json" (builtins.toJSON mattermostConf);
+
+in
+
+{
+ options = {
+ services.mattermost = {
+ enable = mkEnableOption "Mattermost chat server";
+
+ statePath = mkOption {
+ type = types.str;
+ default = "/var/lib/mattermost";
+ description = "Mattermost working directory";
+ };
+
+ siteUrl = mkOption {
+ type = types.str;
+ example = "https://chat.example.com";
+ description = ''
+ URL this Mattermost instance is reachable under, without trailing slash.
+ '';
+ };
+
+ siteName = mkOption {
+ type = types.str;
+ default = "Mattermost";
+ description = "Name of this Mattermost site.";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = ":8065";
+ example = "[::1]:8065";
+ description = ''
+ Address and port this Mattermost instance listens to.
+ '';
+ };
+
+ mutableConfig = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether the Mattermost config.json is writeable by Mattermost.
+
+ Most of the settings can be edited in the system console of
+ Mattermost if this option is enabled. A template config using
+ the options specified in services.mattermost will be generated
+ but won't be overwritten on changes or rebuilds.
+
+ If this option is disabled, changes in the system console won't
+ be possible (default). If an config.json is present, it will be
+ overwritten!
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = { };
+ description = ''
+ Addtional configuration options as Nix attribute set in config.json schema.
+ '';
+ };
+
+ localDatabaseCreate = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Create a local PostgreSQL database for Mattermost automatically.
+ '';
+ };
+
+ localDatabaseName = mkOption {
+ type = types.str;
+ default = "mattermost";
+ description = ''
+ Local Mattermost database name.
+ '';
+ };
+
+ localDatabaseUser = mkOption {
+ type = types.str;
+ default = "mattermost";
+ description = ''
+ Local Mattermost database username.
+ '';
+ };
+
+ localDatabasePassword = mkOption {
+ type = types.str;
+ default = "mmpgsecret";
+ description = ''
+ Password for local Mattermost database user.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "mattermost";
+ description = ''
+ User which runs the Mattermost service.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "mattermost";
+ description = ''
+ Group which runs the Mattermost service.
+ '';
+ };
+
+ matterircd = {
+ enable = mkEnableOption "Mattermost IRC bridge";
+ parameters = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "-mmserver chat.example.com" "-bind [::]:6667" ];
+ description = ''
+ Set commandline parameters to pass to matterircd. See
+ https://github.com/42wim/matterircd#usage for more information.
+ '';
+ };
+ };
+ };
+ };
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ users.users = optionalAttrs (cfg.user == "mattermost") (singleton {
+ name = "mattermost";
+ group = cfg.group;
+ uid = config.ids.uids.mattermost;
+ home = cfg.statePath;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "mattermost") (singleton {
+ name = "mattermost";
+ gid = config.ids.gids.mattermost;
+ });
+
+ services.postgresql.enable = cfg.localDatabaseCreate;
+
+ # The systemd service will fail to execute the preStart hook
+ # if the WorkingDirectory does not exist
+ system.activationScripts.mattermost = ''
+ mkdir -p ${cfg.statePath}
+ '';
+
+ systemd.services.mattermost = {
+ description = "Mattermost chat service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "postgresql.service" ];
+
+ preStart = ''
+ mkdir -p ${cfg.statePath}/{data,config,logs}
+ ln -sf ${pkgs.mattermost}/{bin,fonts,i18n,templates,client} ${cfg.statePath}
+ '' + lib.optionalString (!cfg.mutableConfig) ''
+ ln -sf ${mattermostConfJSON} ${cfg.statePath}/config/config.json
+ '' + lib.optionalString cfg.mutableConfig ''
+ if ! test -e "${cfg.statePath}/config/.initial-created"; then
+ rm -f ${cfg.statePath}/config/config.json
+ cp ${mattermostConfJSON} ${cfg.statePath}/config/config.json
+ touch ${cfg.statePath}/config/.initial-created
+ fi
+ '' + lib.optionalString cfg.localDatabaseCreate ''
+ if ! test -e "${cfg.statePath}/.db-created"; then
+ ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
+ ${config.services.postgresql.package}/bin/psql postgres -c \
+ "CREATE ROLE ${cfg.localDatabaseUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${cfg.localDatabasePassword}'"
+ ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
+ ${config.services.postgresql.package}/bin/createdb \
+ --owner ${cfg.localDatabaseUser} ${cfg.localDatabaseName}
+ touch ${cfg.statePath}/.db-created
+ fi
+ '' + ''
+ chown ${cfg.user}:${cfg.group} -R ${cfg.statePath}
+ chmod u+rw,g+r,o-rwx -R ${cfg.statePath}
+ '';
+
+ serviceConfig = {
+ PermissionsStartOnly = true;
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${pkgs.mattermost}/bin/mattermost";
+ WorkingDirectory = "${cfg.statePath}";
+ Restart = "always";
+ RestartSec = "10";
+ LimitNOFILE = "49152";
+ };
+ unitConfig.JoinsNamespaceOf = mkIf cfg.localDatabaseCreate "postgresql.service";
+ };
+ })
+ (mkIf cfg.matterircd.enable {
+ systemd.services.matterircd = {
+ description = "Mattermost IRC bridge service";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = "nobody";
+ Group = "nogroup";
+ ExecStart = "${pkgs.matterircd.bin}/bin/matterircd ${concatStringsSep " " cfg.matterircd.parameters}";
+ WorkingDirectory = "/tmp";
+ PrivateTmp = true;
+ Restart = "always";
+ RestartSec = "5";
+ };
+ };
+ })
+ ];
+}
+
diff --git a/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix b/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix
new file mode 100644
index 00000000000..ec2568bf952
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix
@@ -0,0 +1,468 @@
+{ config, pkgs, lib, ... }:
+
+let
+
+ inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
+ inherit (lib) concatStringsSep literalExample mapAttrsToList optional optionals optionalString types;
+
+ cfg = config.services.mediawiki;
+ fpm = config.services.phpfpm.pools.mediawiki;
+ user = "mediawiki";
+ group = config.services.httpd.group;
+ cacheDir = "/var/cache/mediawiki";
+ stateDir = "/var/lib/mediawiki";
+
+ pkg = pkgs.stdenv.mkDerivation rec {
+ pname = "mediawiki-full";
+ version = src.version;
+ src = cfg.package;
+
+ installPhase = ''
+ mkdir -p $out
+ cp -r * $out/
+
+ rm -rf $out/share/mediawiki/skins/*
+ rm -rf $out/share/mediawiki/extensions/*
+
+ ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
+ ln -s ${v} $out/share/mediawiki/skins/${k}
+ '') cfg.skins)}
+
+ ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
+ ln -s ${v} $out/share/mediawiki/extensions/${k}
+ '') cfg.extensions)}
+ '';
+ };
+
+ mediawikiScripts = pkgs.runCommand "mediawiki-scripts" {
+ buildInputs = [ pkgs.makeWrapper ];
+ preferLocalBuild = true;
+ } ''
+ mkdir -p $out/bin
+ for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do
+ makeWrapper ${pkgs.php}/bin/php $out/bin/mediawiki-$(basename $i .php) \
+ --set MEDIAWIKI_CONFIG ${mediawikiConfig} \
+ --add-flags ${pkg}/share/mediawiki/maintenance/$i
+ done
+ '';
+
+ mediawikiConfig = pkgs.writeText "LocalSettings.php" ''
+ <?php
+ # Protect against web entry
+ if ( !defined( 'MEDIAWIKI' ) ) {
+ exit;
+ }
+
+ $wgSitename = "${cfg.name}";
+ $wgMetaNamespace = false;
+
+ ## The URL base path to the directory containing the wiki;
+ ## defaults for all runtime URL paths are based off of this.
+ ## For more information on customizing the URLs
+ ## (like /w/index.php/Page_title to /wiki/Page_title) please see:
+ ## https://www.mediawiki.org/wiki/Manual:Short_URL
+ $wgScriptPath = "";
+
+ ## The protocol and server name to use in fully-qualified URLs
+ $wgServer = "${if cfg.virtualHost.enableSSL then "https" else "http"}://${cfg.virtualHost.hostName}";
+
+ ## The URL path to static resources (images, scripts, etc.)
+ $wgResourceBasePath = $wgScriptPath;
+
+ ## The URL path to the logo. Make sure you change this from the default,
+ ## or else you'll overwrite your logo when you upgrade!
+ $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
+
+ ## UPO means: this is also a user preference option
+
+ $wgEnableEmail = true;
+ $wgEnableUserEmail = true; # UPO
+
+ $wgEmergencyContact = "${if cfg.virtualHost.adminAddr != null then cfg.virtualHost.adminAddr else config.services.httpd.adminAddr}";
+ $wgPasswordSender = $wgEmergencyContact;
+
+ $wgEnotifUserTalk = false; # UPO
+ $wgEnotifWatchlist = false; # UPO
+ $wgEmailAuthentication = true;
+
+ ## Database settings
+ $wgDBtype = "${cfg.database.type}";
+ $wgDBserver = "${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}";
+ $wgDBname = "${cfg.database.name}";
+ $wgDBuser = "${cfg.database.user}";
+ ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"}
+
+ ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) ''
+ # MySQL specific settings
+ $wgDBprefix = "${cfg.database.tablePrefix}";
+ ''}
+
+ ${optionalString (cfg.database.type == "mysql") ''
+ # MySQL table options to use during installation or update
+ $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary";
+ ''}
+
+ ## Shared memory settings
+ $wgMainCacheType = CACHE_NONE;
+ $wgMemCachedServers = [];
+
+ ${optionalString (cfg.uploadsDir != null) ''
+ $wgEnableUploads = true;
+ $wgUploadDirectory = "${cfg.uploadsDir}";
+ ''}
+
+ $wgUseImageMagick = true;
+ $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert";
+
+ # InstantCommons allows wiki to use images from https://commons.wikimedia.org
+ $wgUseInstantCommons = false;
+
+ # Periodically send a pingback to https://www.mediawiki.org/ with basic data
+ # about this MediaWiki instance. The Wikimedia Foundation shares this data
+ # with MediaWiki developers to help guide future development efforts.
+ $wgPingback = true;
+
+ ## If you use ImageMagick (or any other shell command) on a
+ ## Linux server, this will need to be set to the name of an
+ ## available UTF-8 locale
+ $wgShellLocale = "C.UTF-8";
+
+ ## Set $wgCacheDirectory to a writable directory on the web server
+ ## to make your wiki go slightly faster. The directory should not
+ ## be publically accessible from the web.
+ $wgCacheDirectory = "${cacheDir}";
+
+ # Site language code, should be one of the list in ./languages/data/Names.php
+ $wgLanguageCode = "en";
+
+ $wgSecretKey = file_get_contents("${stateDir}/secret.key");
+
+ # Changing this will log out all existing sessions.
+ $wgAuthenticationTokenVersion = "";
+
+ ## For attaching licensing metadata to pages, and displaying an
+ ## appropriate copyright notice / icon. GNU Free Documentation
+ ## License and Creative Commons licenses are supported so far.
+ $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
+ $wgRightsUrl = "";
+ $wgRightsText = "";
+ $wgRightsIcon = "";
+
+ # Path to the GNU diff3 utility. Used for conflict resolution.
+ $wgDiff = "${pkgs.diffutils}/bin/diff";
+ $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
+
+ # Enabled skins.
+ ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)}
+
+ # Enabled extensions.
+ ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)}
+
+
+ # End of automatically generated settings.
+ # Add more configuration options below.
+
+ ${cfg.extraConfig}
+ '';
+
+in
+{
+ # interface
+ options = {
+ services.mediawiki = {
+
+ enable = mkEnableOption "MediaWiki";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.mediawiki;
+ description = "Which MediaWiki package to use.";
+ };
+
+ name = mkOption {
+ default = "MediaWiki";
+ example = "Foobar Wiki";
+ description = "Name of the wiki.";
+ };
+
+ uploadsDir = mkOption {
+ type = types.nullOr types.path;
+ default = "${stateDir}/uploads";
+ description = ''
+ This directory is used for uploads of pictures. The directory passed here is automatically
+ created and permissions adjusted as required.
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = types.path;
+ description = "A file containing the initial password for the admin user.";
+ example = "/run/keys/mediawiki-password";
+ };
+
+ skins = mkOption {
+ default = {};
+ type = types.attrsOf types.path;
+ description = ''
+ List of paths whose content is copied to the 'skins'
+ subdirectory of the MediaWiki installation.
+ '';
+ };
+
+ extensions = mkOption {
+ default = {};
+ type = types.attrsOf types.path;
+ description = ''
+ List of paths whose content is copied to the 'extensions'
+ subdirectory of the MediaWiki installation.
+ '';
+ };
+
+ database = {
+ type = mkOption {
+ type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ];
+ default = "mysql";
+ description = "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 3306;
+ description = "Database host port.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "mediawiki";
+ description = "Database name.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "mediawiki";
+ description = "Database user.";
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/mediawiki-dbpassword";
+ description = ''
+ A file containing the password corresponding to
+ <option>database.user</option>.
+ '';
+ };
+
+ tablePrefix = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ If you only have access to a single database and wish to install more than
+ one version of MediaWiki, or have other applications that also use the
+ database, you can give the table names a unique prefix to stop any naming
+ conflicts or confusion.
+ See <link xlink:href='https://www.mediawiki.org/wiki/Manual:$wgDBprefix'/>.
+ '';
+ };
+
+ socket = mkOption {
+ type = types.nullOr types.path;
+ default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" else null;
+ defaultText = "/run/mysqld/mysqld.sock";
+ description = "Path to the unix socket file to use for authentication.";
+ };
+
+ createLocally = mkOption {
+ type = types.bool;
+ default = cfg.database.type == "mysql";
+ defaultText = "true";
+ description = ''
+ Create the database and database user locally.
+ This currently only applies if database type "mysql" is selected.
+ '';
+ };
+ };
+
+ virtualHost = mkOption {
+ type = types.submodule ({
+ options = import ../web-servers/apache-httpd/per-server-options.nix {
+ inherit lib;
+ forMainServer = false;
+ };
+ });
+ example = literalExample ''
+ {
+ hostName = "mediawiki.example.org";
+ enableSSL = true;
+ adminAddr = "webmaster@example.org";
+ sslServerCert = "/var/lib/acme/mediawiki.example.org/full.pem";
+ sslServerKey = "/var/lib/acme/mediawiki.example.org/key.pem";
+ }
+ '';
+ description = ''
+ Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+ See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+ '';
+ };
+
+ poolConfig = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool ]);
+ default = {
+ "pm" = "dynamic";
+ "pm.max_children" = 32;
+ "pm.start_servers" = 2;
+ "pm.min_spare_servers" = 2;
+ "pm.max_spare_servers" = 4;
+ "pm.max_requests" = 500;
+ };
+ description = ''
+ Options for the MediaWiki PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+ for details on configuration directives.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ description = ''
+ Any additional text to be appended to MediaWiki's
+ LocalSettings.php configuration file. For configuration
+ settings, see <link xlink:href="https://www.mediawiki.org/wiki/Manual:Configuration_settings"/>.
+ '';
+ default = "";
+ example = ''
+ $wgEnableEmail = false;
+ '';
+ };
+
+ };
+ };
+
+ # implementation
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.database.createLocally -> cfg.database.type == "mysql";
+ message = "services.mediawiki.createLocally is currently only supported for database type 'mysql'";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.user == user;
+ message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.socket != null;
+ message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+ message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true";
+ }
+ ];
+
+ services.mediawiki.skins = {
+ MonoBook = "${cfg.package}/share/mediawiki/skins/MonoBook";
+ Timeless = "${cfg.package}/share/mediawiki/skins/Timeless";
+ Vector = "${cfg.package}/share/mediawiki/skins/Vector";
+ };
+
+ services.mysql = mkIf cfg.database.createLocally {
+ enable = true;
+ package = mkDefault pkgs.mariadb;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ services.phpfpm.pools.mediawiki = {
+ inherit user group;
+ phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}";
+ settings = {
+ "listen.owner" = config.services.httpd.user;
+ "listen.group" = config.services.httpd.group;
+ } // cfg.poolConfig;
+ };
+
+ services.httpd = {
+ enable = true;
+ adminAddr = mkDefault cfg.virtualHost.adminAddr;
+ extraModules = [ "proxy_fcgi" ];
+ virtualHosts = [ (mkMerge [
+ cfg.virtualHost {
+ documentRoot = mkForce "${pkg}/share/mediawiki";
+ extraConfig = ''
+ <Directory "${pkg}/share/mediawiki">
+ <FilesMatch "\.php$">
+ <If "-f %{REQUEST_FILENAME}">
+ SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+ </If>
+ </FilesMatch>
+
+ Require all granted
+ DirectoryIndex index.php
+ AllowOverride All
+ </Directory>
+ '' + optionalString (cfg.uploadsDir != null) ''
+ Alias "/images" "${cfg.uploadsDir}"
+ <Directory "${cfg.uploadsDir}">
+ Require all granted
+ </Directory>
+ '';
+ }
+ ]) ];
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${stateDir}' 0750 ${user} ${group} - -"
+ "d '${cacheDir}' 0750 ${user} ${group} - -"
+ ] ++ optionals (cfg.uploadsDir != null) [
+ "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+ "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+ ];
+
+ systemd.services.mediawiki-init = {
+ wantedBy = [ "multi-user.target" ];
+ before = [ "phpfpm-mediawiki.service" ];
+ after = optional cfg.database.createLocally "mysql.service";
+ script = ''
+ if ! test -e "${stateDir}/secret.key"; then
+ tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 64 > ${stateDir}/secret.key
+ fi
+
+ echo "exit( wfGetDB( DB_MASTER )->tableExists( 'user' ) ? 1 : 0 );" | \
+ ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/eval.php --conf ${mediawikiConfig} && \
+ ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \
+ --confpath /tmp \
+ --scriptpath / \
+ --dbserver ${cfg.database.host}${optionalString (cfg.database.socket != null) ":${cfg.database.socket}"} \
+ --dbport ${toString cfg.database.port} \
+ --dbname ${cfg.database.name} \
+ ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \
+ --dbuser ${cfg.database.user} \
+ ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \
+ --passfile ${cfg.passwordFile} \
+ ${cfg.name} \
+ admin
+
+ ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick
+ '';
+
+ serviceConfig = {
+ Type = "oneshot";
+ User = user;
+ Group = group;
+ PrivateTmp = true;
+ };
+ };
+
+ systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service";
+
+ users.users.${user}.group = group;
+
+ environment.systemPackages = [ mediawikiScripts ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/miniflux.nix b/nixpkgs/nixos/modules/services/web-apps/miniflux.nix
new file mode 100644
index 00000000000..304712d0efc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/miniflux.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.miniflux;
+
+ dbUser = "miniflux";
+ dbPassword = "miniflux";
+ dbHost = "localhost";
+ dbName = "miniflux";
+
+ defaultCredentials = pkgs.writeText "miniflux-admin-credentials" ''
+ ADMIN_USERNAME=admin
+ ADMIN_PASSWORD=password
+ '';
+
+ pgsu = "${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser}";
+ pgbin = "${config.services.postgresql.package}/bin";
+ preStart = pkgs.writeScript "miniflux-pre-start" ''
+ #!${pkgs.runtimeShell}
+ db_exists() {
+ [ "$(${pgsu} ${pgbin}/psql -Atc "select 1 from pg_database where datname='$1'")" == "1" ]
+ }
+ if ! db_exists "${dbName}"; then
+ ${pgsu} ${pgbin}/psql postgres -c "CREATE ROLE ${dbUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${dbPassword}'"
+ ${pgsu} ${pgbin}/createdb --owner "${dbUser}" "${dbName}"
+ ${pgsu} ${pgbin}/psql "${dbName}" -c "CREATE EXTENSION IF NOT EXISTS hstore"
+ fi
+ '';
+in
+
+{
+ options = {
+ services.miniflux = {
+ enable = mkEnableOption "miniflux";
+
+ config = mkOption {
+ type = types.attrsOf types.str;
+ example = literalExample ''
+ {
+ CLEANUP_FREQUENCY = "48";
+ LISTEN_ADDR = "localhost:8080";
+ }
+ '';
+ description = ''
+ Configuration for Miniflux, refer to
+ <link xlink:href="http://docs.miniflux.app/en/latest/configuration.html"/>
+ for documentation on the supported values.
+ '';
+ };
+
+ adminCredentialsFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ File containing the ADMIN_USERNAME, default is "admin", and
+ ADMIN_PASSWORD (length >= 6), default is "password"; in the format of
+ an EnvironmentFile=, as described by systemd.exec(5).
+ '';
+ example = "/etc/nixos/miniflux-admin-credentials";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ services.miniflux.config = {
+ LISTEN_ADDR = mkDefault "localhost:8080";
+ DATABASE_URL = "postgresql://${dbUser}:${dbPassword}@${dbHost}/${dbName}?sslmode=disable";
+ RUN_MIGRATIONS = "1";
+ CREATE_ADMIN = "1";
+ };
+
+ services.postgresql.enable = true;
+
+ systemd.services.miniflux = {
+ description = "Miniflux service";
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "postgresql.service" ];
+ after = [ "network.target" "postgresql.service" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.miniflux}/bin/miniflux";
+ ExecStartPre = "+${preStart}";
+ DynamicUser = true;
+ RuntimeDirectory = "miniflux";
+ RuntimeDirectoryMode = "0700";
+ EnvironmentFile = if cfg.adminCredentialsFile == null
+ then defaultCredentials
+ else cfg.adminCredentialsFile;
+ };
+
+ environment = cfg.config;
+ };
+ environment.systemPackages = [ pkgs.miniflux ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/moodle.nix b/nixpkgs/nixos/modules/services/web-apps/moodle.nix
new file mode 100644
index 00000000000..211bc17ee19
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/moodle.nix
@@ -0,0 +1,315 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+ inherit (lib) concatStringsSep literalExample mapAttrsToList optional optionalString;
+
+ cfg = config.services.moodle;
+ fpm = config.services.phpfpm.pools.moodle;
+
+ user = "moodle";
+ group = config.services.httpd.group;
+ stateDir = "/var/lib/moodle";
+
+ moodleConfig = pkgs.writeText "config.php" ''
+ <?php // Moodle configuration file
+
+ unset($CFG);
+ global $CFG;
+ $CFG = new stdClass();
+
+ $CFG->dbtype = '${ { mysql = "mariadb"; pgsql = "pgsql"; }.${cfg.database.type} }';
+ $CFG->dblibrary = 'native';
+ $CFG->dbhost = '${cfg.database.host}';
+ $CFG->dbname = '${cfg.database.name}';
+ $CFG->dbuser = '${cfg.database.user}';
+ ${optionalString (cfg.database.passwordFile != null) "$CFG->dbpass = file_get_contents('${cfg.database.passwordFile}');"}
+ $CFG->prefix = 'mdl_';
+ $CFG->dboptions = array (
+ 'dbpersist' => 0,
+ 'dbport' => '${toString cfg.database.port}',
+ ${optionalString (cfg.database.socket != null) "'dbsocket' => '${cfg.database.socket}',"}
+ 'dbcollation' => 'utf8mb4_unicode_ci',
+ );
+
+ $CFG->wwwroot = '${if cfg.virtualHost.enableSSL then "https" else "http"}://${cfg.virtualHost.hostName}';
+ $CFG->dataroot = '${stateDir}';
+ $CFG->admin = 'admin';
+
+ $CFG->directorypermissions = 02777;
+ $CFG->disableupdateautodeploy = true;
+
+ $CFG->pathtogs = '${pkgs.ghostscript}/bin/gs';
+ $CFG->pathtophp = '${pkgs.php}/bin/php';
+ $CFG->pathtodu = '${pkgs.coreutils}/bin/du';
+ $CFG->aspellpath = '${pkgs.aspell}/bin/aspell';
+ $CFG->pathtodot = '${pkgs.graphviz}/bin/dot';
+
+ ${cfg.extraConfig}
+
+ require_once('${cfg.package}/share/moodle/lib/setup.php');
+
+ // There is no php closing tag in this file,
+ // it is intentional because it prevents trailing whitespace problems!
+ '';
+
+ mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+ pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+in
+{
+ # interface
+ options.services.moodle = {
+ enable = mkEnableOption "Moodle web application";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.moodle;
+ defaultText = "pkgs.moodle";
+ description = "The Moodle package to use.";
+ };
+
+ initialPassword = mkOption {
+ type = types.str;
+ example = "correcthorsebatterystaple";
+ description = ''
+ Specifies the initial password for the admin, i.e. the password assigned if the user does not already exist.
+ The password specified here is world-readable in the Nix store, so it should be changed promptly.
+ '';
+ };
+
+ database = {
+ type = mkOption {
+ type = types.enum [ "mysql" "pgsql" ];
+ default = "mysql";
+ description = ''Database engine to use.'';
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ description = "Database host port.";
+ default = {
+ mysql = 3306;
+ pgsql = 5432;
+ }.${cfg.database.type};
+ defaultText = "3306";
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "moodle";
+ description = "Database name.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "moodle";
+ description = "Database user.";
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/moodle-dbpassword";
+ description = ''
+ A file containing the password corresponding to
+ <option>database.user</option>.
+ '';
+ };
+
+ socket = mkOption {
+ type = types.nullOr types.path;
+ default =
+ if mysqlLocal then "/run/mysqld/mysqld.sock"
+ else if pgsqlLocal then "/run/postgresql"
+ else null;
+ defaultText = "/run/mysqld/mysqld.sock";
+ description = "Path to the unix socket file to use for authentication.";
+ };
+
+ createLocally = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Create the database and database user locally.";
+ };
+ };
+
+ virtualHost = mkOption {
+ type = types.submodule ({
+ options = import ../web-servers/apache-httpd/per-server-options.nix {
+ inherit lib;
+ forMainServer = false;
+ };
+ });
+ example = {
+ hostName = "moodle.example.org";
+ enableSSL = true;
+ adminAddr = "webmaster@example.org";
+ sslServerCert = "/var/lib/acme/moodle.example.org/full.pem";
+ sslServerKey = "/var/lib/acme/moodle.example.org/key.pem";
+ };
+ description = ''
+ Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+ See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+ '';
+ };
+
+ poolConfig = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool ]);
+ default = {
+ "pm" = "dynamic";
+ "pm.max_children" = 32;
+ "pm.start_servers" = 2;
+ "pm.min_spare_servers" = 2;
+ "pm.max_spare_servers" = 4;
+ "pm.max_requests" = 500;
+ };
+ description = ''
+ Options for the Moodle PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+ for details on configuration directives.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Any additional text to be appended to the config.php
+ configuration file. This is a PHP script. For configuration
+ details, see <link xlink:href="https://docs.moodle.org/37/en/Configuration_file"/>.
+ '';
+ example = ''
+ $CFG->disableupdatenotifications = true;
+ '';
+ };
+ };
+
+ # implementation
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.database.createLocally -> cfg.database.user == user;
+ message = "services.moodle.database.user must be set to ${user} if services.moodle.database.createLocally is set true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+ message = "a password cannot be specified if services.moodle.database.createLocally is set to true";
+ }
+ ];
+
+ services.mysql = mkIf mysqlLocal {
+ enable = true;
+ package = mkDefault pkgs.mariadb;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = {
+ "${cfg.database.name}.*" = "SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER";
+ };
+ }
+ ];
+ };
+
+ services.postgresql = mkIf pgsqlLocal {
+ enable = true;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ services.phpfpm.pools.moodle = {
+ inherit user group;
+ phpEnv.MOODLE_CONFIG = "${moodleConfig}";
+ phpOptions = ''
+ zend_extension = opcache.so
+ opcache.enable = 1
+ '';
+ settings = {
+ "listen.owner" = config.services.httpd.user;
+ "listen.group" = config.services.httpd.group;
+ } // cfg.poolConfig;
+ };
+
+ services.httpd = {
+ enable = true;
+ adminAddr = mkDefault cfg.virtualHost.adminAddr;
+ extraModules = [ "proxy_fcgi" ];
+ virtualHosts = [ (mkMerge [
+ cfg.virtualHost {
+ documentRoot = mkForce "${cfg.package}/share/moodle";
+ extraConfig = ''
+ <Directory "${cfg.package}/share/moodle">
+ <FilesMatch "\.php$">
+ <If "-f %{REQUEST_FILENAME}">
+ SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+ </If>
+ </FilesMatch>
+ Options -Indexes
+ DirectoryIndex index.php
+ </Directory>
+ '';
+ }
+ ]) ];
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${stateDir}' 0750 ${user} ${group} - -"
+ ];
+
+ systemd.services.moodle-init = {
+ wantedBy = [ "multi-user.target" ];
+ before = [ "phpfpm-moodle.service" ];
+ after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+ environment.MOODLE_CONFIG = moodleConfig;
+ script = ''
+ ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/check_database_schema.php && rc=$? || rc=$?
+
+ [ "$rc" == 1 ] && ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/upgrade.php \
+ --non-interactive \
+ --allow-unstable
+
+ [ "$rc" == 2 ] && ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/install_database.php \
+ --agree-license \
+ --adminpass=${cfg.initialPassword}
+
+ true
+ '';
+ serviceConfig = {
+ User = user;
+ Group = group;
+ Type = "oneshot";
+ };
+ };
+
+ systemd.services.moodle-cron = {
+ description = "Moodle cron service";
+ after = [ "moodle-init.service" ];
+ environment.MOODLE_CONFIG = moodleConfig;
+ serviceConfig = {
+ User = user;
+ Group = group;
+ ExecStart = "${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/cron.php";
+ };
+ };
+
+ systemd.timers.moodle-cron = {
+ description = "Moodle cron timer";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = "minutely";
+ };
+ };
+
+ systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+ users.users.${user}.group = group;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix
new file mode 100644
index 00000000000..db5dc915c89
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix
@@ -0,0 +1,558 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nextcloud;
+ fpm = config.services.phpfpm.pools.nextcloud;
+
+ phpPackage = pkgs.php73;
+ phpPackages = pkgs.php73Packages;
+
+ toKeyValue = generators.toKeyValue {
+ mkKeyValue = generators.mkKeyValueDefault {} " = ";
+ };
+
+ phpOptionsExtensions = ''
+ ${optionalString cfg.caching.apcu "extension=${phpPackages.apcu}/lib/php/extensions/apcu.so"}
+ ${optionalString cfg.caching.redis "extension=${phpPackages.redis}/lib/php/extensions/redis.so"}
+ ${optionalString cfg.caching.memcached "extension=${phpPackages.memcached}/lib/php/extensions/memcached.so"}
+ extension=${phpPackages.imagick}/lib/php/extensions/imagick.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" \
+ ${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.";
+ };
+ logLevel = mkOption {
+ type = types.ints.between 0 4;
+ default = 2;
+ description = "Log level value between 0 (DEBUG) and 4 (FATAL).";
+ };
+ https = mkOption {
+ type = types.bool;
+ default = false;
+ description = "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.
+ '';
+ };
+
+ phpOptions = mkOption {
+ type = types.attrsOf types.str;
+ default = {
+ short_open_tag = "Off";
+ expose_php = "Off";
+ error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
+ display_errors = "stderr";
+ "opcache.enable_cli" = "1";
+ "opcache.interned_strings_buffer" = "8";
+ "opcache.max_accelerated_files" = "10000";
+ "opcache.memory_consumption" = "128";
+ "opcache.revalidate_freq" = "1";
+ "opcache.fast_shutdown" = "1";
+ "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
+ catch_workers_output = "yes";
+ };
+ description = ''
+ Options for PHP's php.ini file for nextcloud.
+ '';
+ };
+
+ poolSettings = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool ]);
+ default = {
+ "pm" = "dynamic";
+ "pm.max_children" = "32";
+ "pm.start_servers" = "2";
+ "pm.min_spare_servers" = "2";
+ "pm.max_spare_servers" = "4";
+ "pm.max_requests" = "500";
+ };
+ description = ''
+ Options for nextcloud's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+ '';
+ };
+
+ poolConfig = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Options for nextcloud's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+ '';
+ };
+
+ config = {
+ dbtype = mkOption {
+ type = types.enum [ "sqlite" "pgsql" "mysql" ];
+ default = "sqlite";
+ description = "Database type.";
+ };
+ dbname = mkOption {
+ type = types.nullOr types.str;
+ default = "nextcloud";
+ description = "Database name.";
+ };
+ dbuser = mkOption {
+ type = types.nullOr types.str;
+ default = "nextcloud";
+ description = "Database user.";
+ };
+ dbpass = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Database password. Use <literal>dbpassFile</literal> to avoid this
+ being world-readable in the <literal>/nix/store</literal>.
+ '';
+ };
+ dbpassFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The full path to a file that contains the database password.
+ '';
+ };
+ dbhost = mkOption {
+ type = types.nullOr types.str;
+ default = "localhost";
+ description = ''
+ Database host.
+
+ Note: for using Unix authentication with PostgreSQL, this should be
+ set to <literal>/run/postgresql</literal>.
+ '';
+ };
+ dbport = mkOption {
+ type = with types; nullOr (either int str);
+ default = null;
+ description = "Database port.";
+ };
+ dbtableprefix = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Table prefix in Nextcloud database.";
+ };
+ adminuser = mkOption {
+ type = types.str;
+ default = "root";
+ description = "Admin username.";
+ };
+ adminpass = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Admin password. Use <literal>adminpassFile</literal> to avoid this
+ being world-readable in the <literal>/nix/store</literal>.
+ '';
+ };
+ adminpassFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The full path to a file that contains the admin's password.
+ '';
+ };
+
+ extraTrustedDomains = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Trusted domains, from which the nextcloud installation will be
+ acessible. You don't need to add
+ <literal>services.nextcloud.hostname</literal> here.
+ '';
+ };
+
+ overwriteProtocol = mkOption {
+ type = types.nullOr (types.enum [ "http" "https" ]);
+ default = null;
+ example = "https";
+
+ description = ''
+ Force Nextcloud to always use HTTPS i.e. for link generation. Nextcloud
+ uses the currently used protocol by default, but when behind a reverse-proxy,
+ it may use <literal>http</literal> for everything although Nextcloud
+ may be served via HTTPS.
+ '';
+ };
+ };
+
+ caching = {
+ apcu = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to load the APCu module into PHP.
+ '';
+ };
+ redis = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to load the Redis module into PHP.
+ You still need to enable Redis in your config.php.
+ See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+ '';
+ };
+ memcached = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to load the Memcached module into PHP.
+ You still need to enable Memcached in your config.php.
+ See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+ '';
+ };
+ };
+ autoUpdateApps = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Run regular auto update of all apps installed from the nextcloud app store.
+ '';
+ };
+ startAt = mkOption {
+ type = with types; either str (listOf str);
+ default = "05:00:00";
+ example = "Sun 14:00:00";
+ description = ''
+ When to run the update. See `systemd.services.&lt;name&gt;.startAt`.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ { assertions = let acfg = cfg.config; in [
+ { assertion = !(acfg.dbpass != null && acfg.dbpassFile != null);
+ message = "Please specify no more than one of dbpass or dbpassFile";
+ }
+ { assertion = ((acfg.adminpass != null || acfg.adminpassFile != null)
+ && !(acfg.adminpass != null && acfg.adminpassFile != null));
+ message = "Please specify exactly one of adminpass or adminpassFile";
+ }
+ ];
+
+ warnings = optional (cfg.poolConfig != null) ''
+ Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
+ Please migrate your configuration to config.services.nextcloud.poolSettings.
+ '';
+ }
+
+ { systemd.timers.nextcloud-cron = {
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnBootSec = "5m";
+ timerConfig.OnUnitActiveSec = "15m";
+ timerConfig.Unit = "nextcloud-cron.service";
+ };
+
+ systemd.services = {
+ nextcloud-setup = let
+ c = cfg.config;
+ writePhpArrary = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]";
+ overrideConfig = pkgs.writeText "nextcloud-config.php" ''
+ <?php
+ ${optionalString (c.dbpassFile != null) ''
+ function nix_read_pwd() {
+ $file = "${c.dbpassFile}";
+ if (!file_exists($file)) {
+ throw new \RuntimeException(sprintf(
+ "Cannot start Nextcloud, dbpass file %s set by NixOS doesn't exist!",
+ $file
+ ));
+ }
+
+ return trim(file_get_contents($file));
+ }
+ ''}
+ $CONFIG = [
+ 'apps_paths' => [
+ [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ],
+ [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ],
+ ],
+ 'datadirectory' => '${cfg.home}/data',
+ 'skeletondirectory' => '${cfg.skeletonDirectory}',
+ ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
+ 'log_type' => 'syslog',
+ 'log_level' => '${builtins.toString cfg.logLevel}',
+ ${optionalString (c.overwriteProtocol != null) "'overwriteprotocol' => '${c.overwriteProtocol}',"}
+ ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
+ ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"}
+ ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"}
+ ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
+ ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
+ ${optionalString (c.dbpass != null) "'dbpassword' => '${c.dbpass}',"}
+ ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_pwd(),"}
+ 'dbtype' => '${c.dbtype}',
+ 'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
+ ];
+ '';
+ occInstallCmd = let
+ dbpass = if c.dbpassFile != null
+ then ''"$(<"${toString c.dbpassFile}")"''
+ else if c.dbpass != null
+ then ''"${toString c.dbpass}"''
+ else null;
+ adminpass = if c.adminpassFile != null
+ then ''"$(<"${toString c.adminpassFile}")"''
+ else ''"${toString c.adminpass}"'';
+ installFlags = concatStringsSep " \\\n "
+ (mapAttrsToList (k: v: "${k} ${toString v}") {
+ "--database" = ''"${c.dbtype}"'';
+ # The following attributes are optional depending on the type of
+ # database. Those that evaluate to null on the left hand side
+ # will be omitted.
+ ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
+ ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
+ ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"'';
+ ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
+ ${if (any (x: x != null) [c.dbpass c.dbpassFile])
+ then "--database-pass" else null} = dbpass;
+ ${if c.dbtableprefix != null
+ then "--database-table-prefix" else null} = ''"${toString c.dbtableprefix}"'';
+ "--admin-user" = ''"${c.adminuser}"'';
+ "--admin-pass" = adminpass;
+ "--data-dir" = ''"${cfg.home}/data"'';
+ });
+ in ''
+ ${occ}/bin/nextcloud-occ maintenance:install \
+ ${installFlags}
+ '';
+ occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0
+ (i: v: ''
+ ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
+ ${toString i} --value="${toString v}"
+ '') ([ cfg.hostName ] ++ cfg.config.extraTrustedDomains));
+
+ in {
+ wantedBy = [ "multi-user.target" ];
+ before = [ "phpfpm-nextcloud.service" ];
+ 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 = "${phpPackage}/bin/php -f ${pkgs.nextcloud}/cron.php";
+ };
+ nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable {
+ serviceConfig.Type = "oneshot";
+ serviceConfig.ExecStart = "${occ}/bin/nextcloud-occ app:update --all";
+ startAt = cfg.autoUpdateApps.startAt;
+ };
+ };
+
+ services.phpfpm = {
+ pools.nextcloud = {
+ user = "nextcloud";
+ group = "nginx";
+ phpOptions = phpOptionsExtensions + phpOptionsStr;
+ phpPackage = phpPackage;
+ phpEnv = {
+ NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+ PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
+ };
+ settings = mapAttrs (name: mkDefault) {
+ "listen.owner" = "nginx";
+ "listen.group" = "nginx";
+ } // cfg.poolSettings;
+ extraConfig = cfg.poolConfig;
+ };
+ };
+
+ users.extraUsers.nextcloud = {
+ home = "${cfg.home}";
+ group = "nginx";
+ createHome = true;
+ };
+
+ environment.systemPackages = [ occ ];
+ }
+
+ (mkIf cfg.nginx.enable {
+ services.nginx = {
+ enable = true;
+ virtualHosts = {
+ ${cfg.hostName} = {
+ root = pkgs.nextcloud;
+ locations = {
+ "= /robots.txt" = {
+ priority = 100;
+ extraConfig = ''
+ allow all;
+ log_not_found off;
+ access_log off;
+ '';
+ };
+ "/" = {
+ priority = 200;
+ extraConfig = "rewrite ^ /index.php$request_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\\/.+|ocm-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:${fpm.socket};
+ fastcgi_intercept_errors on;
+ fastcgi_request_buffering off;
+ fastcgi_read_timeout 120s;
+ '';
+ };
+ "~ ^\\/(?:updater|ocs-provider|ocm-provider)(?:$|\\/)".extraConfig = ''
+ try_files $uri/ =404;
+ index index.php;
+ '';
+ "~ \\.(?:css|js|woff2?|svg|gif)$".extraConfig = ''
+ try_files $uri /index.php$request_uri;
+ add_header Cache-Control "public, max-age=15778463";
+ add_header X-Content-Type-Options nosniff;
+ add_header X-XSS-Protection "1; mode=block";
+ add_header X-Robots-Tag none;
+ add_header X-Download-Options noopen;
+ add_header X-Permitted-Cross-Domain-Policies none;
+ add_header Referrer-Policy no-referrer;
+ access_log off;
+ '';
+ "~ \\.(?:png|html|ttf|ico|jpg|jpeg)$".extraConfig = ''
+ try_files $uri /index.php$request_uri;
+ access_log off;
+ '';
+ };
+ extraConfig = ''
+ add_header X-Content-Type-Options nosniff;
+ add_header X-XSS-Protection "1; mode=block";
+ add_header X-Robots-Tag none;
+ add_header X-Download-Options noopen;
+ add_header X-Permitted-Cross-Domain-Policies none;
+ add_header Referrer-Policy no-referrer;
+ error_page 403 /core/templates/403.php;
+ error_page 404 /core/templates/404.php;
+ client_max_body_size ${cfg.maxUploadSize};
+ fastcgi_buffers 64 4K;
+ fastcgi_hide_header X-Powered-By;
+ gzip on;
+ gzip_vary on;
+ gzip_comp_level 4;
+ gzip_min_length 256;
+ gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
+ gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
+
+ ${optionalString cfg.webfinger ''
+ rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
+ rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
+ ''}
+ '';
+ };
+ };
+ };
+ })
+ ]);
+
+ meta.doc = ./nextcloud.xml;
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml b/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml
new file mode 100644
index 00000000000..d66e0f0c299
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml
@@ -0,0 +1,117 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="module-services-nextcloud">
+ <title>Nextcloud</title>
+ <para>
+ <link xlink:href="https://nextcloud.com/">Nextcloud</link> is an open-source,
+ self-hostable cloud platform. The server setup can be automated using
+ <link linkend="opt-services.nextcloud.enable">services.nextcloud</link>. A
+ desktop client is packaged at <literal>pkgs.nextcloud-client</literal>.
+ </para>
+ <section xml:id="module-services-nextcloud-basic-usage">
+ <title>Basic usage</title>
+
+ <para>
+ Nextcloud is a PHP-based application which requires an HTTP server
+ (<literal><link linkend="opt-services.nextcloud.enable">services.nextcloud</link></literal>
+ optionally supports
+ <literal><link linkend="opt-services.nginx.enable">services.nginx</link></literal>)
+ and a database (it's recommended to use
+ <literal><link linkend="opt-services.postgresql.enable">services.postgresql</link></literal>).
+ </para>
+
+ <para>
+ A very basic configuration may look like this:
+<programlisting>{ pkgs, ... }:
+{
+ services.nextcloud = {
+ <link linkend="opt-services.nextcloud.enable">enable</link> = true;
+ <link linkend="opt-services.nextcloud.hostName">hostName</link> = "nextcloud.tld";
+ <link linkend="opt-services.nextcloud.nginx.enable">nginx.enable</link> = true;
+ config = {
+ <link linkend="opt-services.nextcloud.config.dbtype">dbtype</link> = "pgsql";
+ <link linkend="opt-services.nextcloud.config.dbuser">dbuser</link> = "nextcloud";
+ <link linkend="opt-services.nextcloud.config.dbhost">dbhost</link> = "/run/postgresql"; # nextcloud will add /.s.PGSQL.5432 by itself
+ <link linkend="opt-services.nextcloud.config.dbname">dbname</link> = "nextcloud";
+ <link linkend="opt-services.nextcloud.config.adminpassFile">adminpassFile</link> = "/path/to/admin-pass-file";
+ <link linkend="opt-services.nextcloud.config.adminuser">adminuser</link> = "root";
+ };
+ };
+
+ services.postgresql = {
+ <link linkend="opt-services.postgresql.enable">enable</link> = true;
+ <link linkend="opt-services.postgresql.ensureDatabases">ensureDatabases</link> = [ "nextcloud" ];
+ <link linkend="opt-services.postgresql.ensureUsers">ensureUsers</link> = [
+ { name = "nextcloud";
+ ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
+ }
+ ];
+ };
+
+ # ensure that postgres is running *before* running the setup
+ systemd.services."nextcloud-setup" = {
+ requires = ["postgresql.service"];
+ after = ["postgresql.service"];
+ };
+
+ <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ];
+}</programlisting>
+ </para>
+
+ <para>
+ The options <literal>hostName</literal> and <literal>nginx.enable</literal>
+ are used internally to configure an HTTP server using
+ <literal><link xlink:href="https://php-fpm.org/">PHP-FPM</link></literal>
+ and <literal>nginx</literal>. The <literal>config</literal> attribute set is
+ used by the imperative installer and all values are written to an additional file
+ to ensure that changes can be applied by changing the module's options.
+ </para>
+
+ <para>
+ In case the application serves multiple domains (those are checked with
+ <literal><link xlink:href="http://php.net/manual/en/reserved.variables.server.php">$_SERVER['HTTP_HOST']</link></literal>)
+ it's needed to add them to
+ <literal><link linkend="opt-services.nextcloud.config.extraTrustedDomains">services.nextcloud.config.extraTrustedDomains</link></literal>.
+ </para>
+
+ <para>
+ Auto updates for Nextcloud apps can be enabled using
+ <literal><link linkend="opt-services.nextcloud.autoUpdateApps.enable">services.nextcloud.autoUpdateApps</link></literal>.
+</para>
+
+ </section>
+ <section xml:id="module-services-nextcloud-pitfalls-during-upgrade">
+ <title>Pitfalls</title>
+
+ <para>
+ Unfortunately Nextcloud appears to be very stateful when it comes to
+ managing its own configuration. The config file lives in the home directory
+ of the <literal>nextcloud</literal> user (by default
+ <literal>/var/lib/nextcloud/config/config.php</literal>) and is also used to
+ track several states of the application (e.g. whether installed or not).
+ </para>
+
+ <para>
+ All configuration parameters are also stored in
+ <literal>/var/lib/nextcloud/config/override.config.php</literal> which is generated by
+ the module and linked from the store to ensure that all values from <literal>config.php</literal>
+ can be modified by the module.
+ However <literal>config.php</literal> manages the application's state and shouldn't be touched
+ manually because of that.
+ </para>
+
+ <warning>
+ <para>Don't delete <literal>config.php</literal>! This file
+ tracks the application's state and a deletion can cause unwanted
+ side-effects!</para>
+ </warning>
+
+ <warning>
+ <para>Don't rerun <literal>nextcloud-occ
+ maintenance:install</literal>! This command tries to install the application
+ and can cause unwanted side-effects!</para>
+ </warning>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/nexus.nix b/nixpkgs/nixos/modules/services/web-apps/nexus.nix
new file mode 100644
index 00000000000..3af97e146d0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/nexus.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.nexus;
+
+in
+
+{
+ options = {
+ services.nexus = {
+ enable = mkEnableOption "Sonatype Nexus3 OSS service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.nexus;
+ description = "Package which runs Nexus3";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "nexus";
+ description = "User which runs Nexus3.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "nexus";
+ description = "Group which runs Nexus3.";
+ };
+
+ home = mkOption {
+ type = types.str;
+ default = "/var/lib/sonatype-work";
+ description = "Home directory of the Nexus3 instance.";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Address to listen on.";
+ };
+
+ listenPort = mkOption {
+ type = types.int;
+ default = 8081;
+ description = "Port to listen on.";
+ };
+
+ jvmOpts = mkOption {
+ type = types.lines;
+ default = ''
+ -Xms1200M
+ -Xmx1200M
+ -XX:MaxDirectMemorySize=2G
+ -XX:+UnlockDiagnosticVMOptions
+ -XX:+UnsyncloadClass
+ -XX:+LogVMOutput
+ -XX:LogFile=${cfg.home}/nexus3/log/jvm.log
+ -XX:-OmitStackTraceInFastThrow
+ -Djava.net.preferIPv4Stack=true
+ -Dkaraf.home=${cfg.package}
+ -Dkaraf.base=${cfg.package}
+ -Dkaraf.etc=${cfg.package}/etc/karaf
+ -Djava.util.logging.config.file=${cfg.package}/etc/karaf/java.util.logging.properties
+ -Dkaraf.data=${cfg.home}/nexus3
+ -Djava.io.tmpdir=${cfg.home}/nexus3/tmp
+ -Dkaraf.startLocalConsole=false
+ '';
+
+ description = ''
+ Options for the JVM written to `nexus.jvmopts`.
+ Please refer to the docs (https://help.sonatype.com/repomanager3/installation/configuring-the-runtime-environment)
+ for further information.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.${cfg.user} = {
+ isSystemUser = true;
+ group = cfg.group;
+ home = cfg.home;
+ createHome = true;
+ };
+
+ users.groups.${cfg.group} = {};
+
+ systemd.services.nexus = {
+ description = "Sonatype Nexus3";
+
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ cfg.home ];
+
+ environment = {
+ NEXUS_USER = cfg.user;
+ NEXUS_HOME = cfg.home;
+
+ VM_OPTS_FILE = pkgs.writeText "nexus.vmoptions" cfg.jvmOpts;
+ };
+
+ preStart = ''
+ mkdir -p ${cfg.home}/nexus3/etc
+
+ if [ ! -f ${cfg.home}/nexus3/etc/nexus.properties ]; then
+ echo "# Jetty section" > ${cfg.home}/nexus3/etc/nexus.properties
+ echo "application-port=${toString cfg.listenPort}" >> ${cfg.home}/nexus3/etc/nexus.properties
+ echo "application-host=${toString cfg.listenAddress}" >> ${cfg.home}/nexus3/etc/nexus.properties
+ else
+ sed 's/^application-port=.*/application-port=${toString cfg.listenPort}/' -i ${cfg.home}/nexus3/etc/nexus.properties
+ sed 's/^# application-port=.*/application-port=${toString cfg.listenPort}/' -i ${cfg.home}/nexus3/etc/nexus.properties
+ sed 's/^application-host=.*/application-host=${toString cfg.listenAddress}/' -i ${cfg.home}/nexus3/etc/nexus.properties
+ sed 's/^# application-host=.*/application-host=${toString cfg.listenAddress}/' -i ${cfg.home}/nexus3/etc/nexus.properties
+ fi
+ '';
+
+ script = "${cfg.package}/bin/nexus run";
+
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ PrivateTmp = true;
+ LimitNOFILE = 102642;
+ };
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ ironpinguin ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix b/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
new file mode 100644
index 00000000000..ad70ba70bbe
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.pgpkeyserver-lite;
+ sksCfg = config.services.sks;
+
+ webPkg = cfg.package;
+
+in
+
+{
+
+ options = {
+
+ services.pgpkeyserver-lite = {
+
+ enable = mkEnableOption "pgpkeyserver-lite on a nginx vHost proxying to a gpg keyserver";
+
+ package = mkOption {
+ default = pkgs.pgpkeyserver-lite;
+ defaultText = "pkgs.pgpkeyserver-lite";
+ type = types.package;
+ description = "
+ Which webgui derivation to use.
+ ";
+ };
+
+ hostname = mkOption {
+ type = types.str;
+ description = "
+ Which hostname to set the vHost to that is proxying to sks.
+ ";
+ };
+
+ hkpAddress = mkOption {
+ default = builtins.head sksCfg.hkpAddress;
+ type = types.str;
+ description = "
+ Wich ip address the sks-keyserver is listening on.
+ ";
+ };
+
+ hkpPort = mkOption {
+ default = sksCfg.hkpPort;
+ type = types.int;
+ description = "
+ Which port the sks-keyserver is listening on.
+ ";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ services.nginx.enable = true;
+
+ services.nginx.virtualHosts = let
+ hkpPort = builtins.toString cfg.hkpPort;
+ in {
+ ${cfg.hostname} = {
+ root = webPkg;
+ locations = {
+ "/pks".extraConfig = ''
+ proxy_pass http://${cfg.hkpAddress}:${hkpPort};
+ proxy_pass_header Server;
+ add_header Via "1.1 ${cfg.hostname}";
+ '';
+ };
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/restya-board.nix b/nixpkgs/nixos/modules/services/web-apps/restya-board.nix
new file mode 100644
index 00000000000..2c2f36ac598
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/restya-board.nix
@@ -0,0 +1,383 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+# TODO: are these php-packages needed?
+#imagick
+#php-geoip -> php.ini: extension = geoip.so
+#expat
+
+let
+ cfg = config.services.restya-board;
+ fpm = config.services.phpfpm.pools.${poolName};
+
+ runDir = "/run/restya-board";
+
+ poolName = "restya-board";
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.restya-board = {
+
+ enable = mkEnableOption "restya-board";
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/restya-board";
+ example = "/var/lib/restya-board";
+ description = ''
+ Data of the application.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "restya-board";
+ example = "restya-board";
+ description = ''
+ User account under which the web-application runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "nginx";
+ example = "nginx";
+ description = ''
+ Group account under which the web-application runs.
+ '';
+ };
+
+ virtualHost = {
+ serverName = mkOption {
+ type = types.str;
+ default = "restya.board";
+ description = ''
+ Name of the nginx virtualhost to use.
+ '';
+ };
+
+ listenHost = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = ''
+ Listen address for the virtualhost to use.
+ '';
+ };
+
+ listenPort = mkOption {
+ type = types.int;
+ default = 3000;
+ description = ''
+ Listen port for the virtualhost to use.
+ '';
+ };
+ };
+
+ database = {
+ host = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Host of the database. Leave 'null' to use a local PostgreSQL database.
+ A local PostgreSQL database is initialized automatically.
+ '';
+ };
+
+ port = mkOption {
+ type = types.nullOr types.int;
+ default = 5432;
+ description = ''
+ The database's port.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "restya_board";
+ description = ''
+ Name of the database. The database must exist.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "restya_board";
+ description = ''
+ The database user. The user must exist and have access to
+ the specified database.
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The database user's password. 'null' if no password is set.
+ '';
+ };
+ };
+
+ email = {
+ server = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "localhost";
+ description = ''
+ Hostname to send outgoing mail. Null to use the system MTA.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 25;
+ description = ''
+ Port used to connect to SMTP server.
+ '';
+ };
+
+ login = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ SMTP authentication login used when sending outgoing mail.
+ '';
+ };
+
+ password = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ SMTP authentication password used when sending outgoing mail.
+
+ ATTENTION: The password is stored world-readable in the nix-store!
+ '';
+ };
+ };
+
+ timezone = mkOption {
+ type = types.lines;
+ default = "GMT";
+ description = ''
+ Timezone the web-app runs in.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.phpfpm.pools = {
+ ${poolName} = {
+ inherit (cfg) user group;
+
+ phpOptions = ''
+ date.timezone = "CET"
+
+ ${optionalString (cfg.email.server != null) ''
+ SMTP = ${cfg.email.server}
+ smtp_port = ${toString cfg.email.port}
+ auth_username = ${cfg.email.login}
+ auth_password = ${cfg.email.password}
+ ''}
+ '';
+ settings = mapAttrs (name: mkDefault) {
+ "listen.owner" = "nginx";
+ "listen.group" = "nginx";
+ "listen.mode" = "0600";
+ "pm" = "dynamic";
+ "pm.max_children" = 75;
+ "pm.start_servers" = 10;
+ "pm.min_spare_servers" = 5;
+ "pm.max_spare_servers" = 20;
+ "pm.max_requests" = 500;
+ "catch_workers_output" = 1;
+ };
+ };
+ };
+
+ services.nginx.enable = true;
+ services.nginx.virtualHosts.${cfg.virtualHost.serverName} = {
+ listen = [ { addr = cfg.virtualHost.listenHost; port = cfg.virtualHost.listenPort; } ];
+ serverName = cfg.virtualHost.serverName;
+ root = runDir;
+ extraConfig = ''
+ index index.html index.php;
+
+ gzip on;
+
+ gzip_comp_level 6;
+ gzip_min_length 1100;
+ gzip_buffers 16 8k;
+ gzip_proxied any;
+ gzip_types text/plain application/xml text/css text/js text/xml application/x-javascript text/javascript application/json application/xml+rss;
+
+ client_max_body_size 300M;
+
+ rewrite ^/oauth/authorize$ /server/php/authorize.php last;
+ rewrite ^/oauth_callback/([a-zA-Z0-9_\.]*)/([a-zA-Z0-9_\.]*)$ /server/php/oauth_callback.php?plugin=$1&code=$2 last;
+ rewrite ^/download/([0-9]*)/([a-zA-Z0-9_\.]*)$ /server/php/download.php?id=$1&hash=$2 last;
+ rewrite ^/ical/([0-9]*)/([0-9]*)/([a-z0-9]*).ics$ /server/php/ical.php?board_id=$1&user_id=$2&hash=$3 last;
+ rewrite ^/api/(.*)$ /server/php/R/r.php?_url=$1&$args last;
+ rewrite ^/api_explorer/api-docs/$ /client/api_explorer/api-docs/index.php last;
+ '';
+
+ locations."/".root = "${runDir}/client";
+
+ locations."~ \\.php$" = {
+ tryFiles = "$uri =404";
+ extraConfig = ''
+ include ${pkgs.nginx}/conf/fastcgi_params;
+ fastcgi_pass unix:${fpm.socket};
+ fastcgi_index index.php;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ fastcgi_param PHP_VALUE "upload_max_filesize=9G \n post_max_size=9G \n max_execution_time=200 \n max_input_time=200 \n memory_limit=256M";
+ '';
+ };
+
+ locations."~* \\.(css|js|less|html|ttf|woff|jpg|jpeg|gif|png|bmp|ico)" = {
+ root = "${runDir}/client";
+ extraConfig = ''
+ if (-f $request_filename) {
+ break;
+ }
+ rewrite ^/img/([a-zA-Z_]*)/([a-zA-Z_]*)/([a-zA-Z0-9_\.]*)$ /server/php/image.php?size=$1&model=$2&filename=$3 last;
+ add_header Cache-Control public;
+ add_header Cache-Control must-revalidate;
+ expires 7d;
+ '';
+ };
+ };
+
+ systemd.services.restya-board-init = {
+ description = "Restya board initialization";
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "postgresql.service" ];
+ after = [ "network.target" "postgresql.service" ];
+
+ script = ''
+ rm -rf "${runDir}"
+ mkdir -m 750 -p "${runDir}"
+ cp -r "${pkgs.restya-board}/"* "${runDir}"
+ sed -i "s/@restya.com/@${cfg.virtualHost.serverName}/g" "${runDir}/sql/restyaboard_with_empty_data.sql"
+ rm -rf "${runDir}/media"
+ rm -rf "${runDir}/client/img"
+ chmod -R 0750 "${runDir}"
+
+ sed -i "s@^php@${config.services.phpfpm.phpPackage}/bin/php@" "${runDir}/server/php/shell/"*.sh
+
+ ${if (cfg.database.host == null) then ''
+ sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', 'localhost');/g" "${runDir}/server/php/config.inc.php"
+ sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', 'restya');/g" "${runDir}/server/php/config.inc.php"
+ '' else ''
+ sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', '${cfg.database.host}');/g" "${runDir}/server/php/config.inc.php"
+ sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', '$(<${cfg.database.dbPassFile})');/g" "${runDir}/server/php/config.inc.php"
+ ''}
+ sed -i "s/^.*'R_DB_PORT'.*$/define('R_DB_PORT', '${toString cfg.database.port}');/g" "${runDir}/server/php/config.inc.php"
+ sed -i "s/^.*'R_DB_NAME'.*$/define('R_DB_NAME', '${cfg.database.name}');/g" "${runDir}/server/php/config.inc.php"
+ sed -i "s/^.*'R_DB_USER'.*$/define('R_DB_USER', '${cfg.database.user}');/g" "${runDir}/server/php/config.inc.php"
+
+ chmod 0400 "${runDir}/server/php/config.inc.php"
+
+ ln -sf "${cfg.dataDir}/media" "${runDir}/media"
+ ln -sf "${cfg.dataDir}/client/img" "${runDir}/client/img"
+
+ chmod g+w "${runDir}/tmp/cache"
+ chown -R "${cfg.user}"."${cfg.group}" "${runDir}"
+
+
+ mkdir -m 0750 -p "${cfg.dataDir}"
+ mkdir -m 0750 -p "${cfg.dataDir}/media"
+ mkdir -m 0750 -p "${cfg.dataDir}/client/img"
+ cp -r "${pkgs.restya-board}/media/"* "${cfg.dataDir}/media"
+ cp -r "${pkgs.restya-board}/client/img/"* "${cfg.dataDir}/client/img"
+ chown "${cfg.user}"."${cfg.group}" "${cfg.dataDir}"
+ chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/media"
+ chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/client/img"
+
+ ${optionalString (cfg.database.host == null) ''
+ if ! [ -e "${cfg.dataDir}/.db-initialized" ]; then
+ ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
+ ${config.services.postgresql.package}/bin/psql -U ${config.services.postgresql.superUser} \
+ -c "CREATE USER ${cfg.database.user} WITH ENCRYPTED PASSWORD 'restya'"
+
+ ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
+ ${config.services.postgresql.package}/bin/psql -U ${config.services.postgresql.superUser} \
+ -c "CREATE DATABASE ${cfg.database.name} OWNER ${cfg.database.user} ENCODING 'UTF8' TEMPLATE template0"
+
+ ${pkgs.sudo}/bin/sudo -u ${cfg.user} \
+ ${config.services.postgresql.package}/bin/psql -U ${cfg.database.user} \
+ -d ${cfg.database.name} -f "${runDir}/sql/restyaboard_with_empty_data.sql"
+
+ touch "${cfg.dataDir}/.db-initialized"
+ fi
+ ''}
+ '';
+ };
+
+ systemd.timers.restya-board = {
+ description = "restya-board scripts for e.g. email notification";
+ wantedBy = [ "timers.target" ];
+ after = [ "restya-board-init.service" ];
+ requires = [ "restya-board-init.service" ];
+ timerConfig = {
+ OnUnitInactiveSec = "60s";
+ Unit = "restya-board-timers.service";
+ };
+ };
+
+ systemd.services.restya-board-timers = {
+ description = "restya-board scripts for e.g. email notification";
+ serviceConfig.Type = "oneshot";
+ serviceConfig.User = cfg.user;
+
+ after = [ "restya-board-init.service" ];
+ requires = [ "restya-board-init.service" ];
+
+ script = ''
+ /bin/sh ${runDir}/server/php/shell/instant_email_notification.sh 2> /dev/null || true
+ /bin/sh ${runDir}/server/php/shell/periodic_email_notification.sh 2> /dev/null || true
+ /bin/sh ${runDir}/server/php/shell/imap.sh 2> /dev/null || true
+ /bin/sh ${runDir}/server/php/shell/webhook.sh 2> /dev/null || true
+ /bin/sh ${runDir}/server/php/shell/card_due_notification.sh 2> /dev/null || true
+ '';
+ };
+
+ users.users.restya-board = {
+ isSystemUser = true;
+ createHome = false;
+ home = runDir;
+ group = "restya-board";
+ };
+ users.groups.restya-board = {};
+
+ services.postgresql.enable = mkIf (cfg.database.host == null) true;
+
+ services.postgresql.identMap = optionalString (cfg.database.host == null)
+ ''
+ restya-board-users restya-board restya_board
+ '';
+
+ services.postgresql.authentication = optionalString (cfg.database.host == null)
+ ''
+ local restya_board all ident map=restya-board-users
+ '';
+
+ };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/web-apps/selfoss.nix b/nixpkgs/nixos/modules/services/web-apps/selfoss.nix
new file mode 100644
index 00000000000..d5a660ebf28
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/selfoss.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.selfoss;
+
+ poolName = "selfoss_pool";
+
+ dataDir = "/var/lib/selfoss";
+
+ selfoss-config =
+ let
+ db_type = cfg.database.type;
+ default_port = if (db_type == "mysql") then 3306 else 5342;
+ in
+ pkgs.writeText "selfoss-config.ini" ''
+ [globals]
+ ${lib.optionalString (db_type != "sqlite") ''
+ db_type=${db_type}
+ db_host=${cfg.database.host}
+ db_database=${cfg.database.name}
+ db_username=${cfg.database.user}
+ db_password=${cfg.database.password}
+ db_port=${toString (if (cfg.database.port != null) then cfg.database.port
+ else default_port)}
+ ''
+ }
+ ${cfg.extraConfig}
+ '';
+in
+ {
+ options = {
+ services.selfoss = {
+ enable = mkEnableOption "selfoss";
+
+ user = mkOption {
+ type = types.str;
+ default = "nginx";
+ example = "nginx";
+ description = ''
+ User account under which both the service and the web-application run.
+ '';
+ };
+
+ pool = mkOption {
+ type = types.str;
+ default = "${poolName}";
+ description = ''
+ Name of existing phpfpm pool that is used to run web-application.
+ If not specified a pool will be created automatically with
+ default values.
+ '';
+ };
+
+ database = {
+ type = mkOption {
+ type = types.enum ["pgsql" "mysql" "sqlite"];
+ default = "sqlite";
+ description = ''
+ Database to store feeds. Supported are sqlite, pgsql and mysql.
+ '';
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = ''
+ Host of the database (has no effect if type is "sqlite").
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "tt_rss";
+ description = ''
+ Name of the existing database (has no effect if type is "sqlite").
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "tt_rss";
+ description = ''
+ The database user. The user must exist and has access to
+ the specified database (has no effect if type is "sqlite").
+ '';
+ };
+
+ password = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The database user's password (has no effect if type is "sqlite").
+ '';
+ };
+
+ port = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ The database's port. If not set, the default ports will be
+ provided (5432 and 3306 for pgsql and mysql respectively)
+ (has no effect if type is "sqlite").
+ '';
+ };
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration added to config.ini
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
+ ${poolName} = {
+ user = "nginx";
+ settings = mapAttrs (name: mkDefault) {
+ "listen.owner" = "nginx";
+ "listen.group" = "nginx";
+ "listen.mode" = "0600";
+ "pm" = "dynamic";
+ "pm.max_children" = 75;
+ "pm.start_servers" = 10;
+ "pm.min_spare_servers" = 5;
+ "pm.max_spare_servers" = 20;
+ "pm.max_requests" = 500;
+ "catch_workers_output" = 1;
+ };
+ };
+ };
+
+ systemd.services.selfoss-config = {
+ serviceConfig.Type = "oneshot";
+ script = ''
+ mkdir -m 755 -p ${dataDir}
+ cd ${dataDir}
+
+ # Delete all but the "data" folder
+ ls | grep -v data | while read line; do rm -rf $line; done || true
+
+ # Create the files
+ cp -r "${pkgs.selfoss}/"* "${dataDir}"
+ ln -sf "${selfoss-config}" "${dataDir}/config.ini"
+ chown -R "${cfg.user}" "${dataDir}"
+ chmod -R 755 "${dataDir}"
+ '';
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ systemd.services.selfoss-update = {
+ serviceConfig = {
+ ExecStart = "${pkgs.php}/bin/php ${dataDir}/cliupdate.php";
+ User = "${cfg.user}";
+ };
+ startAt = "hourly";
+ after = [ "selfoss-config.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix b/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix
new file mode 100644
index 00000000000..b92e3449894
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix
@@ -0,0 +1,676 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.tt-rss;
+
+ configVersion = 26;
+
+ cacheDir = "cache";
+ lockDir = "lock";
+ feedIconsDir = "feed-icons";
+
+ dbPort = if cfg.database.port == null
+ then (if cfg.database.type == "pgsql" then 5432 else 3306)
+ else cfg.database.port;
+
+ poolName = "tt-rss";
+
+ mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+ pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+ tt-rss-config = pkgs.writeText "config.php" ''
+ <?php
+
+ define('PHP_EXECUTABLE', '${pkgs.php}/bin/php');
+
+ define('LOCK_DIRECTORY', '${lockDir}');
+ define('CACHE_DIR', '${cacheDir}');
+ define('ICONS_DIR', '${feedIconsDir}');
+ define('ICONS_URL', '${feedIconsDir}');
+ define('SELF_URL_PATH', '${cfg.selfUrlPath}');
+
+ define('MYSQL_CHARSET', 'UTF8');
+
+ define('DB_TYPE', '${cfg.database.type}');
+ define('DB_HOST', '${optionalString (cfg.database.host != null) cfg.database.host}');
+ define('DB_USER', '${cfg.database.user}');
+ define('DB_NAME', '${cfg.database.name}');
+ define('DB_PASS', ${
+ if (cfg.database.password != null) then
+ "'${(escape ["'" "\\"] cfg.database.password)}'"
+ else if (cfg.database.passwordFile != null) then
+ "file_get_contents('${cfg.database.passwordFile}')"
+ else
+ "''"
+ });
+ define('DB_PORT', '${toString dbPort}');
+
+ define('AUTH_AUTO_CREATE', ${boolToString cfg.auth.autoCreate});
+ define('AUTH_AUTO_LOGIN', ${boolToString cfg.auth.autoLogin});
+
+ define('FEED_CRYPT_KEY', '${escape ["'" "\\"] cfg.feedCryptKey}');
+
+
+ define('SINGLE_USER_MODE', ${boolToString cfg.singleUserMode});
+
+ define('SIMPLE_UPDATE_MODE', ${boolToString cfg.simpleUpdateMode});
+
+ // Never check for updates - the running version of the code should be
+ // controlled entirely by the version of TT-RSS active in the current Nix
+ // profile. If TT-RSS updates itself to a version requiring a database
+ // schema upgrade, and then the SystemD tt-rss.service is restarted, the
+ // old code copied from the Nix store will overwrite the updated version,
+ // causing the code to detect the need for a schema "upgrade" (since the
+ // schema version in the database is different than in the code), but the
+ // update schema operation in TT-RSS will do nothing because the schema
+ // version in the database is newer than that in the code.
+ define('CHECK_FOR_UPDATES', false);
+
+ define('FORCE_ARTICLE_PURGE', ${toString cfg.forceArticlePurge});
+ define('SESSION_COOKIE_LIFETIME', ${toString cfg.sessionCookieLifetime});
+ define('ENABLE_GZIP_OUTPUT', ${boolToString cfg.enableGZipOutput});
+
+ define('PLUGINS', '${builtins.concatStringsSep "," cfg.plugins}');
+
+ define('LOG_DESTINATION', '${cfg.logDestination}');
+ define('CONFIG_VERSION', ${toString configVersion});
+
+
+ define('PUBSUBHUBBUB_ENABLED', ${boolToString cfg.pubSubHubbub.enable});
+ define('PUBSUBHUBBUB_HUB', '${cfg.pubSubHubbub.hub}');
+
+ define('SPHINX_SERVER', '${cfg.sphinx.server}');
+ define('SPHINX_INDEX', '${builtins.concatStringsSep "," cfg.sphinx.index}');
+
+ define('ENABLE_REGISTRATION', ${boolToString cfg.registration.enable});
+ define('REG_NOTIFY_ADDRESS', '${cfg.registration.notifyAddress}');
+ define('REG_MAX_USERS', ${toString cfg.registration.maxUsers});
+
+ define('SMTP_SERVER', '${cfg.email.server}');
+ define('SMTP_LOGIN', '${cfg.email.login}');
+ define('SMTP_PASSWORD', '${escape ["'" "\\"] cfg.email.password}');
+ define('SMTP_SECURE', '${cfg.email.security}');
+
+ define('SMTP_FROM_NAME', '${escape ["'" "\\"] cfg.email.fromName}');
+ define('SMTP_FROM_ADDRESS', '${escape ["'" "\\"] cfg.email.fromAddress}');
+ define('DIGEST_SUBJECT', '${escape ["'" "\\"] cfg.email.digestSubject}');
+
+ ${cfg.extraConfig}
+ '';
+
+ in {
+
+ ###### interface
+
+ options = {
+
+ services.tt-rss = {
+
+ enable = mkEnableOption "tt-rss";
+
+ root = mkOption {
+ type = types.path;
+ default = "/var/lib/tt-rss";
+ example = "/var/lib/tt-rss";
+ description = ''
+ Root of the application.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "tt_rss";
+ example = "tt_rss";
+ description = ''
+ User account under which both the update daemon and the web-application run.
+ '';
+ };
+
+ pool = mkOption {
+ type = types.str;
+ default = "${poolName}";
+ description = ''
+ Name of existing phpfpm pool that is used to run web-application.
+ If not specified a pool will be created automatically with
+ default values.
+ '';
+ };
+
+ virtualHost = mkOption {
+ type = types.nullOr types.str;
+ default = "tt-rss";
+ description = ''
+ Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
+ '';
+ };
+
+ database = {
+ type = mkOption {
+ type = types.enum ["pgsql" "mysql"];
+ default = "pgsql";
+ description = ''
+ Database to store feeds. Supported are pgsql and mysql.
+ '';
+ };
+
+ host = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Host of the database. Leave null to use Unix domain socket.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "tt_rss";
+ description = ''
+ Name of the existing database.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "tt_rss";
+ description = ''
+ The database user. The user must exist and has access to
+ the specified database.
+ '';
+ };
+
+ password = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The database user's password.
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The database user's password.
+ '';
+ };
+
+ port = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ The database's port. If not set, the default ports will be provided (5432
+ and 3306 for pgsql and mysql respectively).
+ '';
+ };
+
+ createLocally = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Create the database and database user locally.";
+ };
+ };
+
+ auth = {
+ autoCreate = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Allow authentication modules to auto-create users in tt-rss internal
+ database when authenticated successfully.
+ '';
+ };
+
+ autoLogin = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Automatically login user on remote or other kind of externally supplied
+ authentication, otherwise redirect to login form as normal.
+ If set to true, users won't be able to set application language
+ and settings profile.
+ '';
+ };
+ };
+
+ pubSubHubbub = {
+ hub = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ URL to a PubSubHubbub-compatible hub server. If defined, "Published
+ articles" generated feed would automatically become PUSH-enabled.
+ '';
+ };
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable client PubSubHubbub support in tt-rss. When disabled, tt-rss
+ won't try to subscribe to PUSH feed updates.
+ '';
+ };
+ };
+
+ sphinx = {
+ server = mkOption {
+ type = types.str;
+ default = "localhost:9312";
+ description = ''
+ Hostname:port combination for the Sphinx server.
+ '';
+ };
+
+ index = mkOption {
+ type = types.listOf types.str;
+ default = ["ttrss" "delta"];
+ description = ''
+ Index names in Sphinx configuration. Example configuration
+ files are available on tt-rss wiki.
+ '';
+ };
+ };
+
+ registration = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow users to register themselves. Please be aware that allowing
+ random people to access your tt-rss installation is a security risk
+ and potentially might lead to data loss or server exploit. Disabled
+ by default.
+ '';
+ };
+
+ notifyAddress = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Email address to send new user notifications to.
+ '';
+ };
+
+ maxUsers = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Maximum amount of users which will be allowed to register on this
+ system. 0 - no limit.
+ '';
+ };
+ };
+
+ email = {
+ server = mkOption {
+ type = types.str;
+ default = "";
+ example = "localhost:25";
+ description = ''
+ Hostname:port combination to send outgoing mail. Blank - use system
+ MTA.
+ '';
+ };
+
+ login = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ SMTP authentication login used when sending outgoing mail.
+ '';
+ };
+
+ password = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ SMTP authentication password used when sending outgoing mail.
+ '';
+ };
+
+ security = mkOption {
+ type = types.enum ["" "ssl" "tls"];
+ default = "";
+ description = ''
+ Used to select a secure SMTP connection. Allowed values: ssl, tls,
+ or empty.
+ '';
+ };
+
+ fromName = mkOption {
+ type = types.str;
+ default = "Tiny Tiny RSS";
+ description = ''
+ Name for sending outgoing mail. This applies to password reset
+ notifications, digest emails and any other mail.
+ '';
+ };
+
+ fromAddress = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Address for sending outgoing mail. This applies to password reset
+ notifications, digest emails and any other mail.
+ '';
+ };
+
+ digestSubject = mkOption {
+ type = types.str;
+ default = "[tt-rss] New headlines for last 24 hours";
+ description = ''
+ Subject line for email digests.
+ '';
+ };
+ };
+
+ sessionCookieLifetime = mkOption {
+ type = types.int;
+ default = 86400;
+ description = ''
+ Default lifetime of a session (e.g. login) cookie. In seconds,
+ 0 means cookie will be deleted when browser closes.
+ '';
+ };
+
+ selfUrlPath = mkOption {
+ type = types.str;
+ description = ''
+ Full URL of your tt-rss installation. This should be set to the
+ location of tt-rss directory, e.g. http://example.org/tt-rss/
+ You need to set this option correctly otherwise several features
+ including PUSH, bookmarklets and browser integration will not work properly.
+ '';
+ example = "http://localhost";
+ };
+
+ feedCryptKey = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Key used for encryption of passwords for password-protected feeds
+ in the database. A string of 24 random characters. If left blank, encryption
+ is not used. Requires mcrypt functions.
+ Warning: changing this key will make your stored feed passwords impossible
+ to decrypt.
+ '';
+ };
+
+ singleUserMode = mkOption {
+ type = types.bool;
+ default = false;
+
+ description = ''
+ Operate in single user mode, disables all functionality related to
+ multiple users and authentication. Enabling this assumes you have
+ your tt-rss directory protected by other means (e.g. http auth).
+ '';
+ };
+
+ simpleUpdateMode = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enables fallback update mode where tt-rss tries to update feeds in
+ background while tt-rss is open in your browser.
+ If you don't have a lot of feeds and don't want to or can't run
+ background processes while not running tt-rss, this method is generally
+ viable to keep your feeds up to date.
+ Still, there are more robust (and recommended) updating methods
+ available, you can read about them here: http://tt-rss.org/wiki/UpdatingFeeds
+ '';
+ };
+
+ forceArticlePurge = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ When this option is not 0, users ability to control feed purging
+ intervals is disabled and all articles (which are not starred)
+ older than this amount of days are purged.
+ '';
+ };
+
+ enableGZipOutput = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Selectively gzip output to improve wire performance. This requires
+ PHP Zlib extension on the server.
+ Enabling this can break tt-rss in several httpd/php configurations,
+ if you experience weird errors and tt-rss failing to start, blank pages
+ after login, or content encoding errors, disable it.
+ '';
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.str;
+ default = ["auth_internal" "note"];
+ description = ''
+ List of plugins to load automatically for all users.
+ System plugins have to be specified here. Please enable at least one
+ authentication plugin here (auth_*).
+ Users may enable other user plugins from Preferences/Plugins but may not
+ disable plugins specified in this list.
+ Disabling auth_internal in this list would automatically disable
+ reset password link on the login form.
+ '';
+ };
+
+ pluginPackages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = ''
+ List of plugins to install. The list elements are expected to
+ be derivations. All elements in this derivation are automatically
+ copied to the <literal>plugins.local</literal> directory.
+ '';
+ };
+
+ themePackages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = ''
+ List of themes to install. The list elements are expected to
+ be derivations. All elements in this derivation are automatically
+ copied to the <literal>themes.local</literal> directory.
+ '';
+ };
+
+ logDestination = mkOption {
+ type = types.enum ["" "sql" "syslog"];
+ default = "sql";
+ description = ''
+ Log destination to use. Possible values: sql (uses internal logging
+ you can read in Preferences -> System), syslog - logs to system log.
+ Setting this to blank uses PHP logging (usually to http server
+ error.log).
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional lines to append to <literal>config.php</literal>.
+ '';
+ };
+ };
+ };
+
+ imports = [
+ (mkRemovedOptionModule ["services" "tt-rss" "checkForUpdates"] ''
+ This option was removed because setting this to true will cause TT-RSS
+ to be unable to start if an automatic update of the code in
+ services.tt-rss.root leads to a database schema upgrade that is not
+ supported by the code active in the Nix store.
+ '')
+ ];
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ {
+ assertion = cfg.database.password != null -> cfg.database.passwordFile == null;
+ message = "Cannot set both password and passwordFile";
+ }
+ ];
+
+ services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
+ ${poolName} = {
+ inherit (cfg) user;
+ settings = mapAttrs (name: mkDefault) {
+ "listen.owner" = "nginx";
+ "listen.group" = "nginx";
+ "listen.mode" = "0600";
+ "pm" = "dynamic";
+ "pm.max_children" = 75;
+ "pm.start_servers" = 10;
+ "pm.min_spare_servers" = 5;
+ "pm.max_spare_servers" = 20;
+ "pm.max_requests" = 500;
+ "catch_workers_output" = 1;
+ };
+ };
+ };
+
+ # NOTE: No configuration is done if not using virtual host
+ services.nginx = mkIf (cfg.virtualHost != null) {
+ enable = true;
+ virtualHosts = {
+ ${cfg.virtualHost} = {
+ root = "${cfg.root}";
+
+ locations."/" = {
+ index = "index.php";
+ };
+
+ locations."~ \\.php$" = {
+ extraConfig = ''
+ fastcgi_split_path_info ^(.+\.php)(/.+)$;
+ fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket};
+ fastcgi_index index.php;
+ '';
+ };
+ };
+ };
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.root}' 0755 ${cfg.user} tt_rss - -"
+ "Z '${cfg.root}' 0755 ${cfg.user} tt_rss - -"
+ ];
+
+ systemd.services.tt-rss =
+ {
+
+ description = "Tiny Tiny RSS feeds update daemon";
+
+ preStart = let
+ callSql = e:
+ if cfg.database.type == "pgsql" then ''
+ ${optionalString (cfg.database.password != null) "PGPASSWORD=${cfg.database.password}"} \
+ ${optionalString (cfg.database.passwordFile != null) "PGPASSWORD=$(cat ${cfg.database.passwordFile})"} \
+ ${config.services.postgresql.package}/bin/psql \
+ -U ${cfg.database.user} \
+ ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} --port ${toString dbPort}"} \
+ -c '${e}' \
+ ${cfg.database.name}''
+
+ else if cfg.database.type == "mysql" then ''
+ echo '${e}' | ${config.services.mysql.package}/bin/mysql \
+ -u ${cfg.database.user} \
+ ${optionalString (cfg.database.password != null) "-p${cfg.database.password}"} \
+ ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} -P ${toString dbPort}"} \
+ ${cfg.database.name}''
+
+ else "";
+
+ in ''
+ rm -rf "${cfg.root}/*"
+ cp -r "${pkgs.tt-rss}/"* "${cfg.root}"
+ ${optionalString (cfg.pluginPackages != []) ''
+ for plugin in ${concatStringsSep " " cfg.pluginPackages}; do
+ cp -r "$plugin"/* "${cfg.root}/plugins.local/"
+ done
+ ''}
+ ${optionalString (cfg.themePackages != []) ''
+ for theme in ${concatStringsSep " " cfg.themePackages}; do
+ cp -r "$theme"/* "${cfg.root}/themes.local/"
+ done
+ ''}
+ ln -sf "${tt-rss-config}" "${cfg.root}/config.php"
+ chmod -R 755 "${cfg.root}"
+ ''
+
+ + (optionalString (cfg.database.type == "pgsql") ''
+ exists=$(${callSql "select count(*) > 0 from pg_tables where tableowner = user"} \
+ | tail -n+3 | head -n-2 | sed -e 's/[ \n\t]*//')
+
+ if [ "$exists" == 'f' ]; then
+ ${callSql "\\i ${pkgs.tt-rss}/schema/ttrss_schema_${cfg.database.type}.sql"}
+ else
+ echo 'The database contains some data. Leaving it as it is.'
+ fi;
+ '')
+
+ + (optionalString (cfg.database.type == "mysql") ''
+ exists=$(${callSql "select count(*) > 0 from information_schema.tables where table_schema = schema()"} \
+ | tail -n+2 | sed -e 's/[ \n\t]*//')
+
+ if [ "$exists" == '0' ]; then
+ ${callSql "\\. ${pkgs.tt-rss}/schema/ttrss_schema_${cfg.database.type}.sql"}
+ else
+ echo 'The database contains some data. Leaving it as it is.'
+ fi;
+ '');
+
+ serviceConfig = {
+ User = "${cfg.user}";
+ Group = "tt_rss";
+ ExecStart = "${pkgs.php}/bin/php ${cfg.root}/update.php --daemon";
+ StandardOutput = "syslog";
+ StandardError = "syslog";
+ };
+
+ wantedBy = [ "multi-user.target" ];
+ requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+ after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+ };
+
+ services.mysql = mkIf mysqlLocal {
+ enable = true;
+ package = mkDefault pkgs.mysql;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ {
+ name = cfg.user;
+ ensurePermissions = {
+ "${cfg.database.name}.*" = "ALL PRIVILEGES";
+ };
+ }
+ ];
+ };
+
+ services.postgresql = mkIf pgsqlLocal {
+ enable = mkDefault true;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.user;
+ ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ users.users.tt_rss = optionalAttrs (cfg.user == "tt_rss") {
+ description = "tt-rss service user";
+ isSystemUser = true;
+ group = "tt_rss";
+ };
+
+ users.groups.tt_rss = {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/virtlyst.nix b/nixpkgs/nixos/modules/services/web-apps/virtlyst.nix
new file mode 100644
index 00000000000..e5c0bff2168
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/virtlyst.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.virtlyst;
+ stateDir = "/var/lib/virtlyst";
+
+ ini = pkgs.writeText "virtlyst-config.ini" ''
+ [wsgi]
+ master = true
+ threads = auto
+ http-socket = ${cfg.httpSocket}
+ application = ${pkgs.virtlyst}/lib/libVirtlyst.so
+ chdir2 = ${stateDir}
+ static-map = /static=${pkgs.virtlyst}/root/static
+
+ [Cutelyst]
+ production = true
+ DatabasePath = virtlyst.sqlite
+ TemplatePath = ${pkgs.virtlyst}/root/src
+
+ [Rules]
+ cutelyst.* = true
+ virtlyst.* = true
+ '';
+
+in
+
+{
+
+ options.services.virtlyst = {
+ enable = mkEnableOption "Virtlyst libvirt web interface";
+
+ adminPassword = mkOption {
+ type = types.str;
+ description = ''
+ Initial admin password with which the database will be seeded.
+ '';
+ };
+
+ httpSocket = mkOption {
+ type = types.str;
+ default = "localhost:3000";
+ description = ''
+ IP and/or port to which to bind the http socket.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.users.virtlyst = {
+ home = stateDir;
+ createHome = true;
+ group = mkIf config.virtualisation.libvirtd.enable "libvirtd";
+ };
+
+ systemd.services.virtlyst = {
+ wantedBy = [ "multi-user.target" ];
+ environment = {
+ VIRTLYST_ADMIN_PASSWORD = cfg.adminPassword;
+ };
+ serviceConfig = {
+ ExecStart = "${pkgs.cutelyst}/bin/cutelyst-wsgi2 --ini ${ini}";
+ User = "virtlyst";
+ WorkingDirectory = stateDir;
+ };
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/wordpress.nix b/nixpkgs/nixos/modules/services/web-apps/wordpress.nix
new file mode 100644
index 00000000000..e311dd917dd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/wordpress.nix
@@ -0,0 +1,373 @@
+{ config, pkgs, lib, ... }:
+
+let
+ inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+ inherit (lib) any attrValues concatMapStringsSep flatten literalExample;
+ inherit (lib) mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString;
+
+ eachSite = config.services.wordpress;
+ user = "wordpress";
+ group = config.services.httpd.group;
+ stateDir = hostName: "/var/lib/wordpress/${hostName}";
+
+ pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
+ pname = "wordpress-${hostName}";
+ version = src.version;
+ src = cfg.package;
+
+ installPhase = ''
+ mkdir -p $out
+ cp -r * $out/
+
+ # symlink the wordpress config
+ ln -s ${wpConfig hostName cfg} $out/share/wordpress/wp-config.php
+ # symlink uploads directory
+ ln -s ${cfg.uploadsDir} $out/share/wordpress/wp-content/uploads
+
+ # https://github.com/NixOS/nixpkgs/pull/53399
+ #
+ # Symlinking works for most plugins and themes, but Avada, for instance, fails to
+ # understand the symlink, causing its file path stripping to fail. This results in
+ # requests that look like: https://example.com/wp-content//nix/store/...plugin/path/some-file.js
+ # Since hard linking directories is not allowed, copying is the next best thing.
+
+ # copy additional plugin(s) and theme(s)
+ ${concatMapStringsSep "\n" (theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${theme.name}") cfg.themes}
+ ${concatMapStringsSep "\n" (plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${plugin.name}") cfg.plugins}
+ '';
+ };
+
+ wpConfig = hostName: cfg: pkgs.writeText "wp-config-${hostName}.php" ''
+ <?php
+ define('DB_NAME', '${cfg.database.name}');
+ define('DB_HOST', '${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}');
+ define('DB_USER', '${cfg.database.user}');
+ ${optionalString (cfg.database.passwordFile != null) "define('DB_PASSWORD', file_get_contents('${cfg.database.passwordFile}'));"}
+ define('DB_CHARSET', 'utf8');
+ $table_prefix = '${cfg.database.tablePrefix}';
+
+ require_once('${stateDir hostName}/secret-keys.php');
+
+ # wordpress is installed onto a read-only file system
+ define('DISALLOW_FILE_EDIT', true);
+ define('AUTOMATIC_UPDATER_DISABLED', true);
+
+ ${cfg.extraConfig}
+
+ if ( !defined('ABSPATH') )
+ define('ABSPATH', dirname(__FILE__) . '/');
+
+ require_once(ABSPATH . 'wp-settings.php');
+ ?>
+ '';
+
+ secretsVars = [ "AUTH_KEY" "SECURE_AUTH_KEY" "LOOGGED_IN_KEY" "NONCE_KEY" "AUTH_SALT" "SECURE_AUTH_SALT" "LOGGED_IN_SALT" "NONCE_SALT" ];
+ secretsScript = hostStateDir: ''
+ if ! test -e "${hostStateDir}/secret-keys.php"; then
+ umask 0177
+ echo "<?php" >> "${hostStateDir}/secret-keys.php"
+ ${concatMapStringsSep "\n" (var: ''
+ echo "define('${var}', '`tr -dc a-zA-Z0-9 </dev/urandom | head -c 64`');" >> "${hostStateDir}/secret-keys.php"
+ '') secretsVars}
+ echo "?>" >> "${hostStateDir}/secret-keys.php"
+ chmod 440 "${hostStateDir}/secret-keys.php"
+ fi
+ '';
+
+ siteOpts = { lib, name, ... }:
+ {
+ options = {
+ package = mkOption {
+ type = types.package;
+ default = pkgs.wordpress;
+ description = "Which WordPress package to use.";
+ };
+
+ uploadsDir = mkOption {
+ type = types.path;
+ default = "/var/lib/wordpress/${name}/uploads";
+ description = ''
+ This directory is used for uploads of pictures. The directory passed here is automatically
+ created and permissions adjusted as required.
+ '';
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ List of path(s) to respective plugin(s) which are copied from the 'plugins' directory.
+ <note><para>These plugins need to be packaged before use, see example.</para></note>
+ '';
+ example = ''
+ # Wordpress plugin 'embed-pdf-viewer' installation example
+ embedPdfViewerPlugin = pkgs.stdenv.mkDerivation {
+ name = "embed-pdf-viewer-plugin";
+ # Download the theme from the wordpress site
+ src = pkgs.fetchurl {
+ url = https://downloads.wordpress.org/plugin/embed-pdf-viewer.2.0.3.zip;
+ sha256 = "1rhba5h5fjlhy8p05zf0p14c9iagfh96y91r36ni0rmk6y891lyd";
+ };
+ # We need unzip to build this package
+ buildInputs = [ pkgs.unzip ];
+ # Installing simply means copying all files to the output directory
+ installPhase = "mkdir -p $out; cp -R * $out/";
+ };
+
+ And then pass this theme to the themes list like this:
+ plugins = [ embedPdfViewerPlugin ];
+ '';
+ };
+
+ themes = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = ''
+ List of path(s) to respective theme(s) which are copied from the 'theme' directory.
+ <note><para>These themes need to be packaged before use, see example.</para></note>
+ '';
+ example = ''
+ # For shits and giggles, let's package the responsive theme
+ responsiveTheme = pkgs.stdenv.mkDerivation {
+ name = "responsive-theme";
+ # Download the theme from the wordpress site
+ src = pkgs.fetchurl {
+ url = https://downloads.wordpress.org/theme/responsive.3.14.zip;
+ sha256 = "0rjwm811f4aa4q43r77zxlpklyb85q08f9c8ns2akcarrvj5ydx3";
+ };
+ # We need unzip to build this package
+ buildInputs = [ pkgs.unzip ];
+ # Installing simply means copying all files to the output directory
+ installPhase = "mkdir -p $out; cp -R * $out/";
+ };
+
+ And then pass this theme to the themes list like this:
+ themes = [ responsiveTheme ];
+ '';
+ };
+
+ database = {
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 3306;
+ description = "Database host port.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "wordpress";
+ description = "Database name.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "wordpress";
+ description = "Database user.";
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/wordpress-dbpassword";
+ description = ''
+ A file containing the password corresponding to
+ <option>database.user</option>.
+ '';
+ };
+
+ tablePrefix = mkOption {
+ type = types.str;
+ default = "wp_";
+ description = ''
+ The $table_prefix is the value placed in the front of your database tables.
+ Change the value if you want to use something other than wp_ for your database
+ prefix. Typically this is changed if you are installing multiple WordPress blogs
+ in the same database.
+
+ See <link xlink:href='https://codex.wordpress.org/Editing_wp-config.php#table_prefix'/>.
+ '';
+ };
+
+ socket = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ defaultText = "/run/mysqld/mysqld.sock";
+ description = "Path to the unix socket file to use for authentication.";
+ };
+
+ createLocally = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Create the database and database user locally.";
+ };
+ };
+
+ virtualHost = mkOption {
+ type = types.submodule ({
+ options = import ../web-servers/apache-httpd/per-server-options.nix {
+ inherit lib;
+ forMainServer = false;
+ };
+ });
+ example = literalExample ''
+ {
+ enableSSL = true;
+ adminAddr = "webmaster@example.org";
+ sslServerCert = "/var/lib/acme/wordpress.example.org/full.pem";
+ sslServerKey = "/var/lib/acme/wordpress.example.org/key.pem";
+ }
+ '';
+ description = ''
+ Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+ '';
+ };
+
+ poolConfig = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool ]);
+ default = {
+ "pm" = "dynamic";
+ "pm.max_children" = 32;
+ "pm.start_servers" = 2;
+ "pm.min_spare_servers" = 2;
+ "pm.max_spare_servers" = 4;
+ "pm.max_requests" = 500;
+ };
+ description = ''
+ Options for the WordPress PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+ for details on configuration directives.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Any additional text to be appended to the wp-config.php
+ configuration file. This is a PHP script. For configuration
+ settings, see <link xlink:href='https://codex.wordpress.org/Editing_wp-config.php'/>.
+ '';
+ example = ''
+ define( 'AUTOSAVE_INTERVAL', 60 ); // Seconds
+ '';
+ };
+ };
+
+ config.virtualHost.hostName = mkDefault name;
+ };
+in
+{
+ # interface
+ options = {
+ services.wordpress = mkOption {
+ type = types.attrsOf (types.submodule siteOpts);
+ default = {};
+ description = "Specification of one or more WordPress sites to serve via Apache.";
+ };
+ };
+
+ # implementation
+ config = mkIf (eachSite != {}) {
+
+ assertions = mapAttrsToList (hostName: cfg:
+ { assertion = cfg.database.createLocally -> cfg.database.user == user;
+ message = "services.wordpress.${hostName}.database.user must be ${user} if the database is to be automatically provisioned";
+ }
+ ) eachSite;
+
+ services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) {
+ enable = true;
+ package = mkDefault pkgs.mariadb;
+ ensureDatabases = mapAttrsToList (hostName: cfg: cfg.database.name) eachSite;
+ ensureUsers = mapAttrsToList (hostName: cfg:
+ { name = cfg.database.user;
+ ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+ }
+ ) eachSite;
+ };
+
+ services.phpfpm.pools = mapAttrs' (hostName: cfg: (
+ nameValuePair "wordpress-${hostName}" {
+ inherit user group;
+ settings = {
+ "listen.owner" = config.services.httpd.user;
+ "listen.group" = config.services.httpd.group;
+ } // cfg.poolConfig;
+ }
+ )) eachSite;
+
+ services.httpd = {
+ enable = true;
+ extraModules = [ "proxy_fcgi" ];
+ virtualHosts = mapAttrsToList (hostName: cfg:
+ (mkMerge [
+ cfg.virtualHost {
+ documentRoot = mkForce "${pkg hostName cfg}/share/wordpress";
+ extraConfig = ''
+ <Directory "${pkg hostName cfg}/share/wordpress">
+ <FilesMatch "\.php$">
+ <If "-f %{REQUEST_FILENAME}">
+ SetHandler "proxy:unix:${config.services.phpfpm.pools."wordpress-${hostName}".socket}|fcgi://localhost/"
+ </If>
+ </FilesMatch>
+
+ # standard wordpress .htaccess contents
+ <IfModule mod_rewrite.c>
+ RewriteEngine On
+ RewriteBase /
+ RewriteRule ^index\.php$ - [L]
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule . /index.php [L]
+ </IfModule>
+
+ DirectoryIndex index.php
+ Require all granted
+ Options +FollowSymLinks
+ </Directory>
+
+ # https://wordpress.org/support/article/hardening-wordpress/#securing-wp-config-php
+ <Files wp-config.php>
+ Require all denied
+ </Files>
+ '';
+ }
+ ])
+ ) eachSite;
+ };
+
+ systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
+ "d '${stateDir hostName}' 0750 ${user} ${group} - -"
+ "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+ "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+ ]) eachSite);
+
+ systemd.services = mkMerge [
+ (mapAttrs' (hostName: cfg: (
+ nameValuePair "wordpress-init-${hostName}" {
+ wantedBy = [ "multi-user.target" ];
+ before = [ "phpfpm-wordpress-${hostName}.service" ];
+ after = optional cfg.database.createLocally "mysql.service";
+ script = secretsScript (stateDir hostName);
+
+ serviceConfig = {
+ Type = "oneshot";
+ User = user;
+ Group = group;
+ };
+ })) eachSite)
+
+ (optionalAttrs (any (v: v.database.createLocally) (attrValues eachSite)) {
+ httpd.after = [ "mysql.service" ];
+ })
+ ];
+
+ users.users.${user}.group = group;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/youtrack.nix b/nixpkgs/nixos/modules/services/web-apps/youtrack.nix
new file mode 100644
index 00000000000..830edac20ba
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/youtrack.nix
@@ -0,0 +1,178 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.youtrack;
+
+ extraAttr = concatStringsSep " " (mapAttrsToList (k: v: "-D${k}=${v}") (stdParams // cfg.extraParams));
+ mergeAttrList = lib.foldl' lib.mergeAttrs {};
+
+ stdParams = mergeAttrList [
+ (optionalAttrs (cfg.baseUrl != null) {
+ "jetbrains.youtrack.baseUrl" = cfg.baseUrl;
+ })
+ {
+ "java.aws.headless" = "true";
+ "jetbrains.youtrack.disableBrowser" = "true";
+ }
+ ];
+in
+{
+ options.services.youtrack = {
+
+ enable = mkEnableOption "YouTrack service";
+
+ address = mkOption {
+ description = ''
+ The interface youtrack will listen on.
+ '';
+ default = "127.0.0.1";
+ type = types.str;
+ };
+
+ baseUrl = mkOption {
+ description = ''
+ Base URL for youtrack. Will be auto-detected and stored in database.
+ '';
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ extraParams = mkOption {
+ default = {};
+ description = ''
+ Extra parameters to pass to youtrack. See
+ https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Java-Start-Parameters.html
+ for more information.
+ '';
+ example = {
+ "jetbrains.youtrack.overrideRootPassword" = "tortuga";
+ };
+ type = types.attrsOf types.str;
+ };
+
+ package = mkOption {
+ description = ''
+ Package to use.
+ '';
+ type = types.package;
+ default = pkgs.youtrack;
+ defaultText = "pkgs.youtrack";
+ };
+
+ port = mkOption {
+ description = ''
+ The port youtrack will listen on.
+ '';
+ default = 8080;
+ type = types.int;
+ };
+
+ statePath = mkOption {
+ description = ''
+ Where to keep the youtrack database.
+ '';
+ type = types.path;
+ default = "/var/lib/youtrack";
+ };
+
+ virtualHost = mkOption {
+ description = ''
+ Name of the nginx virtual host to use and setup.
+ If null, do not setup anything.
+ '';
+ default = null;
+ type = types.nullOr types.str;
+ };
+
+ jvmOpts = mkOption {
+ description = ''
+ Extra options to pass to the JVM.
+ See https://www.jetbrains.com/help/youtrack/standalone/Configure-JVM-Options.html
+ for more information.
+ '';
+ type = types.separatedString " ";
+ example = "-XX:MetaspaceSize=250m";
+ default = "";
+ };
+
+ maxMemory = mkOption {
+ description = ''
+ Maximum Java heap size
+ '';
+ type = types.str;
+ default = "1g";
+ };
+
+ maxMetaspaceSize = mkOption {
+ description = ''
+ Maximum java Metaspace memory.
+ '';
+ type = types.str;
+ default = "350m";
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.youtrack = {
+ environment.HOME = cfg.statePath;
+ environment.YOUTRACK_JVM_OPTS = "${extraAttr}";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [ unixtools.hostname ];
+ serviceConfig = {
+ Type = "simple";
+ User = "youtrack";
+ Group = "youtrack";
+ ExecStart = ''${cfg.package}/bin/youtrack --J-Xmx${cfg.maxMemory} --J-XX:MaxMetaspaceSize=${cfg.maxMetaspaceSize} ${cfg.jvmOpts} ${cfg.address}:${toString cfg.port}'';
+ };
+ };
+
+ users.users.youtrack = {
+ description = "Youtrack service user";
+ isSystemUser = true;
+ home = cfg.statePath;
+ createHome = true;
+ group = "youtrack";
+ };
+
+ users.groups.youtrack = {};
+
+ services.nginx = mkIf (cfg.virtualHost != null) {
+ upstreams.youtrack.servers."${cfg.address}:${toString cfg.port}" = {};
+ virtualHosts.${cfg.virtualHost}.locations = {
+ "/" = {
+ proxyPass = "http://youtrack";
+ extraConfig = ''
+ client_max_body_size 10m;
+ proxy_http_version 1.1;
+ proxy_set_header X-Forwarded-Host $http_host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ '';
+ };
+
+ "/api/eventSourceBus" = {
+ proxyPass = "http://youtrack";
+ extraConfig = ''
+ proxy_cache off;
+ proxy_buffering off;
+ proxy_read_timeout 86400s;
+ proxy_send_timeout 86400s;
+ proxy_set_header Connection "";
+ chunked_transfer_encoding off;
+ client_max_body_size 10m;
+ proxy_http_version 1.1;
+ proxy_set_header X-Forwarded-Host $http_host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ '';
+ };
+
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/zabbix.nix b/nixpkgs/nixos/modules/services/web-apps/zabbix.nix
new file mode 100644
index 00000000000..09538726b7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/zabbix.nix
@@ -0,0 +1,223 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+ inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+ inherit (lib) literalExample mapAttrs optionalString;
+
+ cfg = config.services.zabbixWeb;
+ fpm = config.services.phpfpm.pools.zabbix;
+
+ user = "zabbix";
+ group = "zabbix";
+ stateDir = "/var/lib/zabbix";
+
+ zabbixConfig = pkgs.writeText "zabbix.conf.php" ''
+ <?php
+ // Zabbix GUI configuration file.
+ global $DB;
+ $DB['TYPE'] = '${ { mysql = "MYSQL"; pgsql = "POSTGRESQL"; oracle = "ORACLE"; }.${cfg.database.type} }';
+ $DB['SERVER'] = '${cfg.database.host}';
+ $DB['PORT'] = '${toString cfg.database.port}';
+ $DB['DATABASE'] = '${cfg.database.name}';
+ $DB['USER'] = '${cfg.database.user}';
+ $DB['PASSWORD'] = ${if cfg.database.passwordFile != null then "file_get_contents('${cfg.database.passwordFile}')" else "''"};
+ // Schema name. Used for IBM DB2 and PostgreSQL.
+ $DB['SCHEMA'] = ''';
+ $ZBX_SERVER = '${cfg.server.address}';
+ $ZBX_SERVER_PORT = '${toString cfg.server.port}';
+ $ZBX_SERVER_NAME = ''';
+ $IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
+ '';
+
+in
+{
+ # interface
+
+ options.services = {
+ zabbixWeb = {
+ enable = mkEnableOption "the Zabbix web interface";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.zabbix.web;
+ defaultText = "zabbix.web";
+ description = "Which Zabbix package to use.";
+ };
+
+ server = {
+ port = mkOption {
+ type = types.int;
+ description = "The port of the Zabbix server to connect to.";
+ default = 10051;
+ };
+
+ address = mkOption {
+ type = types.str;
+ description = "The IP address or hostname of the Zabbix server to connect to.";
+ default = "localhost";
+ };
+ };
+
+ database = {
+ type = mkOption {
+ type = types.enum [ "mysql" "pgsql" "oracle" ];
+ example = "mysql";
+ default = "pgsql";
+ description = "Database engine to use.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default =
+ if cfg.database.type == "mysql" then config.services.mysql.port
+ else if cfg.database.type == "pgsql" then config.services.postgresql.port
+ else 1521;
+ description = "Database host port.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "zabbix";
+ description = "Database name.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "zabbix";
+ description = "Database user.";
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/zabbix-dbpassword";
+ description = ''
+ A file containing the password corresponding to
+ <option>database.user</option>.
+ '';
+ };
+
+ socket = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/postgresql";
+ description = "Path to the unix socket file to use for authentication.";
+ };
+ };
+
+ virtualHost = mkOption {
+ type = types.submodule ({
+ options = import ../web-servers/apache-httpd/per-server-options.nix {
+ inherit lib;
+ forMainServer = false;
+ };
+ });
+ example = {
+ hostName = "zabbix.example.org";
+ enableSSL = true;
+ adminAddr = "webmaster@example.org";
+ sslServerCert = "/var/lib/acme/zabbix.example.org/full.pem";
+ sslServerKey = "/var/lib/acme/zabbix.example.org/key.pem";
+ };
+ description = ''
+ Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
+ See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+ '';
+ };
+
+ poolConfig = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool ]);
+ default = {
+ "pm" = "dynamic";
+ "pm.max_children" = 32;
+ "pm.start_servers" = 2;
+ "pm.min_spare_servers" = 2;
+ "pm.max_spare_servers" = 4;
+ "pm.max_requests" = 500;
+ };
+ description = ''
+ Options for the Zabbix PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+ '';
+ };
+
+ };
+ };
+
+ # implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.tmpfiles.rules = [
+ "d '${stateDir}' 0750 ${user} ${group} - -"
+ "d '${stateDir}/session' 0750 ${user} ${config.services.httpd.group} - -"
+ ];
+
+ services.phpfpm.pools.zabbix = {
+ inherit user;
+ group = config.services.httpd.group;
+ phpOptions = ''
+ # https://www.zabbix.com/documentation/current/manual/installation/install
+ memory_limit = 128M
+ post_max_size = 16M
+ upload_max_filesize = 2M
+ max_execution_time = 300
+ max_input_time = 300
+ session.auto_start = 0
+ mbstring.func_overload = 0
+ always_populate_raw_post_data = -1
+ # https://bbs.archlinux.org/viewtopic.php?pid=1745214#p1745214
+ session.save_path = ${stateDir}/session
+ '' + optionalString (config.time.timeZone != null) ''
+ date.timezone = "${config.time.timeZone}"
+ '' + optionalString (cfg.database.type == "oracle") ''
+ extension=${pkgs.phpPackages.oci8}/lib/php/extensions/oci8.so
+ '';
+ phpEnv.ZABBIX_CONFIG = "${zabbixConfig}";
+ settings = {
+ "listen.owner" = config.services.httpd.user;
+ "listen.group" = config.services.httpd.group;
+ } // cfg.poolConfig;
+ };
+
+ services.httpd = {
+ enable = true;
+ adminAddr = mkDefault cfg.virtualHost.adminAddr;
+ extraModules = [ "proxy_fcgi" ];
+ virtualHosts = [ (mkMerge [
+ cfg.virtualHost {
+ documentRoot = mkForce "${cfg.package}/share/zabbix";
+ extraConfig = ''
+ <Directory "${cfg.package}/share/zabbix">
+ <FilesMatch "\.php$">
+ <If "-f %{REQUEST_FILENAME}">
+ SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+ </If>
+ </FilesMatch>
+ AllowOverride all
+ Options -Indexes
+ DirectoryIndex index.php
+ </Directory>
+ '';
+ }
+ ]) ];
+ };
+
+ users.users.${user} = mapAttrs (name: mkDefault) {
+ description = "Zabbix daemon user";
+ uid = config.ids.uids.zabbix;
+ inherit group;
+ };
+
+ users.groups.${group} = mapAttrs (name: mkDefault) {
+ gid = config.ids.gids.zabbix;
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
new file mode 100644
index 00000000000..098160ee369
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -0,0 +1,719 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ mainCfg = config.services.httpd;
+
+ httpd = mainCfg.package.out;
+
+ httpdConf = mainCfg.configFile;
+
+ php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ };
+
+ phpMajorVersion = head (splitString "." php.version);
+
+ mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; };
+
+ defaultListen = cfg: if cfg.enableSSL
+ then [{ip = "*"; port = 443;}]
+ else [{ip = "*"; port = 80;}];
+
+ getListen = cfg:
+ if cfg.listen == []
+ then defaultListen cfg
+ else cfg.listen;
+
+ listenToString = l: "${l.ip}:${toString l.port}";
+
+ extraModules = attrByPath ["extraModules"] [] mainCfg;
+ extraForeignModules = filter isAttrs extraModules;
+ extraApacheModules = filter isString extraModules;
+
+
+ makeServerInfo = cfg: {
+ # Canonical name must not include a trailing slash.
+ canonicalNames =
+ let defaultPort = (head (defaultListen cfg)).port; in
+ map (port:
+ (if cfg.enableSSL then "https" else "http") + "://" +
+ cfg.hostName +
+ (if port != defaultPort then ":${toString port}" else "")
+ ) (map (x: x.port) (getListen cfg));
+
+ # Admin address: inherit from the main server if not specified for
+ # a virtual host.
+ adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr;
+
+ vhostConfig = cfg;
+ serverConfig = mainCfg;
+ fullConfig = config; # machine config
+ };
+
+
+ allHosts = [mainCfg] ++ mainCfg.virtualHosts;
+
+
+ callSubservices = serverInfo: defs:
+ let f = svc:
+ let
+ svcFunction =
+ if svc ? function then svc.function
+ # instead of using serviceType="mediawiki"; you can copy mediawiki.nix to any location outside nixpkgs, modify it at will, and use serviceExpression=./mediawiki.nix;
+ else if svc ? serviceExpression then import (toString svc.serviceExpression)
+ else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
+ config = (evalModules
+ { modules = [ { options = res.options; config = svc.config or svc; } ];
+ check = false;
+ }).config;
+ defaults = {
+ extraConfig = "";
+ extraModules = [];
+ extraModulesPre = [];
+ extraPath = [];
+ extraServerPath = [];
+ globalEnvVars = [];
+ robotsEntries = "";
+ startupScript = "";
+ enablePHP = false;
+ enablePerl = false;
+ phpOptions = "";
+ options = {};
+ documentRoot = null;
+ };
+ res = defaults // svcFunction { inherit config lib pkgs serverInfo php; };
+ in res;
+ in map f defs;
+
+
+ # !!! callSubservices is expensive
+ subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices;
+
+ mainSubservices = subservicesFor mainCfg;
+
+ allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts;
+
+
+ enableSSL = any (vhost: vhost.enableSSL) allHosts;
+
+
+ # Names of modules from ${httpd}/modules that we want to load.
+ apacheModules =
+ [ # HTTP authentication mechanisms: basic and digest.
+ "auth_basic" "auth_digest"
+
+ # Authentication: is the user who he claims to be?
+ "authn_file" "authn_dbm" "authn_anon" "authn_core"
+
+ # Authorization: is the user allowed access?
+ "authz_user" "authz_groupfile" "authz_host" "authz_core"
+
+ # Other modules.
+ "ext_filter" "include" "log_config" "env" "mime_magic"
+ "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
+ "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
+ "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
+ "userdir" "alias" "rewrite" "proxy" "proxy_http"
+ "unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb"
+ "mpm_${mainCfg.multiProcessingModule}"
+
+ # For compatibility with old configurations, the new module mod_access_compat is provided.
+ "access_compat"
+ ]
+ ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
+ ++ optional enableSSL "ssl"
+ ++ extraApacheModules;
+
+
+ allDenied = "Require all denied";
+ allGranted = "Require all granted";
+
+
+ loggingConf = (if mainCfg.logFormat != "none" then ''
+ ErrorLog ${mainCfg.logDir}/error.log
+
+ LogLevel notice
+
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+ LogFormat "%h %l %u %t \"%r\" %>s %b" common
+ LogFormat "%{Referer}i -> %U" referer
+ LogFormat "%{User-agent}i" agent
+
+ CustomLog ${mainCfg.logDir}/access.log ${mainCfg.logFormat}
+ '' else ''
+ ErrorLog /dev/null
+ '');
+
+
+ browserHacks = ''
+ BrowserMatch "Mozilla/2" nokeepalive
+ BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
+ BrowserMatch "RealPlayer 4\.0" force-response-1.0
+ BrowserMatch "Java/1\.0" force-response-1.0
+ BrowserMatch "JDK/1\.0" force-response-1.0
+ BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
+ BrowserMatch "^WebDrive" redirect-carefully
+ BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
+ BrowserMatch "^gnome-vfs" redirect-carefully
+ '';
+
+
+ sslConf = ''
+ SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000)
+
+ Mutex posixsem
+
+ SSLRandomSeed startup builtin
+ SSLRandomSeed connect builtin
+
+ SSLProtocol ${mainCfg.sslProtocols}
+ SSLCipherSuite ${mainCfg.sslCiphers}
+ SSLHonorCipherOrder on
+ '';
+
+
+ mimeConf = ''
+ TypesConfig ${httpd}/conf/mime.types
+
+ AddType application/x-x509-ca-cert .crt
+ AddType application/x-pkcs7-crl .crl
+ AddType application/x-httpd-php .php .phtml
+
+ <IfModule mod_mime_magic.c>
+ MIMEMagicFile ${httpd}/conf/magic
+ </IfModule>
+ '';
+
+
+ perServerConf = isMainServer: cfg: let
+
+ serverInfo = makeServerInfo cfg;
+
+ subservices = callSubservices serverInfo cfg.extraSubservices;
+
+ maybeDocumentRoot = fold (svc: acc:
+ if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc
+ ) null ([ cfg ] ++ subservices);
+
+ documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else
+ pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out";
+
+ documentRootConf = ''
+ DocumentRoot "${documentRoot}"
+
+ <Directory "${documentRoot}">
+ Options Indexes FollowSymLinks
+ AllowOverride None
+ ${allGranted}
+ </Directory>
+ '';
+
+ robotsTxt =
+ concatStringsSep "\n" (filter (x: x != "") (
+ # If this is a vhost, the include the entries for the main server as well.
+ (if isMainServer then [] else [mainCfg.robotsEntries] ++ map (svc: svc.robotsEntries) mainSubservices)
+ ++ [cfg.robotsEntries]
+ ++ (map (svc: svc.robotsEntries) subservices)));
+
+ in ''
+ ${concatStringsSep "\n" (map (n: "ServerName ${n}") serverInfo.canonicalNames)}
+
+ ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
+
+ ${if cfg.sslServerCert != null then ''
+ SSLCertificateFile ${cfg.sslServerCert}
+ SSLCertificateKeyFile ${cfg.sslServerKey}
+ ${if cfg.sslServerChain != null then ''
+ SSLCertificateChainFile ${cfg.sslServerChain}
+ '' else ""}
+ '' else ""}
+
+ ${if cfg.enableSSL then ''
+ SSLEngine on
+ '' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */
+ ''
+ SSLEngine off
+ '' else ""}
+
+ ${if isMainServer || cfg.adminAddr != null then ''
+ ServerAdmin ${cfg.adminAddr}
+ '' else ""}
+
+ ${if !isMainServer && mainCfg.logPerVirtualHost then ''
+ ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log
+ CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat}
+ '' else ""}
+
+ ${optionalString (robotsTxt != "") ''
+ Alias /robots.txt ${pkgs.writeText "robots.txt" robotsTxt}
+ ''}
+
+ ${if isMainServer || maybeDocumentRoot != null then documentRootConf else ""}
+
+ ${if cfg.enableUserDir then ''
+
+ UserDir public_html
+ UserDir disabled root
+
+ <Directory "/home/*/public_html">
+ AllowOverride FileInfo AuthConfig Limit Indexes
+ Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+ <Limit GET POST OPTIONS>
+ ${allGranted}
+ </Limit>
+ <LimitExcept GET POST OPTIONS>
+ ${allDenied}
+ </LimitExcept>
+ </Directory>
+
+ '' else ""}
+
+ ${if cfg.globalRedirect != null && cfg.globalRedirect != "" then ''
+ RedirectPermanent / ${cfg.globalRedirect}
+ '' else ""}
+
+ ${
+ let makeFileConf = elem: ''
+ Alias ${elem.urlPath} ${elem.file}
+ '';
+ in concatMapStrings makeFileConf cfg.servedFiles
+ }
+
+ ${
+ let makeDirConf = elem: ''
+ Alias ${elem.urlPath} ${elem.dir}/
+ <Directory ${elem.dir}>
+ Options +Indexes
+ ${allGranted}
+ AllowOverride All
+ </Directory>
+ '';
+ in concatMapStrings makeDirConf cfg.servedDirs
+ }
+
+ ${concatMapStrings (svc: svc.extraConfig) subservices}
+
+ ${cfg.extraConfig}
+ '';
+
+
+ confFile = pkgs.writeText "httpd.conf" ''
+
+ ServerRoot ${httpd}
+
+ DefaultRuntimeDir ${mainCfg.stateDir}/runtime
+
+ PidFile ${mainCfg.stateDir}/httpd.pid
+
+ ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
+ # mod_cgid requires this.
+ ScriptSock ${mainCfg.stateDir}/cgisock
+ ''}
+
+ <IfModule prefork.c>
+ MaxClients ${toString mainCfg.maxClients}
+ MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild}
+ </IfModule>
+
+ ${let
+ listen = concatMap getListen allHosts;
+ toStr = listen: "Listen ${listenToString listen}\n";
+ uniqueListen = uniqList {inputList = map toStr listen;};
+ in concatStrings uniqueListen
+ }
+
+ User ${mainCfg.user}
+ Group ${mainCfg.group}
+
+ ${let
+ load = {name, path}: "LoadModule ${name}_module ${path}\n";
+ allModules =
+ concatMap (svc: svc.extraModulesPre) allSubservices
+ ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
+ ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
+ ++ optional enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+ ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
+ ++ concatMap (svc: svc.extraModules) allSubservices
+ ++ extraForeignModules;
+ in concatMapStrings load (unique allModules)
+ }
+
+ AddHandler type-map var
+
+ <Files ~ "^\.ht">
+ ${allDenied}
+ </Files>
+
+ ${mimeConf}
+ ${loggingConf}
+ ${browserHacks}
+
+ Include ${httpd}/conf/extra/httpd-default.conf
+ Include ${httpd}/conf/extra/httpd-autoindex.conf
+ Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf
+ Include ${httpd}/conf/extra/httpd-languages.conf
+
+ TraceEnable off
+
+ ${if enableSSL then sslConf else ""}
+
+ # Fascist default - deny access to everything.
+ <Directory />
+ Options FollowSymLinks
+ AllowOverride None
+ ${allDenied}
+ </Directory>
+
+ # But do allow access to files in the store so that we don't have
+ # to generate <Directory> clauses for every generated file that we
+ # want to serve.
+ <Directory /nix/store>
+ ${allGranted}
+ </Directory>
+
+ # Generate directives for the main server.
+ ${perServerConf true mainCfg}
+
+ ${let
+ makeVirtualHost = vhost: ''
+ <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
+ ${perServerConf false vhost}
+ </VirtualHost>
+ '';
+ in concatMapStrings makeVirtualHost mainCfg.virtualHosts
+ }
+ '';
+
+
+ enablePHP = mainCfg.enablePHP || any (svc: svc.enablePHP) allSubservices;
+
+ enablePerl = mainCfg.enablePerl || any (svc: svc.enablePerl) allSubservices;
+
+
+ # Generate the PHP configuration file. Should probably be factored
+ # out into a separate module.
+ phpIni = pkgs.runCommand "php.ini"
+ { options = concatStringsSep "\n"
+ ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices));
+ preferLocalBuild = true;
+ }
+ ''
+ cat ${php}/etc/php.ini > $out
+ echo "$options" >> $out
+ '';
+
+in
+
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.httpd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Apache HTTP Server.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.apacheHttpd;
+ defaultText = "pkgs.apacheHttpd";
+ description = ''
+ Overridable attribute of the Apache HTTP Server package to use.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ default = confFile;
+ defaultText = "confFile";
+ example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
+ description = ''
+ Override the configuration file used by Apache. By default,
+ NixOS generates one automatically.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Cnfiguration lines appended to the generated Apache
+ configuration file. Note that this mechanism may not work
+ when <option>configFile</option> is overridden.
+ '';
+ };
+
+ extraModules = mkOption {
+ type = types.listOf types.unspecified;
+ default = [];
+ example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
+ description = ''
+ Additional Apache modules to be used. These can be
+ specified as a string in the case of modules distributed
+ with Apache, or as an attribute set specifying the
+ <varname>name</varname> and <varname>path</varname> of the
+ module.
+ '';
+ };
+
+ logPerVirtualHost = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, each virtual host gets its own
+ <filename>access.log</filename> and
+ <filename>error.log</filename>, namely suffixed by the
+ <option>hostName</option> of the virtual host.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "wwwrun";
+ description = ''
+ User account under which httpd runs. The account is created
+ automatically if it doesn't exist.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "wwwrun";
+ description = ''
+ Group under which httpd runs. The account is created
+ automatically if it doesn't exist.
+ '';
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ default = "/var/log/httpd";
+ description = ''
+ Directory for Apache's log files. It is created automatically.
+ '';
+ };
+
+ stateDir = mkOption {
+ type = types.path;
+ default = "/run/httpd";
+ description = ''
+ Directory for Apache's transient runtime state (such as PID
+ files). It is created automatically. Note that the default,
+ <filename>/run/httpd</filename>, is deleted at boot time.
+ '';
+ };
+
+ virtualHosts = mkOption {
+ type = types.listOf (types.submodule (
+ { options = import ./per-server-options.nix {
+ inherit lib;
+ forMainServer = false;
+ };
+ }));
+ default = [];
+ example = [
+ { hostName = "foo";
+ documentRoot = "/data/webroot-foo";
+ }
+ { hostName = "bar";
+ documentRoot = "/data/webroot-bar";
+ }
+ ];
+ description = ''
+ Specification of the virtual hosts served by Apache. Each
+ element should be an attribute set specifying the
+ configuration of the virtual host. The available options
+ are the non-global options permissible for the main host.
+ '';
+ };
+
+ enableMellon = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the mod_auth_mellon module.";
+ };
+
+ enablePHP = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the PHP module.";
+ };
+
+ phpPackage = mkOption {
+ type = types.package;
+ default = pkgs.php;
+ defaultText = "pkgs.php";
+ description = ''
+ Overridable attribute of the PHP package to use.
+ '';
+ };
+
+ enablePerl = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Perl module (mod_perl).";
+ };
+
+ phpOptions = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ date.timezone = "CET"
+ '';
+ description =
+ "Options appended to the PHP configuration file <filename>php.ini</filename>.";
+ };
+
+ multiProcessingModule = mkOption {
+ type = types.str;
+ default = "prefork";
+ example = "worker";
+ description =
+ ''
+ Multi-processing module to be used by Apache. Available
+ modules are <literal>prefork</literal> (the default;
+ handles each request in a separate child process),
+ <literal>worker</literal> (hybrid approach that starts a
+ number of child processes each running a number of
+ threads) and <literal>event</literal> (a recent variant of
+ <literal>worker</literal> that handles persistent
+ connections more efficiently).
+ '';
+ };
+
+ maxClients = mkOption {
+ type = types.int;
+ default = 150;
+ example = 8;
+ description = "Maximum number of httpd processes (prefork)";
+ };
+
+ maxRequestsPerChild = mkOption {
+ type = types.int;
+ default = 0;
+ example = 500;
+ description =
+ "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited";
+ };
+
+ sslCiphers = mkOption {
+ type = types.str;
+ default = "HIGH:!aNULL:!MD5:!EXP";
+ description = "Cipher Suite available for negotiation in SSL proxy handshake.";
+ };
+
+ sslProtocols = mkOption {
+ type = types.str;
+ default = "All -SSLv2 -SSLv3 -TLSv1";
+ example = "All -SSLv2 -SSLv3";
+ description = "Allowed SSL/TLS protocol versions.";
+ };
+ }
+
+ # Include the options shared between the main server and virtual hosts.
+ // (import ./per-server-options.nix {
+ inherit lib;
+ forMainServer = true;
+ });
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.httpd.enable {
+
+ assertions = [ { assertion = mainCfg.enableSSL == true
+ -> mainCfg.sslServerCert != null
+ && mainCfg.sslServerKey != null;
+ message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; }
+ ];
+
+ warnings = map (cfg: "apache-httpd's extraSubservices option is deprecated. Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") (lib.filter (cfg: cfg.extraSubservices != []) allHosts);
+
+ users.users = optionalAttrs (mainCfg.user == "wwwrun") (singleton
+ { name = "wwwrun";
+ group = mainCfg.group;
+ description = "Apache httpd user";
+ uid = config.ids.uids.wwwrun;
+ });
+
+ users.groups = optionalAttrs (mainCfg.group == "wwwrun") (singleton
+ { name = "wwwrun";
+ gid = config.ids.gids.wwwrun;
+ });
+
+ environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
+
+ services.httpd.phpOptions =
+ ''
+ ; Needed for PHP's mail() function.
+ sendmail_path = sendmail -t -i
+
+ ; Don't advertise PHP
+ expose_php = off
+ '' + optionalString (config.time.timeZone != null) ''
+
+ ; Apparently PHP doesn't use $TZ.
+ date.timezone = "${config.time.timeZone}"
+ '';
+
+ systemd.services.httpd =
+ { description = "Apache HTTPD";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "fs.target" ];
+
+ path =
+ [ httpd pkgs.coreutils pkgs.gnugrep ]
+ ++ optional enablePHP pkgs.system-sendmail # Needed for PHP's mail() function.
+ ++ concatMap (svc: svc.extraServerPath) allSubservices;
+
+ environment =
+ optionalAttrs enablePHP { PHPRC = phpIni; }
+ // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }
+ // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices));
+
+ preStart =
+ ''
+ mkdir -m 0750 -p ${mainCfg.stateDir}
+ [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
+
+ mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
+ [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
+
+ mkdir -m 0700 -p ${mainCfg.logDir}
+
+ # Get rid of old semaphores. These tend to accumulate across
+ # server restarts, eventually preventing it from restarting
+ # successfully.
+ for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do
+ ${pkgs.utillinux}/bin/ipcrm -s $i
+ done
+
+ # Run the startup hooks for the subservices.
+ for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do
+ echo Running Apache startup hook $i...
+ $i
+ done
+ '';
+
+ serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
+ serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
+ serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
+ serviceConfig.Type = "forking";
+ serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
+ serviceConfig.Restart = "always";
+ serviceConfig.RestartSec = "5s";
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
new file mode 100644
index 00000000000..9d747549c27
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
@@ -0,0 +1,180 @@
+# This file defines the options that can be used both for the Apache
+# main server configuration, and for the virtual hosts. (The latter
+# has additional options that affect the web server as a whole, like
+# the user/group to run under.)
+
+{ forMainServer, lib }:
+
+with lib;
+
+{
+
+ hostName = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Canonical hostname for the server.";
+ };
+
+ serverAliases = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ["www.example.org" "www.example.org:8080" "example.org"];
+ description = ''
+ Additional names of virtual hosts served by this virtual host configuration.
+ '';
+ };
+
+ listen = mkOption {
+ type = types.listOf (types.submodule (
+ {
+ options = {
+ port = mkOption {
+ type = types.int;
+ description = "port to listen on";
+ };
+ ip = mkOption {
+ type = types.str;
+ default = "*";
+ description = "Ip to listen on. 0.0.0.0 for ipv4 only, * for all.";
+ };
+ };
+ } ));
+ description = ''
+ List of { /* ip: "*"; */ port = 80;} to listen on
+ '';
+
+ default = [];
+ };
+
+ enableSSL = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable SSL (https) support.";
+ };
+
+ # Note: sslServerCert and sslServerKey can be left empty, but this
+ # only makes sense for virtual hosts (they will inherit from the
+ # main server).
+
+ sslServerCert = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/var/host.cert";
+ description = "Path to server SSL certificate.";
+ };
+
+ sslServerKey = mkOption {
+ type = types.path;
+ example = "/var/host.key";
+ description = "Path to server SSL certificate key.";
+ };
+
+ sslServerChain = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/var/ca.pem";
+ description = "Path to server SSL chain file.";
+ };
+
+ adminAddr = mkOption ({
+ type = types.nullOr types.str;
+ example = "admin@example.org";
+ description = "E-mail address of the server administrator.";
+ } // (if forMainServer then {} else {default = null;}));
+
+ documentRoot = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/data/webserver/docs";
+ description = ''
+ The path of Apache's document root directory. If left undefined,
+ an empty directory in the Nix store will be used as root.
+ '';
+ };
+
+ servedDirs = mkOption {
+ type = types.listOf types.attrs;
+ default = [];
+ example = [
+ { urlPath = "/nix";
+ dir = "/home/eelco/Dev/nix-homepage";
+ }
+ ];
+ description = ''
+ This option provides a simple way to serve static directories.
+ '';
+ };
+
+ servedFiles = mkOption {
+ type = types.listOf types.attrs;
+ default = [];
+ example = [
+ { urlPath = "/foo/bar.png";
+ file = "/home/eelco/some-file.png";
+ }
+ ];
+ description = ''
+ This option provides a simple way to serve individual, static files.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ <Directory /home>
+ Options FollowSymlinks
+ AllowOverride All
+ </Directory>
+ '';
+ description = ''
+ These lines go to httpd.conf verbatim. They will go after
+ directories and directory aliases defined by default.
+ '';
+ };
+
+ extraSubservices = mkOption {
+ type = types.listOf types.unspecified;
+ default = [];
+ description = "Extra subservices to enable in the webserver.";
+ };
+
+ enableUserDir = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable serving <filename>~/public_html</filename> as
+ <literal>/~<replaceable>username</replaceable></literal>.
+ '';
+ };
+
+ globalRedirect = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = http://newserver.example.org/;
+ description = ''
+ If set, all requests for this host are redirected permanently to
+ the given URL.
+ '';
+ };
+
+ logFormat = mkOption {
+ type = types.str;
+ default = "common";
+ example = "combined";
+ description = ''
+ Log format for Apache's log files. Possible values are: combined, common, referer, agent.
+ '';
+ };
+
+ robotsEntries = mkOption {
+ type = types.lines;
+ default = "";
+ example = "Disallow: /foo/";
+ description = ''
+ Specification of pages to be ignored by web crawlers. See <link
+ xlink:href='http://www.robotstxt.org/'/> for details.
+ '';
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/caddy.nix b/nixpkgs/nixos/modules/services/web-servers/caddy.nix
new file mode 100644
index 00000000000..132c50735d9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/caddy.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.caddy;
+ configFile = pkgs.writeText "Caddyfile" cfg.config;
+in {
+ options.services.caddy = {
+ enable = mkEnableOption "Caddy web server";
+
+ config = mkOption {
+ default = "";
+ example = ''
+ example.com {
+ gzip
+ minify
+ log syslog
+
+ root /srv/http
+ }
+ '';
+ type = types.lines;
+ description = "Verbatim Caddyfile to use";
+ };
+
+ ca = mkOption {
+ default = "https://acme-v02.api.letsencrypt.org/directory";
+ example = "https://acme-staging-v02.api.letsencrypt.org/directory";
+ type = types.str;
+ description = "Certificate authority ACME server. The default (Let's Encrypt production server) should be fine for most people.";
+ };
+
+ email = mkOption {
+ default = "";
+ type = types.str;
+ description = "Email address (for Let's Encrypt certificate)";
+ };
+
+ agree = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Agree to Let's Encrypt Subscriber Agreement";
+ };
+
+ dataDir = mkOption {
+ default = "/var/lib/caddy";
+ type = types.path;
+ description = ''
+ The data directory, for storing certificates. Before 17.09, this
+ would create a .caddy directory. With 17.09 the contents of the
+ .caddy directory are in the specified data directory instead.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.caddy;
+ defaultText = "pkgs.caddy";
+ type = types.package;
+ description = "Caddy package to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.caddy = {
+ description = "Caddy web server";
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ environment = mkIf (versionAtLeast config.system.stateVersion "17.09")
+ { CADDYPATH = cfg.dataDir; };
+ serviceConfig = {
+ ExecStart = ''
+ ${cfg.package}/bin/caddy -root=/var/tmp -conf=${configFile} \
+ -ca=${cfg.ca} -email=${cfg.email} ${optionalString cfg.agree "-agree"}
+ '';
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ Type = "simple";
+ User = "caddy";
+ Group = "caddy";
+ Restart = "on-failure";
+ StartLimitInterval = 86400;
+ StartLimitBurst = 5;
+ AmbientCapabilities = "cap_net_bind_service";
+ CapabilityBoundingSet = "cap_net_bind_service";
+ NoNewPrivileges = true;
+ LimitNPROC = 64;
+ LimitNOFILE = 1048576;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectHome = true;
+ ProtectSystem = "full";
+ ReadWriteDirectories = cfg.dataDir;
+ };
+ };
+
+ users.users.caddy = {
+ group = "caddy";
+ uid = config.ids.uids.caddy;
+ home = cfg.dataDir;
+ createHome = true;
+ };
+
+ users.groups.caddy.gid = config.ids.uids.caddy;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix b/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix
new file mode 100644
index 00000000000..d6649fd472d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.darkhttpd;
+
+ args = concatStringsSep " " ([
+ cfg.rootDir
+ "--port ${toString cfg.port}"
+ "--addr ${cfg.address}"
+ ] ++ cfg.extraArgs
+ ++ optional cfg.hideServerId "--no-server-id"
+ ++ optional config.networking.enableIPv6 "--ipv6");
+
+in {
+ options.services.darkhttpd = with types; {
+ enable = mkEnableOption "DarkHTTPd web server";
+
+ port = mkOption {
+ default = 80;
+ type = ints.u16;
+ description = ''
+ Port to listen on.
+ Pass 0 to let the system choose any free port for you.
+ '';
+ };
+
+ address = mkOption {
+ default = "127.0.0.1";
+ type = str;
+ description = ''
+ Address to listen on.
+ Pass `all` to listen on all interfaces.
+ '';
+ };
+
+ rootDir = mkOption {
+ type = path;
+ description = ''
+ Path from which to serve files.
+ '';
+ };
+
+ hideServerId = mkOption {
+ type = bool;
+ default = true;
+ description = ''
+ Don't identify the server type in headers or directory listings.
+ '';
+ };
+
+ extraArgs = mkOption {
+ type = listOf str;
+ default = [];
+ description = ''
+ Additional configuration passed to the executable.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.darkhttpd = {
+ description = "Dark HTTPd";
+ wants = [ "network.target" ];
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ DynamicUser = true;
+ ExecStart = "${pkgs.darkhttpd}/bin/darkhttpd ${args}";
+ AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+ Restart = "on-failure";
+ RestartSec = "2s";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix
new file mode 100644
index 00000000000..a64a187255a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.fcgiwrap;
+in {
+
+ options = {
+ services.fcgiwrap = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable fcgiwrap, a server for running CGI applications over FastCGI.";
+ };
+
+ preforkProcesses = mkOption {
+ type = types.int;
+ default = 1;
+ description = "Number of processes to prefork.";
+ };
+
+ socketType = mkOption {
+ type = types.enum [ "unix" "tcp" "tcp6" ];
+ default = "unix";
+ description = "Socket type: 'unix', 'tcp' or 'tcp6'.";
+ };
+
+ socketAddress = mkOption {
+ type = types.str;
+ default = "/run/fcgiwrap.sock";
+ example = "1.2.3.4:5678";
+ description = "Socket address. In case of a UNIX socket, this should be its filesystem path.";
+ };
+
+ user = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "User permissions for the socket.";
+ };
+
+ group = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Group permissions for the socket.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.fcgiwrap = {
+ after = [ "nss-user-lookup.target" ];
+ wantedBy = optional (cfg.socketType != "unix") "multi-user.target";
+
+ serviceConfig = {
+ ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${builtins.toString cfg.preforkProcesses} ${
+ if (cfg.socketType != "unix") then "-s ${cfg.socketType}:${cfg.socketAddress}" else ""
+ }";
+ } // (if cfg.user != null && cfg.group != null then {
+ User = cfg.user;
+ Group = cfg.group;
+ } else { } );
+ };
+
+ systemd.sockets = if (cfg.socketType == "unix") then {
+ fcgiwrap = {
+ wantedBy = [ "sockets.target" ];
+ socketConfig.ListenStream = cfg.socketAddress;
+ };
+ } else { };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix
new file mode 100644
index 00000000000..a6c4cbea122
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ...}:
+let
+ cfg = config.services.hitch;
+ ocspDir = lib.optionalString cfg.ocsp-stapling.enabled "/var/cache/hitch/ocsp";
+ hitchConfig = with lib; pkgs.writeText "hitch.conf" (concatStringsSep "\n" [
+ ("backend = \"${cfg.backend}\"")
+ (concatMapStrings (s: "frontend = \"${s}\"\n") cfg.frontend)
+ (concatMapStrings (s: "pem-file = \"${s}\"\n") cfg.pem-files)
+ ("ciphers = \"${cfg.ciphers}\"")
+ ("ocsp-dir = \"${ocspDir}\"")
+ "user = \"${cfg.user}\""
+ "group = \"${cfg.group}\""
+ cfg.extraConfig
+ ]);
+in
+with lib;
+{
+ options = {
+ services.hitch = {
+ enable = mkEnableOption "Hitch Server";
+
+ backend = mkOption {
+ type = types.str;
+ description = ''
+ The host and port Hitch connects to when receiving
+ a connection in the form [HOST]:PORT
+ '';
+ };
+
+ ciphers = mkOption {
+ type = types.str;
+ default = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+ description = "The list of ciphers to use";
+ };
+
+ frontend = mkOption {
+ type = types.either types.str (types.listOf types.str);
+ default = "[127.0.0.1]:443";
+ description = ''
+ The port and interface of the listen endpoint in the
++ form [HOST]:PORT[+CERT].
+ '';
+ apply = toList;
+ };
+
+ pem-files = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = "PEM files to use";
+ };
+
+ ocsp-stapling = {
+ enabled = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to enable OCSP Stapling";
+ };
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "hitch";
+ description = "The user to run as";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "hitch";
+ description = "The group to run as";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional configuration lines";
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.hitch = {
+ description = "Hitch";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ preStart = ''
+ ${pkgs.hitch}/sbin/hitch -t --config ${hitchConfig}
+ '' + (optionalString cfg.ocsp-stapling.enabled ''
+ mkdir -p ${ocspDir}
+ chown -R hitch:hitch ${ocspDir}
+ '');
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.hitch}/sbin/hitch --daemon --config ${hitchConfig}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ Restart = "always";
+ RestartSec = "5s";
+ LimitNOFILE = 131072;
+ };
+ };
+
+ environment.systemPackages = [ pkgs.hitch ];
+
+ users.users.hitch.group = "hitch";
+ users.groups.hitch = {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/hydron.nix b/nixpkgs/nixos/modules/services/web-servers/hydron.nix
new file mode 100644
index 00000000000..a4a5a435b2e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/hydron.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.hydron;
+in with lib; {
+ options.services.hydron = {
+ enable = mkEnableOption "hydron";
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/hydron";
+ example = "/home/okina/hydron";
+ description = "Location where hydron runs and stores data.";
+ };
+
+ interval = mkOption {
+ type = types.str;
+ default = "weekly";
+ example = "06:00";
+ description = ''
+ How often we run hydron import and possibly fetch tags. Runs by default every week.
+
+ The format is described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ '';
+ };
+
+ password = mkOption {
+ type = types.str;
+ default = "hydron";
+ example = "dumbpass";
+ description = "Password for the hydron database.";
+ };
+
+ passwordFile = mkOption {
+ type = types.path;
+ default = "/run/keys/hydron-password-file";
+ example = "/home/okina/hydron/keys/pass";
+ description = "Password file for the hydron database.";
+ };
+
+ postgresArgs = mkOption {
+ type = types.str;
+ description = "Postgresql connection arguments.";
+ example = ''
+ {
+ "driver": "postgres",
+ "connection": "user=hydron password=dumbpass dbname=hydron sslmode=disable"
+ }
+ '';
+ };
+
+ postgresArgsFile = mkOption {
+ type = types.path;
+ default = "/run/keys/hydron-postgres-args";
+ example = "/home/okina/hydron/keys/postgres";
+ description = "Postgresql connection arguments file.";
+ };
+
+ listenAddress = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "127.0.0.1:8010";
+ description = "Listen on a specific IP address and port.";
+ };
+
+ importPaths = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ example = [ "/home/okina/Pictures" ];
+ description = "Paths that hydron will recursively import.";
+ };
+
+ fetchTags = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Fetch tags for imported images and webm from gelbooru.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.hydron.passwordFile = mkDefault (pkgs.writeText "hydron-password-file" cfg.password);
+ services.hydron.postgresArgsFile = mkDefault (pkgs.writeText "hydron-postgres-args" cfg.postgresArgs);
+ services.hydron.postgresArgs = mkDefault ''
+ {
+ "driver": "postgres",
+ "connection": "user=hydron password=${cfg.password} host=/run/postgresql dbname=hydron sslmode=disable"
+ }
+ '';
+
+ services.postgresql = {
+ enable = true;
+ ensureDatabases = [ "hydron" ];
+ ensureUsers = [
+ { name = "hydron";
+ ensurePermissions = { "DATABASE hydron" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0750 hydron hydron - -"
+ "d '${cfg.dataDir}/.hydron' - hydron hydron - -"
+ "d '${cfg.dataDir}/images' - hydron hydron - -"
+ "Z '${cfg.dataDir}' - hydron hydron - -"
+
+ "L+ '${cfg.dataDir}/.hydron/db_conf.json' - - - - ${cfg.postgresArgsFile}"
+ ];
+
+ systemd.services.hydron = {
+ description = "hydron";
+ after = [ "network.target" "postgresql.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = "hydron";
+ Group = "hydron";
+ ExecStart = "${pkgs.hydron}/bin/hydron serve"
+ + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}";
+ };
+ };
+
+ systemd.services.hydron-fetch = {
+ description = "Import paths into hydron and possibly fetch tags";
+
+ serviceConfig = {
+ Type = "oneshot";
+ User = "hydron";
+ Group = "hydron";
+ ExecStart = "${pkgs.hydron}/bin/hydron import "
+ + optionalString cfg.fetchTags "-f "
+ + (escapeShellArg cfg.dataDir) + "/images " + (escapeShellArgs cfg.importPaths);
+ };
+ };
+
+ systemd.timers.hydron-fetch = {
+ description = "Automatically import paths into hydron and possibly fetch tags";
+ after = [ "network.target" "hydron.service" ];
+ wantedBy = [ "timers.target" ];
+
+ timerConfig = {
+ Persistent = true;
+ OnCalendar = cfg.interval;
+ };
+ };
+
+ users = {
+ groups.hydron.gid = config.ids.gids.hydron;
+
+ users.hydron = {
+ description = "hydron server service user";
+ home = cfg.dataDir;
+ group = "hydron";
+ uid = config.ids.uids.hydron;
+ };
+ };
+ };
+
+ imports = [
+ (mkRenamedOptionModule [ "services" "hydron" "baseDir" ] [ "services" "hydron" "dataDir" ])
+ ];
+
+ meta.maintainers = with maintainers; [ chiiruno ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh b/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh
new file mode 100644
index 00000000000..2eb89a90f67
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh
@@ -0,0 +1,72 @@
+set -e
+
+source $stdenv/setup
+
+mkdir -p $out/bin
+
+cat > $out/bin/control <<EOF
+mkdir -p $logDir
+chown -R $user $logDir
+export PATH=$PATH:$su/bin
+
+start()
+{
+ su $user -s /bin/sh -c "$jboss/bin/run.sh \
+ -Djboss.server.base.dir=$serverDir \
+ -Djboss.server.base.url=file://$serverDir \
+ -Djboss.server.temp.dir=$tempDir \
+ -Djboss.server.log.dir=$logDir \
+ -Djboss.server.lib.url=$libUrl \
+ -c default"
+}
+
+stop()
+{
+ su $user -s /bin/sh -c "$jboss/bin/shutdown.sh -S"
+}
+
+if test "\$1" = start
+then
+ trap stop 15
+
+ start
+elif test "\$1" = stop
+then
+ stop
+elif test "\$1" = init
+then
+ echo "Are you sure you want to create a new server instance (old server instance will be lost!)?"
+ read answer
+
+ if ! test \$answer = "yes"
+ then
+ exit 1
+ fi
+
+ rm -rf $serverDir
+ mkdir -p $serverDir
+ cd $serverDir
+ cp -av $jboss/server/default .
+ sed -i -e "s|deploy/|$deployDir|" default/conf/jboss-service.xml
+
+ if ! test "$useJK" = ""
+ then
+ sed -i -e 's|<attribute name="UseJK">false</attribute>|<attribute name="UseJK">true</attribute>|' default/deploy/jboss-web.deployer/META-INF/jboss-service.xml
+ sed -i -e 's|<Engine name="jboss.web" defaultHost="localhost">|<Engine name="jboss.web" defaultHost="localhost" jvmRoute="node1">|' default/deploy/jboss-web.deployer/server.xml
+ fi
+
+ # Make files accessible for the server user
+
+ chown -R $user $serverDir
+ for i in \`find $serverDir -type d\`
+ do
+ chmod 755 \$i
+ done
+ for i in \`find $serverDir -type f\`
+ do
+ chmod 644 \$i
+ done
+fi
+EOF
+
+chmod +x $out/bin/*
diff --git a/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix b/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix
new file mode 100644
index 00000000000..d28724281a8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.jboss;
+
+ jbossService = pkgs.stdenv.mkDerivation {
+ name = "jboss-server";
+ builder = ./builder.sh;
+ inherit (pkgs) jboss su;
+ inherit (cfg) tempDir logDir libUrl deployDir serverDir user useJK;
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.jboss = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable JBoss. WARNING : this package is outdated and is known to have vulnerabilities.";
+ };
+
+ tempDir = mkOption {
+ default = "/tmp";
+ description = "Location where JBoss stores its temp files";
+ };
+
+ logDir = mkOption {
+ default = "/var/log/jboss";
+ description = "Location of the logfile directory of JBoss";
+ };
+
+ serverDir = mkOption {
+ description = "Location of the server instance files";
+ default = "/var/jboss/server";
+ };
+
+ deployDir = mkOption {
+ description = "Location of the deployment files";
+ default = "/nix/var/nix/profiles/default/server/default/deploy/";
+ };
+
+ libUrl = mkOption {
+ default = "file:///nix/var/nix/profiles/default/server/default/lib";
+ description = "Location where the shared library JARs are stored";
+ };
+
+ user = mkOption {
+ default = "nobody";
+ description = "User account under which jboss runs.";
+ };
+
+ useJK = mkOption {
+ default = false;
+ description = "Whether to use to connector to the Apache HTTP server";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.jboss.enable {
+ systemd.services.jboss = {
+ description = "JBoss server";
+ script = "${jbossService}/bin/control start";
+ wantedBy = [ "multi-user.target" ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix
new file mode 100644
index 00000000000..9f25dc34f3f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.lighttpd.cgit;
+ pathPrefix = if stringLength cfg.subdir == 0 then "" else "/" + cfg.subdir;
+ configFile = pkgs.writeText "cgitrc"
+ ''
+ # default paths to static assets
+ css=${pathPrefix}/cgit.css
+ logo=${pathPrefix}/cgit.png
+ favicon=${pathPrefix}/favicon.ico
+
+ # user configuration
+ ${cfg.configText}
+ '';
+in
+{
+
+ options.services.lighttpd.cgit = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If true, enable cgit (fast web interface for git repositories) as a
+ sub-service in lighttpd.
+ '';
+ };
+
+ subdir = mkOption {
+ default = "cgit";
+ example = "";
+ type = types.str;
+ description = ''
+ The subdirectory in which to serve cgit. The web application will be
+ accessible at http://yourserver/''${subdir}
+ '';
+ };
+
+ configText = mkOption {
+ default = "";
+ example = ''
+ source-filter=''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
+ about-filter=''${pkgs.cgit}/lib/cgit/filters/about-formatting.sh
+ cache-size=1000
+ scan-path=/srv/git
+ '';
+ type = types.lines;
+ description = ''
+ Verbatim contents of the cgit runtime configuration file. Documentation
+ (with cgitrc example file) is available in "man cgitrc". Or online:
+ http://git.zx2c4.com/cgit/tree/cgitrc.5.txt
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ # make the cgitrc manpage available
+ environment.systemPackages = [ pkgs.cgit ];
+
+ # declare module dependencies
+ services.lighttpd.enableModules = [ "mod_cgi" "mod_alias" "mod_setenv" ];
+
+ services.lighttpd.extraConfig = ''
+ $HTTP["url"] =~ "^/${cfg.subdir}" {
+ cgi.assign = (
+ "cgit.cgi" => "${pkgs.cgit}/cgit/cgit.cgi"
+ )
+ alias.url = (
+ "${pathPrefix}/cgit.css" => "${pkgs.cgit}/cgit/cgit.css",
+ "${pathPrefix}/cgit.png" => "${pkgs.cgit}/cgit/cgit.png",
+ "${pathPrefix}" => "${pkgs.cgit}/cgit/cgit.cgi"
+ )
+ setenv.add-environment = (
+ "CGIT_CONFIG" => "${configFile}"
+ )
+ }
+ '';
+
+ systemd.services.lighttpd.preStart = ''
+ mkdir -p /var/cache/cgit
+ chown lighttpd:lighttpd /var/cache/cgit
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
new file mode 100644
index 00000000000..3f262451c2c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.lighttpd.collectd;
+
+ collectionConf = pkgs.writeText "collection.conf" ''
+ datadir: "${config.services.collectd.dataDir}"
+ libdir: "${config.services.collectd.package}/lib/collectd"
+ '';
+
+ defaultCollectionCgi = config.services.collectd.package.overrideDerivation(old: {
+ name = "collection.cgi";
+ dontConfigure = true;
+ buildPhase = "true";
+ installPhase = ''
+ substituteInPlace contrib/collection.cgi --replace '"/etc/collection.conf"' '$ENV{COLLECTION_CONF}'
+ cp contrib/collection.cgi $out
+ '';
+ });
+in
+{
+
+ options.services.lighttpd.collectd = {
+
+ enable = mkEnableOption "collectd subservice accessible at http://yourserver/collectd";
+
+ collectionCgi = mkOption {
+ type = types.path;
+ default = defaultCollectionCgi;
+ description = ''
+ Path to collection.cgi script from (collectd sources)/contrib/collection.cgi
+ This option allows to use a customized version
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.lighttpd.enableModules = [ "mod_cgi" "mod_alias" "mod_setenv" ];
+
+ services.lighttpd.extraConfig = ''
+ $HTTP["url"] =~ "^/collectd" {
+ cgi.assign = (
+ ".cgi" => "${pkgs.perl}/bin/perl"
+ )
+ alias.url = (
+ "/collectd" => "${cfg.collectionCgi}"
+ )
+ setenv.add-environment = (
+ "PERL5LIB" => "${with pkgs.perlPackages; makePerlPath [ CGI HTMLParser URI pkgs.rrdtool ]}",
+ "COLLECTION_CONF" => "${collectionConf}"
+ )
+ }
+ '';
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix
new file mode 100644
index 00000000000..7a3df26e47a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -0,0 +1,256 @@
+# NixOS module for lighttpd web server
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.lighttpd;
+
+ # List of known lighttpd modules, ordered by how the lighttpd documentation
+ # recommends them being imported:
+ # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+ #
+ # Some modules are always imported and should not appear in the config:
+ # disallowedModules = [ "mod_indexfile" "mod_dirlisting" "mod_staticfile" ];
+ #
+ # For full module list, see the output of running ./configure in the lighttpd
+ # source.
+ allKnownModules = [
+ "mod_rewrite"
+ "mod_redirect"
+ "mod_alias"
+ "mod_access"
+ "mod_auth"
+ "mod_status"
+ "mod_simple_vhost"
+ "mod_evhost"
+ "mod_userdir"
+ "mod_secdownload"
+ "mod_fastcgi"
+ "mod_proxy"
+ "mod_cgi"
+ "mod_ssi"
+ "mod_compress"
+ "mod_usertrack"
+ "mod_expire"
+ "mod_rrdtool"
+ "mod_accesslog"
+ # Remaining list of modules, order assumed to be unimportant.
+ "mod_authn_file"
+ "mod_authn_gssapi"
+ "mod_authn_ldap"
+ "mod_authn_mysql"
+ "mod_cml"
+ "mod_deflate"
+ "mod_evasive"
+ "mod_extforward"
+ "mod_flv_streaming"
+ "mod_geoip"
+ "mod_magnet"
+ "mod_mysql_vhost"
+ "mod_openssl" # since v1.4.46
+ "mod_scgi"
+ "mod_setenv"
+ "mod_trigger_b4_dl"
+ "mod_uploadprogress"
+ "mod_vhostdb" # since v1.4.46
+ "mod_webdav"
+ "mod_wstunnel" # since v1.4.46
+ ];
+
+ maybeModuleString = moduleName:
+ if elem moduleName cfg.enableModules then ''"${moduleName}"'' else "";
+
+ modulesIncludeString = concatStringsSep ",\n"
+ (filter (x: x != "") (map maybeModuleString allKnownModules));
+
+ configFile = if cfg.configText != "" then
+ pkgs.writeText "lighttpd.conf" ''
+ ${cfg.configText}
+ ''
+ else
+ pkgs.writeText "lighttpd.conf" ''
+ server.document-root = "${cfg.document-root}"
+ server.port = ${toString cfg.port}
+ server.username = "lighttpd"
+ server.groupname = "lighttpd"
+
+ # As for why all modules are loaded here, instead of having small
+ # server.modules += () entries in each sub-service extraConfig snippet,
+ # read this:
+ #
+ # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+ # http://redmine.lighttpd.net/issues/2337
+ #
+ # Basically, lighttpd doesn't want to load (or even silently ignore) a
+ # module for a second time, and there is no way to check if a module has
+ # been loaded already. So if two services were to put the same module in
+ # server.modules += (), that would break the lighttpd configuration.
+ server.modules = (
+ ${modulesIncludeString}
+ )
+
+ # Logging (logs end up in systemd journal)
+ accesslog.use-syslog = "enable"
+ server.errorlog-use-syslog = "enable"
+
+ ${lib.optionalString cfg.enableUpstreamMimeTypes ''
+ include "${pkgs.lighttpd}/share/lighttpd/doc/config/conf.d/mime.conf"
+ ''}
+
+ static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
+ index-file.names = ( "index.html" )
+
+ ${if cfg.mod_userdir then ''
+ userdir.path = "public_html"
+ '' else ""}
+
+ ${if cfg.mod_status then ''
+ status.status-url = "/server-status"
+ status.statistics-url = "/server-statistics"
+ status.config-url = "/server-config"
+ '' else ""}
+
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+
+ options = {
+
+ services.lighttpd = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable the lighttpd web server.
+ '';
+ };
+
+ port = mkOption {
+ default = 80;
+ type = types.int;
+ description = ''
+ TCP port number for lighttpd to bind to.
+ '';
+ };
+
+ document-root = mkOption {
+ default = "/srv/www";
+ type = types.path;
+ description = ''
+ Document-root of the web server. Must be readable by the "lighttpd" user.
+ '';
+ };
+
+ mod_userdir = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If true, requests in the form /~user/page.html are rewritten to take
+ the file public_html/page.html from the home directory of the user.
+ '';
+ };
+
+ enableModules = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "mod_cgi" "mod_status" ];
+ description = ''
+ List of lighttpd modules to enable. Sub-services take care of
+ enabling modules as needed, so this option is mainly for when you
+ want to add custom stuff to
+ <option>services.lighttpd.extraConfig</option> that depends on a
+ certain module.
+ '';
+ };
+
+ enableUpstreamMimeTypes = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to include the list of mime types bundled with lighttpd
+ (upstream). If you disable this, no mime types will be added by
+ NixOS and you will have to add your own mime types in
+ <option>services.lighttpd.extraConfig</option>.
+ '';
+ };
+
+ mod_status = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Show server status overview at /server-status, statistics at
+ /server-statistics and list of loaded modules at /server-config.
+ '';
+ };
+
+ configText = mkOption {
+ default = "";
+ type = types.lines;
+ example = ''...verbatim config file contents...'';
+ description = ''
+ Overridable config file contents to use for lighttpd. By default, use
+ the contents automatically generated by NixOS.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ These configuration lines will be appended to the generated lighttpd
+ config file. Note that this mechanism does not work when the manual
+ <option>configText</option> option is used.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = all (x: elem x allKnownModules) cfg.enableModules;
+ message = ''
+ One (or more) modules in services.lighttpd.enableModules are
+ unrecognized.
+
+ Known modules: ${toString allKnownModules}
+
+ services.lighttpd.enableModules: ${toString cfg.enableModules}
+ '';
+ }
+ ];
+
+ services.lighttpd.enableModules = mkMerge
+ [ (mkIf cfg.mod_status [ "mod_status" ])
+ (mkIf cfg.mod_userdir [ "mod_userdir" ])
+ # always load mod_accesslog so that we can log to the journal
+ [ "mod_accesslog" ]
+ ];
+
+ systemd.services.lighttpd = {
+ description = "Lighttpd Web Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${pkgs.lighttpd}/sbin/lighttpd -D -f ${configFile}";
+ # SIGINT => graceful shutdown
+ serviceConfig.KillSignal = "SIGINT";
+ };
+
+ users.users.lighttpd = {
+ group = "lighttpd";
+ description = "lighttpd web server privilege separation user";
+ uid = config.ids.uids.lighttpd;
+ };
+
+ users.groups.lighttpd.gid = config.ids.gids.lighttpd;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix
new file mode 100644
index 00000000000..c494d6966a7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gitweb;
+ package = pkgs.gitweb.override (optionalAttrs cfg.gitwebTheme {
+ gitwebTheme = true;
+ });
+
+in
+{
+
+ options.services.lighttpd.gitweb = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If true, enable gitweb in lighttpd. Access it at http://yourserver/gitweb
+ '';
+ };
+
+ };
+
+ config = mkIf config.services.lighttpd.gitweb.enable {
+
+ # declare module dependencies
+ services.lighttpd.enableModules = [ "mod_cgi" "mod_redirect" "mod_alias" "mod_setenv" ];
+
+ services.lighttpd.extraConfig = ''
+ $HTTP["url"] =~ "^/gitweb" {
+ cgi.assign = (
+ ".cgi" => "${pkgs.perl}/bin/perl"
+ )
+ url.redirect = (
+ "^/gitweb$" => "/gitweb/"
+ )
+ alias.url = (
+ "/gitweb/static/" => "${package}/static/",
+ "/gitweb/" => "${package}/gitweb.cgi"
+ )
+ setenv.add-environment = (
+ "GITWEB_CONFIG" => "${cfg.gitwebConfigFile}",
+ "HOME" => "${cfg.projectroot}"
+ )
+ }
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/meguca.nix b/nixpkgs/nixos/modules/services/web-servers/meguca.nix
new file mode 100644
index 00000000000..5a00070dc94
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/meguca.nix
@@ -0,0 +1,174 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.meguca;
+ postgres = config.services.postgresql;
+in with lib; {
+ options.services.meguca = {
+ enable = mkEnableOption "meguca";
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/lib/meguca";
+ example = "/home/okina/meguca";
+ description = "Location where meguca stores it's database and links.";
+ };
+
+ password = mkOption {
+ type = types.str;
+ default = "meguca";
+ example = "dumbpass";
+ description = "Password for the meguca database.";
+ };
+
+ passwordFile = mkOption {
+ type = types.path;
+ default = "/run/keys/meguca-password-file";
+ example = "/home/okina/meguca/keys/pass";
+ description = "Password file for the meguca database.";
+ };
+
+ reverseProxy = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "192.168.1.5";
+ description = "Reverse proxy IP.";
+ };
+
+ sslCertificate = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/home/okina/meguca/ssl.cert";
+ description = "Path to the SSL certificate.";
+ };
+
+ listenAddress = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "127.0.0.1:8000";
+ description = "Listen on a specific IP address and port.";
+ };
+
+ cacheSize = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 256;
+ description = "Cache size in MB.";
+ };
+
+ postgresArgs = mkOption {
+ type = types.str;
+ example = "user=meguca password=dumbpass dbname=meguca sslmode=disable";
+ description = "Postgresql connection arguments.";
+ };
+
+ postgresArgsFile = mkOption {
+ type = types.path;
+ default = "/run/keys/meguca-postgres-args";
+ example = "/home/okina/meguca/keys/postgres";
+ description = "Postgresql connection arguments file.";
+ };
+
+ compressTraffic = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Compress all traffic with gzip.";
+ };
+
+ assumeReverseProxy = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Assume the server is behind a reverse proxy, when resolving client IPs.";
+ };
+
+ httpsOnly = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Serve and listen only through HTTPS.";
+ };
+
+ videoPaths = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ example = [ "/home/okina/Videos/tehe_pero.webm" ];
+ description = "Videos that will be symlinked into www/videos.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ security.sudo.enable = cfg.enable;
+ services.postgresql.enable = cfg.enable;
+ services.postgresql.package = pkgs.postgresql_11;
+ services.meguca.passwordFile = mkDefault (pkgs.writeText "meguca-password-file" cfg.password);
+ services.meguca.postgresArgsFile = mkDefault (pkgs.writeText "meguca-postgres-args" cfg.postgresArgs);
+ services.meguca.postgresArgs = mkDefault "user=meguca password=${cfg.password} dbname=meguca sslmode=disable";
+
+ systemd.services.meguca = {
+ description = "meguca";
+ after = [ "network.target" "postgresql.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ preStart = ''
+ # Ensure folder exists or create it and links and permissions are correct
+ mkdir -p ${escapeShellArg cfg.dataDir}/www
+ rm -rf ${escapeShellArg cfg.dataDir}/www/videos
+ ln -sf ${pkgs.meguca}/share/meguca/www/* ${escapeShellArg cfg.dataDir}/www
+ unlink ${escapeShellArg cfg.dataDir}/www/videos
+ mkdir -p ${escapeShellArg cfg.dataDir}/www/videos
+
+ for vid in ${escapeShellArg cfg.videoPaths}; do
+ ln -sf $vid ${escapeShellArg cfg.dataDir}/www/videos
+ done
+
+ chmod 750 ${escapeShellArg cfg.dataDir}
+ chown -R meguca:meguca ${escapeShellArg cfg.dataDir}
+
+ # Ensure the database is correct or create it
+ ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createuser \
+ -SDR meguca || true
+ ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createdb \
+ -T template0 -E UTF8 -O meguca meguca || true
+ ${pkgs.sudo}/bin/sudo -u meguca ${postgres.package}/bin/psql \
+ -c "ALTER ROLE meguca WITH PASSWORD '$(cat ${escapeShellArg cfg.passwordFile})';" || true
+ '';
+
+ script = ''
+ cd ${escapeShellArg cfg.dataDir}
+
+ ${pkgs.meguca}/bin/meguca -d "$(cat ${escapeShellArg cfg.postgresArgsFile})"''
+ + optionalString (cfg.reverseProxy != null) " -R ${cfg.reverseProxy}"
+ + optionalString (cfg.sslCertificate != null) " -S ${cfg.sslCertificate}"
+ + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}"
+ + optionalString (cfg.cacheSize != null) " -c ${toString cfg.cacheSize}"
+ + optionalString (cfg.compressTraffic) " -g"
+ + optionalString (cfg.assumeReverseProxy) " -r"
+ + optionalString (cfg.httpsOnly) " -s" + " start";
+
+ serviceConfig = {
+ PermissionsStartOnly = true;
+ Type = "forking";
+ User = "meguca";
+ Group = "meguca";
+ ExecStop = "${pkgs.meguca}/bin/meguca stop";
+ };
+ };
+
+ users = {
+ groups.meguca.gid = config.ids.gids.meguca;
+
+ users.meguca = {
+ description = "meguca server service user";
+ home = cfg.dataDir;
+ createHome = true;
+ group = "meguca";
+ uid = config.ids.uids.meguca;
+ };
+ };
+ };
+
+ imports = [
+ (mkRenamedOptionModule [ "services" "meguca" "baseDir" ] [ "services" "meguca" "dataDir" ])
+ ];
+
+ meta.maintainers = with maintainers; [ chiiruno ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix b/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix
new file mode 100644
index 00000000000..f9b1a8b6ccc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix
@@ -0,0 +1,132 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.mighttpd2;
+ configFile = pkgs.writeText "mighty-config" cfg.config;
+ routingFile = pkgs.writeText "mighty-routing" cfg.routing;
+in {
+ options.services.mighttpd2 = {
+ enable = mkEnableOption "Mighttpd2 web server";
+
+ config = mkOption {
+ default = "";
+ example = ''
+ # Example configuration for Mighttpd 2
+ Port: 80
+ # IP address or "*"
+ Host: *
+ Debug_Mode: Yes # Yes or No
+ # If available, "nobody" is much more secure for User:.
+ User: root
+ # If available, "nobody" is much more secure for Group:.
+ Group: root
+ Pid_File: /run/mighty.pid
+ Logging: Yes # Yes or No
+ Log_File: /var/log/mighty # The directory must be writable by User:
+ Log_File_Size: 16777216 # bytes
+ Log_Backup_Number: 10
+ Index_File: index.html
+ Index_Cgi: index.cgi
+ Status_File_Dir: /usr/local/share/mighty/status
+ Connection_Timeout: 30 # seconds
+ Fd_Cache_Duration: 10 # seconds
+ # Server_Name: Mighttpd/3.x.y
+ Tls_Port: 443
+ Tls_Cert_File: cert.pem # should change this with an absolute path
+ # should change this with comma-separated absolute paths
+ Tls_Chain_Files: chain.pem
+ # Currently, Tls_Key_File must not be encrypted.
+ Tls_Key_File: privkey.pem # should change this with an absolute path
+ Service: 0 # 0 is HTTP only, 1 is HTTPS only, 2 is both
+ '';
+ type = types.lines;
+ description = ''
+ Verbatim config file to use
+ (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+ '';
+ };
+
+ routing = mkOption {
+ default = "";
+ example = ''
+ # Example routing for Mighttpd 2
+
+ # Domain lists
+ [localhost www.example.com]
+
+ # Entries are looked up in the specified order
+ # All paths must end with "/"
+
+ # A path to CGI scripts should be specified with "=>"
+ /~alice/cgi-bin/ => /home/alice/public_html/cgi-bin/
+
+ # A path to static files should be specified with "->"
+ /~alice/ -> /home/alice/public_html/
+ /cgi-bin/ => /export/cgi-bin/
+
+ # Reverse proxy rules should be specified with ">>"
+ # /path >> host:port/path2
+ # Either "host" or ":port" can be committed, but not both.
+ /app/cal/ >> example.net/calendar/
+ # Yesod app in the same server
+ /app/wiki/ >> 127.0.0.1:3000/
+
+ / -> /export/www/
+ '';
+ type = types.lines;
+ description = ''
+ Verbatim routing file to use
+ (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+ '';
+ };
+
+ cores = mkOption {
+ default = null;
+ type = types.nullOr types.int;
+ description = ''
+ How many cores to use.
+ If null it will be determined automatically
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ assertions =
+ [ { assertion = cfg.routing != "";
+ message = "You need at least one rule in mighttpd2.routing";
+ }
+ ];
+ systemd.services.mighttpd2 = {
+ description = "Mighttpd2 web server";
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.haskellPackages.mighttpd2}/bin/mighty \
+ ${configFile} \
+ ${routingFile} \
+ +RTS -N${optionalString (cfg.cores != null) "${cfg.cores}"}
+ '';
+ Type = "simple";
+ User = "mighttpd2";
+ Group = "mighttpd2";
+ Restart = "on-failure";
+ AmbientCapabilities = "cap_net_bind_service";
+ CapabilityBoundingSet = "cap_net_bind_service";
+ };
+ };
+
+ users.users.mighttpd2 = {
+ group = "mighttpd2";
+ uid = config.ids.uids.mighttpd2;
+ isSystemUser = true;
+ };
+
+ users.groups.mighttpd2.gid = config.ids.gids.mighttpd2;
+ };
+
+ meta.maintainers = with lib.maintainers; [ fgaz ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/minio.nix b/nixpkgs/nixos/modules/services/web-servers/minio.nix
new file mode 100644
index 00000000000..cd123000f00
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/minio.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.minio;
+in
+{
+ meta.maintainers = [ maintainers.bachp ];
+
+ options.services.minio = {
+ enable = mkEnableOption "Minio Object Storage";
+
+ listenAddress = mkOption {
+ default = ":9000";
+ type = types.str;
+ description = "Listen on a specific IP address and port.";
+ };
+
+ dataDir = mkOption {
+ default = "/var/lib/minio/data";
+ type = types.path;
+ description = "The data directory, for storing the objects.";
+ };
+
+ configDir = mkOption {
+ default = "/var/lib/minio/config";
+ type = types.path;
+ description = "The config directory, for the access keys and other settings.";
+ };
+
+ accessKey = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Access key of 5 to 20 characters in length that clients use to access the server.
+ This overrides the access key that is generated by minio on first startup and stored inside the
+ <literal>configDir</literal> directory.
+ '';
+ };
+
+ secretKey = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ Specify the Secret key of 8 to 40 characters in length that clients use to access the server.
+ This overrides the secret key that is generated by minio on first startup and stored inside the
+ <literal>configDir</literal> directory.
+ '';
+ };
+
+ region = mkOption {
+ default = "us-east-1";
+ type = types.str;
+ description = ''
+ The physical location of the server. By default it is set to us-east-1, which is same as AWS S3's and Minio's default region.
+ '';
+ };
+
+ browser = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Enable or disable access to web UI.";
+ };
+
+ package = mkOption {
+ default = pkgs.minio;
+ defaultText = "pkgs.minio";
+ type = types.package;
+ description = "Minio package to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.configDir}' - minio minio - -"
+ "d '${cfg.dataDir}' - minio minio - -"
+ ];
+
+ systemd.services.minio = {
+ description = "Minio Object Storage";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --config-dir=${cfg.configDir} ${cfg.dataDir}";
+ Type = "simple";
+ User = "minio";
+ Group = "minio";
+ LimitNOFILE = 65536;
+ };
+ environment = {
+ MINIO_REGION = "${cfg.region}";
+ MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
+ } // optionalAttrs (cfg.accessKey != "") {
+ MINIO_ACCESS_KEY = "${cfg.accessKey}";
+ } // optionalAttrs (cfg.secretKey != "") {
+ MINIO_SECRET_KEY = "${cfg.secretKey}";
+ };
+ };
+
+ users.users.minio = {
+ group = "minio";
+ uid = config.ids.uids.minio;
+ };
+
+ users.groups.minio.gid = config.ids.uids.minio;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
new file mode 100644
index 00000000000..e597f34700a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
@@ -0,0 +1,709 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nginx;
+ certs = config.security.acme.certs;
+ vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
+ acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME && vhostConfig.useACMEHost == null) vhostsConfigs;
+ virtualHosts = mapAttrs (vhostName: vhostConfig:
+ let
+ serverName = if vhostConfig.serverName != null
+ then vhostConfig.serverName
+ else vhostName;
+ in
+ vhostConfig // {
+ inherit serverName;
+ } // (optionalAttrs vhostConfig.enableACME {
+ sslCertificate = "${certs.${serverName}.directory}/fullchain.pem";
+ sslCertificateKey = "${certs.${serverName}.directory}/key.pem";
+ sslTrustedCertificate = "${certs.${serverName}.directory}/full.pem";
+ }) // (optionalAttrs (vhostConfig.useACMEHost != null) {
+ sslCertificate = "${certs.${vhostConfig.useACMEHost}.directory}/fullchain.pem";
+ sslCertificateKey = "${certs.${vhostConfig.useACMEHost}.directory}/key.pem";
+ sslTrustedCertificate = "${certs.${vhostConfig.useACMEHost}.directory}/fullchain.pem";
+ })
+ ) cfg.virtualHosts;
+ enableIPv6 = config.networking.enableIPv6;
+
+ recommendedProxyConfig = pkgs.writeText "nginx-recommended-proxy-headers.conf" ''
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Forwarded-Server $host;
+ proxy_set_header Accept-Encoding "";
+ '';
+
+ upstreamConfig = toString (flip mapAttrsToList cfg.upstreams (name: upstream: ''
+ upstream ${name} {
+ ${toString (flip mapAttrsToList upstream.servers (name: server: ''
+ server ${name} ${optionalString server.backup "backup"};
+ ''))}
+ ${upstream.extraConfig}
+ }
+ ''));
+
+ configFile = pkgs.writers.writeNginxConfig "nginx.conf" ''
+ user ${cfg.user} ${cfg.group};
+ error_log ${cfg.logError};
+ daemon off;
+
+ ${cfg.config}
+
+ ${optionalString (cfg.eventsConfig != "" || cfg.config == "") ''
+ events {
+ ${cfg.eventsConfig}
+ }
+ ''}
+
+ ${optionalString (cfg.httpConfig == "" && cfg.config == "") ''
+ http {
+ include ${cfg.package}/conf/mime.types;
+ include ${cfg.package}/conf/fastcgi.conf;
+ include ${cfg.package}/conf/uwsgi_params;
+
+ ${optionalString (cfg.resolver.addresses != []) ''
+ resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"} ${optionalString (!cfg.resolver.ipv6) "ipv6=off"};
+ ''}
+ ${upstreamConfig}
+
+ ${optionalString (cfg.recommendedOptimisation) ''
+ # optimisation
+ sendfile on;
+ tcp_nopush on;
+ tcp_nodelay on;
+ keepalive_timeout 65;
+ types_hash_max_size 2048;
+ ''}
+
+ ssl_protocols ${cfg.sslProtocols};
+ ssl_ciphers ${cfg.sslCiphers};
+ ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"}
+
+ ${optionalString (cfg.recommendedTlsSettings) ''
+ ssl_session_cache shared:SSL:42m;
+ ssl_session_timeout 23m;
+ ssl_ecdh_curve secp384r1;
+ ssl_prefer_server_ciphers on;
+ ssl_stapling on;
+ ssl_stapling_verify on;
+ ''}
+
+ ${optionalString (cfg.recommendedGzipSettings) ''
+ gzip on;
+ gzip_proxied any;
+ gzip_comp_level 5;
+ gzip_types
+ application/atom+xml
+ application/javascript
+ application/json
+ application/xml
+ application/xml+rss
+ image/svg+xml
+ text/css
+ text/javascript
+ text/plain
+ text/xml;
+ gzip_vary on;
+ ''}
+
+ ${optionalString (cfg.recommendedProxySettings) ''
+ proxy_redirect off;
+ proxy_connect_timeout 90;
+ proxy_send_timeout 90;
+ proxy_read_timeout 90;
+ proxy_http_version 1.0;
+ include ${recommendedProxyConfig};
+ ''}
+
+ # $connection_upgrade is used for websocket proxying
+ map $http_upgrade $connection_upgrade {
+ default upgrade;
+ ''' close;
+ }
+ client_max_body_size ${cfg.clientMaxBodySize};
+
+ server_tokens ${if cfg.serverTokens then "on" else "off"};
+
+ ${cfg.commonHttpConfig}
+
+ ${vhosts}
+
+ ${optionalString cfg.statusPage ''
+ server {
+ listen 80;
+ ${optionalString enableIPv6 "listen [::]:80;" }
+
+ server_name localhost;
+
+ location /nginx_status {
+ stub_status on;
+ access_log off;
+ allow 127.0.0.1;
+ ${optionalString enableIPv6 "allow ::1;"}
+ deny all;
+ }
+ }
+ ''}
+
+ ${cfg.appendHttpConfig}
+ }''}
+
+ ${optionalString (cfg.httpConfig != "") ''
+ http {
+ include ${cfg.package}/conf/mime.types;
+ include ${cfg.package}/conf/fastcgi.conf;
+ include ${cfg.package}/conf/uwsgi_params;
+ ${cfg.httpConfig}
+ }''}
+
+ ${cfg.appendConfig}
+ '';
+
+ configPath = if cfg.enableReload
+ then "/etc/nginx/nginx.conf"
+ else configFile;
+
+ vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost:
+ let
+ onlySSL = vhost.onlySSL || vhost.enableSSL;
+ hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL;
+
+ defaultListen =
+ if vhost.listen != [] then vhost.listen
+ else ((optionals hasSSL (
+ singleton { addr = "0.0.0.0"; port = 443; ssl = true; }
+ ++ optional enableIPv6 { addr = "[::]"; port = 443; ssl = true; }
+ )) ++ optionals (!onlySSL) (
+ singleton { addr = "0.0.0.0"; port = 80; ssl = false; }
+ ++ optional enableIPv6 { addr = "[::]"; port = 80; ssl = false; }
+ ));
+
+ hostListen =
+ if vhost.forceSSL
+ then filter (x: x.ssl) defaultListen
+ else defaultListen;
+
+ listenString = { addr, port, ssl, extraParameters ? [], ... }:
+ "listen ${addr}:${toString port} "
+ + optionalString ssl "ssl "
+ + optionalString (ssl && vhost.http2) "http2 "
+ + optionalString vhost.default "default_server "
+ + optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
+ + ";";
+
+ redirectListen = filter (x: !x.ssl) defaultListen;
+
+ acmeLocation = optionalString (vhost.enableACME || vhost.useACMEHost != null) ''
+ location /.well-known/acme-challenge {
+ ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"}
+ root ${vhost.acmeRoot};
+ auth_basic off;
+ }
+ ${optionalString (vhost.acmeFallbackHost != null) ''
+ location @acme-fallback {
+ auth_basic off;
+ proxy_pass http://${vhost.acmeFallbackHost};
+ }
+ ''}
+ '';
+
+ in ''
+ ${optionalString vhost.forceSSL ''
+ server {
+ ${concatMapStringsSep "\n" listenString redirectListen}
+
+ server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
+ ${acmeLocation}
+ location / {
+ return 301 https://$host$request_uri;
+ }
+ }
+ ''}
+
+ server {
+ ${concatMapStringsSep "\n" listenString hostListen}
+ server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
+ ${acmeLocation}
+ ${optionalString (vhost.root != null) "root ${vhost.root};"}
+ ${optionalString (vhost.globalRedirect != null) ''
+ return 301 http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
+ ''}
+ ${optionalString hasSSL ''
+ ssl_certificate ${vhost.sslCertificate};
+ ssl_certificate_key ${vhost.sslCertificateKey};
+ ''}
+ ${optionalString (hasSSL && vhost.sslTrustedCertificate != null) ''
+ ssl_trusted_certificate ${vhost.sslTrustedCertificate};
+ ''}
+
+ ${optionalString (vhost.basicAuthFile != null || vhost.basicAuth != {}) ''
+ auth_basic secured;
+ auth_basic_user_file ${if vhost.basicAuthFile != null then vhost.basicAuthFile else mkHtpasswd vhostName vhost.basicAuth};
+ ''}
+
+ ${mkLocations vhost.locations}
+
+ ${vhost.extraConfig}
+ }
+ ''
+ ) virtualHosts);
+ mkLocations = locations: concatStringsSep "\n" (map (config: ''
+ location ${config.location} {
+ ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning)
+ "proxy_pass ${config.proxyPass};"
+ }
+ ${optionalString (config.proxyPass != null && cfg.proxyResolveWhileRunning) ''
+ set $nix_proxy_target "${config.proxyPass}";
+ proxy_pass $nix_proxy_target;
+ ''}
+ ${optionalString config.proxyWebsockets ''
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+ ''}
+ ${optionalString (config.index != null) "index ${config.index};"}
+ ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"}
+ ${optionalString (config.root != null) "root ${config.root};"}
+ ${optionalString (config.alias != null) "alias ${config.alias};"}
+ ${optionalString (config.return != null) "return ${config.return};"}
+ ${config.extraConfig}
+ ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"}
+ }
+ '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
+ mkHtpasswd = vhostName: authDef: pkgs.writeText "${vhostName}.htpasswd" (
+ concatStringsSep "\n" (mapAttrsToList (user: password: ''
+ ${user}:{PLAIN}${password}
+ '') authDef)
+ );
+in
+
+{
+ options = {
+ services.nginx = {
+ enable = mkEnableOption "Nginx Web Server";
+
+ statusPage = mkOption {
+ default = false;
+ type = types.bool;
+ description = "
+ Enable status page reachable from localhost on http://127.0.0.1/nginx_status.
+ ";
+ };
+
+ recommendedTlsSettings = mkOption {
+ default = false;
+ type = types.bool;
+ description = "
+ Enable recommended TLS settings.
+ ";
+ };
+
+ recommendedOptimisation = mkOption {
+ default = false;
+ type = types.bool;
+ description = "
+ Enable recommended optimisation settings.
+ ";
+ };
+
+ recommendedGzipSettings = mkOption {
+ default = false;
+ type = types.bool;
+ description = "
+ Enable recommended gzip settings.
+ ";
+ };
+
+ recommendedProxySettings = mkOption {
+ default = false;
+ type = types.bool;
+ description = "
+ Enable recommended proxy settings.
+ ";
+ };
+
+ package = mkOption {
+ default = pkgs.nginxStable;
+ defaultText = "pkgs.nginxStable";
+ type = types.package;
+ description = "
+ Nginx package to use. This defaults to the stable version. Note
+ that the nginx team recommends to use the mainline version which
+ available in nixpkgs as <literal>nginxMainline</literal>.
+ ";
+ };
+
+ logError = mkOption {
+ default = "stderr";
+ description = "
+ Configures logging.
+ The first parameter defines a file that will store the log. The
+ special value stderr selects the standard error file. Logging to
+ syslog can be configured by specifying the “syslog:” prefix.
+ The second parameter determines the level of logging, and can be
+ one of the following: debug, info, notice, warn, error, crit,
+ alert, or emerg. Log levels above are listed in the order of
+ increasing severity. Setting a certain log level will cause all
+ messages of the specified and more severe log levels to be logged.
+ If this parameter is omitted then error is used.
+ ";
+ };
+
+ preStart = mkOption {
+ type = types.lines;
+ default = ''
+ 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 = "
+ Verbatim nginx.conf configuration.
+ This is mutually exclusive with the structured configuration
+ via virtualHosts and the recommendedXyzSettings configuration
+ options. See appendConfig for appending to the generated http block.
+ ";
+ };
+
+ appendConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Configuration lines appended to the generated Nginx
+ configuration file. Commonly used by different modules
+ providing http snippets. <option>appendConfig</option>
+ can be specified more than once and it's value will be
+ concatenated (contrary to <option>config</option> which
+ can be set only once).
+ '';
+ };
+
+ commonHttpConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ resolver 127.0.0.1 valid=5s;
+
+ log_format myformat '$remote_addr - $remote_user [$time_local] '
+ '"$request" $status $body_bytes_sent '
+ '"$http_referer" "$http_user_agent"';
+ '';
+ description = ''
+ With nginx you must provide common http context definitions before
+ they are used, e.g. log_format, resolver, etc. inside of server
+ or location contexts. Use this attribute to set these definitions
+ at the appropriate location.
+ '';
+ };
+
+ httpConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "
+ Configuration lines to be set inside the http block.
+ This is mutually exclusive with the structured configuration
+ via virtualHosts and the recommendedXyzSettings configuration
+ options. See appendHttpConfig for appending to the generated http block.
+ ";
+ };
+
+ eventsConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Configuration lines to be set inside the events block.
+ '';
+ };
+
+ appendHttpConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "
+ Configuration lines to be appended to the generated http block.
+ This is mutually exclusive with using config and httpConfig for
+ specifying the whole http block verbatim.
+ ";
+ };
+
+ enableReload = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Reload nginx when configuration file changes (instead of restart).
+ The configuration file is exposed at <filename>/etc/nginx/nginx.conf</filename>.
+ See also <literal>systemd.services.*.restartIfChanged</literal>.
+ '';
+ };
+
+ stateDir = mkOption {
+ default = "/var/spool/nginx";
+ description = "
+ Directory holding all state for nginx to run.
+ ";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "nginx";
+ description = "User account under which nginx runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "nginx";
+ description = "Group account under which nginx runs.";
+ };
+
+ serverTokens = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Show nginx version in headers and error pages.";
+ };
+
+ clientMaxBodySize = mkOption {
+ type = types.str;
+ default = "10m";
+ description = "Set nginx global client_max_body_size.";
+ };
+
+ sslCiphers = mkOption {
+ type = types.str;
+ default = "EECDH+aRSA+AESGCM:EDH+aRSA:EECDH+aRSA:+AES256:+AES128:+SHA1:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL";
+ description = "Ciphers to choose from when negotiating tls handshakes.";
+ };
+
+ sslProtocols = mkOption {
+ type = types.str;
+ default = "TLSv1.2 TLSv1.3";
+ example = "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3";
+ description = "Allowed TLS protocol versions.";
+ };
+
+ sslDhparam = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/path/to/dhparams.pem";
+ description = "Path to DH parameters file.";
+ };
+
+ proxyResolveWhileRunning = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Resolves domains of proxyPass targets at runtime
+ and not only at start, you have to set
+ services.nginx.resolver, too.
+ '';
+ };
+
+ resolver = mkOption {
+ type = types.submodule {
+ options = {
+ addresses = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = literalExample ''[ "[::1]" "127.0.0.1:5353" ]'';
+ description = "List of resolvers to use";
+ };
+ valid = mkOption {
+ type = types.str;
+ default = "";
+ example = "30s";
+ description = ''
+ By default, nginx caches answers using the TTL value of a response.
+ An optional valid parameter allows overriding it
+ '';
+ };
+ ipv6 = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ By default, nginx will look up both IPv4 and IPv6 addresses while resolving.
+ If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be
+ specified.
+ '';
+ };
+ };
+ };
+ description = ''
+ Configures name servers used to resolve names of upstream servers into addresses
+ '';
+ default = {};
+ };
+
+ upstreams = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ servers = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ backup = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Marks the server as a backup server. It will be passed
+ requests when the primary servers are unavailable.
+ '';
+ };
+ };
+ });
+ description = ''
+ Defines the address and other parameters of the upstream servers.
+ '';
+ default = {};
+ };
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ These lines go to the end of the upstream verbatim.
+ '';
+ };
+ };
+ });
+ description = ''
+ Defines a group of servers to use as proxy target.
+ '';
+ default = {};
+ };
+
+ virtualHosts = mkOption {
+ type = types.attrsOf (types.submodule (import ./vhost-options.nix {
+ inherit config lib;
+ }));
+ default = {
+ localhost = {};
+ };
+ example = literalExample ''
+ {
+ "hydra.example.com" = {
+ forceSSL = true;
+ enableACME = true;
+ locations."/" = {
+ proxyPass = "http://localhost:3000";
+ };
+ };
+ };
+ '';
+ description = "Declarative vhost config";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # TODO: test user supplied config file pases syntax test
+
+ warnings =
+ let
+ deprecatedSSL = name: config: optional config.enableSSL
+ ''
+ config.services.nginx.virtualHosts.<name>.enableSSL is deprecated,
+ use config.services.nginx.virtualHosts.<name>.onlySSL instead.
+ '';
+
+ in flatten (mapAttrsToList deprecatedSSL virtualHosts);
+
+ assertions =
+ let
+ hostOrAliasIsNull = l: l.root == null || l.alias == null;
+ in [
+ {
+ assertion = all (host: all hostOrAliasIsNull (attrValues host.locations)) (attrValues virtualHosts);
+ message = "Only one of nginx root or alias can be specified on a location.";
+ }
+
+ {
+ assertion = all (conf: with conf;
+ !(addSSL && (onlySSL || enableSSL)) &&
+ !(forceSSL && (onlySSL || enableSSL)) &&
+ !(addSSL && forceSSL)
+ ) (attrValues virtualHosts);
+ message = ''
+ Options services.nginx.service.virtualHosts.<name>.addSSL,
+ services.nginx.virtualHosts.<name>.onlySSL and services.nginx.virtualHosts.<name>.forceSSL
+ are mutually exclusive.
+ '';
+ }
+
+ {
+ assertion = all (conf: !(conf.enableACME && conf.useACMEHost != null)) (attrValues virtualHosts);
+ message = ''
+ Options services.nginx.service.virtualHosts.<name>.enableACME and
+ services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive.
+ '';
+ }
+ ];
+
+ systemd.services.nginx = {
+ description = "Nginx Web Server";
+ wantedBy = [ "multi-user.target" ];
+ wants = concatLists (map (vhostConfig: ["acme-${vhostConfig.serverName}.service" "acme-selfsigned-${vhostConfig.serverName}.service"]) acmeEnabledVhosts);
+ after = [ "network.target" ] ++ map (vhostConfig: "acme-selfsigned-${vhostConfig.serverName}.service") acmeEnabledVhosts;
+ stopIfChanged = false;
+ preStart =
+ ''
+ ${cfg.preStart}
+ ${cfg.package}/bin/nginx -c ${configPath} -p ${cfg.stateDir} -t
+ '';
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/nginx -c ${configPath} -p ${cfg.stateDir}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ Restart = "always";
+ RestartSec = "10s";
+ StartLimitInterval = "1min";
+ };
+ };
+
+ environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
+ source = configFile;
+ };
+
+ systemd.services.nginx-config-reload = mkIf cfg.enableReload {
+ wantedBy = [ "nginx.service" ];
+ restartTriggers = [ configFile ];
+ script = ''
+ if ${pkgs.systemd}/bin/systemctl -q is-active nginx.service ; then
+ ${pkgs.systemd}/bin/systemctl reload nginx.service
+ fi
+ '';
+ serviceConfig.RemainAfterExit = true;
+ };
+
+ security.acme.certs = filterAttrs (n: v: v != {}) (
+ let
+ acmePairs = map (vhostConfig: { name = vhostConfig.serverName; value = {
+ user = cfg.user;
+ group = lib.mkDefault cfg.group;
+ webroot = vhostConfig.acmeRoot;
+ extraDomains = genAttrs vhostConfig.serverAliases (alias: null);
+ postRun = ''
+ systemctl reload nginx
+ '';
+ }; }) acmeEnabledVhosts;
+ in
+ listToAttrs acmePairs
+ );
+
+ users.users = optionalAttrs (cfg.user == "nginx") (singleton
+ { name = "nginx";
+ group = cfg.group;
+ uid = config.ids.uids.nginx;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "nginx") (singleton
+ { name = "nginx";
+ gid = config.ids.gids.nginx;
+ });
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix
new file mode 100644
index 00000000000..272fd148018
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gitweb;
+ package = pkgs.gitweb.override (optionalAttrs cfg.gitwebTheme {
+ gitwebTheme = true;
+ });
+
+in
+{
+
+ options.services.nginx.gitweb = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If true, enable gitweb in nginx. Access it at http://yourserver/gitweb
+ '';
+ };
+
+ };
+
+ config = mkIf config.services.nginx.gitweb.enable {
+
+ systemd.services.gitweb = {
+ description = "GitWeb service";
+ script = "${package}/gitweb.cgi --fastcgi --nproc=1";
+ environment = {
+ FCGI_SOCKET_PATH = "/run/gitweb/gitweb.sock";
+ };
+ serviceConfig = {
+ User = "nginx";
+ Group = "nginx";
+ RuntimeDirectory = [ "gitweb" ];
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ services.nginx = {
+ virtualHosts.default = {
+ locations."/gitweb/static/" = {
+ alias = "${package}/static/";
+ };
+ locations."/gitweb/" = {
+ extraConfig = ''
+ include ${pkgs.nginx}/conf/fastcgi_params;
+ fastcgi_param GITWEB_CONFIG ${cfg.gitwebConfigFile};
+ fastcgi_pass unix:/run/gitweb/gitweb.sock;
+ '';
+ };
+ };
+ };
+
+ };
+
+ meta.maintainers = with maintainers; [ gnidorah ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
new file mode 100644
index 00000000000..aeb9b1dd79e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -0,0 +1,95 @@
+# This file defines the options that can be used both for the Apache
+# main server configuration, and for the virtual hosts. (The latter
+# has additional options that affect the web server as a whole, like
+# the user/group to run under.)
+
+{ lib }:
+
+with lib;
+
+{
+ options = {
+ proxyPass = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "http://www.example.org/";
+ description = ''
+ Adds proxy_pass directive and sets recommended proxy headers if
+ recommendedProxySettings is enabled.
+ '';
+ };
+
+ proxyWebsockets = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Whether to supporty proxying websocket connections with HTTP/1.1.
+ '';
+ };
+
+ index = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "index.php index.html";
+ description = ''
+ Adds index directive.
+ '';
+ };
+
+ tryFiles = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "$uri =404";
+ description = ''
+ Adds try_files directive.
+ '';
+ };
+
+ root = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/your/root/directory";
+ description = ''
+ Root directory for requests.
+ '';
+ };
+
+ alias = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/your/alias/directory";
+ description = ''
+ Alias directory for requests.
+ '';
+ };
+
+ return = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "301 http://example.com$request_uri;";
+ description = ''
+ Adds a return directive, for e.g. redirections.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ These lines go to the end of the location verbatim.
+ '';
+ };
+
+ priority = mkOption {
+ type = types.int;
+ default = 1000;
+ description = ''
+ Order of this location block in relation to the others in the vhost.
+ The semantics are the same as with `lib.mkOrder`. Smaller values have
+ a greater priority.
+ '';
+ };
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
new file mode 100644
index 00000000000..15b933c984a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -0,0 +1,228 @@
+# This file defines the options that can be used both for the Apache
+# main server configuration, and for the virtual hosts. (The latter
+# has additional options that affect the web server as a whole, like
+# the user/group to run under.)
+
+{ lib, ... }:
+
+with lib;
+{
+ options = {
+ serverName = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Name of this virtual host. Defaults to attribute name in virtualHosts.
+ '';
+ example = "example.org";
+ };
+
+ serverAliases = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ["www.example.org" "example.org"];
+ description = ''
+ Additional names of virtual hosts served by this virtual host configuration.
+ '';
+ };
+
+ listen = mkOption {
+ type = with types; listOf (submodule { options = {
+ addr = mkOption { type = str; description = "IP address."; };
+ port = mkOption { type = int; description = "Port number."; default = 80; };
+ ssl = mkOption { type = bool; description = "Enable SSL."; default = false; };
+ extraParameters = mkOption { type = listOf str; description = "Extra parameters of this listen directive."; default = []; example = [ "reuseport" "deferred" ]; };
+ }; });
+ default = [];
+ example = [
+ { addr = "195.154.1.1"; port = 443; ssl = true;}
+ { addr = "192.154.1.1"; port = 80; }
+ ];
+ description = ''
+ Listen addresses and ports for this virtual host.
+ IPv6 addresses must be enclosed in square brackets.
+ Note: this option overrides <literal>addSSL</literal>
+ and <literal>onlySSL</literal>.
+ '';
+ };
+
+ enableACME = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to ask Let's Encrypt to sign a certificate for this vhost.
+ Alternately, you can use an existing certificate through <option>useACMEHost</option>.
+ '';
+ };
+
+ useACMEHost = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ A host of an existing Let's Encrypt certificate to use.
+ This is useful if you have many subdomains and want to avoid hitting the
+ <link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
+ Alternately, you can generate a certificate through <option>enableACME</option>.
+ <emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using <xref linkend="opt-security.acme.certs"/>.</emphasis>
+ '';
+ };
+
+ acmeRoot = mkOption {
+ type = types.str;
+ default = "/var/lib/acme/acme-challenge";
+ description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here";
+ };
+
+ acmeFallbackHost = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Host which to proxy requests to if acme challenge is not found. Useful
+ if you want multiple hosts to be able to verify the same domain name.
+ '';
+ };
+
+ addSSL = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable HTTPS in addition to plain HTTP. This will set defaults for
+ <literal>listen</literal> to listen on all interfaces on the respective default
+ ports (80, 443).
+ '';
+ };
+
+ onlySSL = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable HTTPS and reject plain HTTP connections. This will set
+ defaults for <literal>listen</literal> to listen on all interfaces on port 443.
+ '';
+ };
+
+ enableSSL = mkOption {
+ type = types.bool;
+ visible = false;
+ default = false;
+ };
+
+ forceSSL = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to add a separate nginx server block that permanently redirects (301)
+ all plain HTTP traffic to HTTPS. This will set defaults for
+ <literal>listen</literal> to listen on all interfaces on the respective default
+ ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
+ '';
+ };
+
+ sslCertificate = mkOption {
+ type = types.path;
+ example = "/var/host.cert";
+ description = "Path to server SSL certificate.";
+ };
+
+ sslCertificateKey = mkOption {
+ type = types.path;
+ example = "/var/host.key";
+ description = "Path to server SSL certificate key.";
+ };
+
+ sslTrustedCertificate = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/var/root.cert";
+ description = "Path to root SSL certificate for stapling and client certificates.";
+ };
+
+ http2 = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable HTTP 2.
+ Note that (as of writing) due to nginx's implementation, to disable
+ HTTP 2 you have to disable it on all vhosts that use a given
+ IP address / port.
+ If there is one server block configured to enable http2,then it is
+ enabled for all server blocks on this IP.
+ See https://stackoverflow.com/a/39466948/263061.
+ '';
+ };
+
+ root = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/data/webserver/docs";
+ description = ''
+ The path of the web root directory.
+ '';
+ };
+
+ default = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Makes this vhost the default.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ These lines go to the end of the vhost verbatim.
+ '';
+ };
+
+ globalRedirect = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "newserver.example.org";
+ description = ''
+ If set, all requests for this host are redirected permanently to
+ the given hostname.
+ '';
+ };
+
+ basicAuth = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = literalExample ''
+ {
+ user = "password";
+ };
+ '';
+ description = ''
+ Basic Auth protection for a vhost.
+
+ WARNING: This is implemented to store the password in plain text in the
+ nix store.
+ '';
+ };
+
+ basicAuthFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Basic Auth password file for a vhost.
+ '';
+ };
+
+ locations = mkOption {
+ type = types.attrsOf (types.submodule (import ./location-options.nix {
+ inherit lib;
+ }));
+ default = {};
+ example = literalExample ''
+ {
+ "/" = {
+ proxyPass = "http://localhost:3000";
+ };
+ };
+ '';
+ description = "Declarative location config";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix
new file mode 100644
index 00000000000..4ab7e3f0c0a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -0,0 +1,279 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.phpfpm;
+
+ runtimeDir = "/run/phpfpm";
+
+ toStr = value:
+ if true == value then "yes"
+ else if false == value then "no"
+ else toString value;
+
+ fpmCfgFile = pool: poolOpts: pkgs.writeText "phpfpm-${pool}.conf" ''
+ [global]
+ ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)}
+ ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
+
+ [${pool}]
+ ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") poolOpts.settings)}
+ ${concatStringsSep "\n" (mapAttrsToList (n: v: "env[${n}] = ${toStr v}") poolOpts.phpEnv)}
+ ${optionalString (poolOpts.extraConfig != null) poolOpts.extraConfig}
+ '';
+
+ phpIni = poolOpts: pkgs.runCommand "php.ini" {
+ inherit (poolOpts) phpPackage phpOptions;
+ preferLocalBuild = true;
+ nixDefaults = ''
+ sendmail_path = "/run/wrappers/bin/sendmail -t -i"
+ '';
+ passAsFile = [ "nixDefaults" "phpOptions" ];
+ } ''
+ cat $phpPackage/etc/php.ini $nixDefaultsPath $phpOptionsPath > $out
+ '';
+
+ poolOpts = { name, ... }:
+ let
+ poolOpts = cfg.pools.${name};
+ in
+ {
+ options = {
+ socket = mkOption {
+ type = types.str;
+ readOnly = true;
+ description = ''
+ Path to the unix socket file on which to accept FastCGI requests.
+ <note><para>This option is read-only and managed by NixOS.</para></note>
+ '';
+ };
+
+ listen = mkOption {
+ type = types.str;
+ default = "";
+ example = "/path/to/unix/socket";
+ description = ''
+ The address on which to accept FastCGI requests.
+ '';
+ };
+
+ phpPackage = mkOption {
+ type = types.package;
+ default = cfg.phpPackage;
+ defaultText = "config.services.phpfpm.phpPackage";
+ description = ''
+ The PHP package to use for running this PHP-FPM pool.
+ '';
+ };
+
+ phpOptions = mkOption {
+ type = types.lines;
+ default = cfg.phpOptions;
+ defaultText = "config.services.phpfpm.phpOptions";
+ description = ''
+ "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
+ '';
+ };
+
+ phpEnv = lib.mkOption {
+ type = with types; attrsOf str;
+ default = {};
+ description = ''
+ Environment variables used for this PHP-FPM pool.
+ '';
+ example = literalExample ''
+ {
+ HOSTNAME = "$HOSTNAME";
+ TMP = "/tmp";
+ TMPDIR = "/tmp";
+ TEMP = "/tmp";
+ }
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ description = "User account under which this pool runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ description = "Group account under which this pool runs.";
+ };
+
+ settings = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool ]);
+ default = {};
+ description = ''
+ PHP-FPM pool directives. Refer to the "List of pool directives" section of
+ <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
+ for details. Note that settings names must be enclosed in quotes (e.g.
+ <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
+ '';
+ example = literalExample ''
+ {
+ "pm" = "dynamic";
+ "pm.max_children" = 75;
+ "pm.start_servers" = 10;
+ "pm.min_spare_servers" = 5;
+ "pm.max_spare_servers" = 20;
+ "pm.max_requests" = 500;
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = with types; nullOr lines;
+ default = null;
+ description = ''
+ Extra lines that go into the pool configuration.
+ See the documentation on <literal>php-fpm.conf</literal> for
+ details on configuration directives.
+ '';
+ };
+ };
+
+ config = {
+ socket = if poolOpts.listen == "" then "${runtimeDir}/${name}.sock" else poolOpts.listen;
+ group = mkDefault poolOpts.user;
+
+ settings = mapAttrs (name: mkDefault){
+ listen = poolOpts.socket;
+ user = poolOpts.user;
+ group = poolOpts.group;
+ };
+ };
+ };
+
+in {
+
+ options = {
+ services.phpfpm = {
+ settings = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool ]);
+ default = {};
+ description = ''
+ PHP-FPM global directives. Refer to the "List of global php-fpm.conf directives" section of
+ <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
+ for details. Note that settings names must be enclosed in quotes (e.g.
+ <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
+ You need not specify the options <literal>error_log</literal> or
+ <literal>daemonize</literal> here, since they are generated by NixOS.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = with types; nullOr lines;
+ default = null;
+ description = ''
+ Extra configuration that should be put in the global section of
+ the PHP-FPM configuration file. Do not specify the options
+ <literal>error_log</literal> or
+ <literal>daemonize</literal> here, since they are generated by
+ NixOS.
+ '';
+ };
+
+ phpPackage = mkOption {
+ type = types.package;
+ default = pkgs.php;
+ defaultText = "pkgs.php";
+ description = ''
+ The PHP package to use for running the PHP-FPM service.
+ '';
+ };
+
+ phpOptions = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ date.timezone = "CET"
+ '';
+ description = ''
+ Options appended to the PHP configuration file <filename>php.ini</filename>.
+ '';
+ };
+
+ pools = mkOption {
+ type = types.attrsOf (types.submodule poolOpts);
+ default = {};
+ example = literalExample ''
+ {
+ mypool = {
+ user = "php";
+ group = "php";
+ phpPackage = pkgs.php;
+ settings = '''
+ "pm" = "dynamic";
+ "pm.max_children" = 75;
+ "pm.start_servers" = 10;
+ "pm.min_spare_servers" = 5;
+ "pm.max_spare_servers" = 20;
+ "pm.max_requests" = 500;
+ ''';
+ }
+ }'';
+ description = ''
+ PHP-FPM pools. If no pools are defined, the PHP-FPM
+ service is disabled.
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.pools != {}) {
+
+ warnings =
+ mapAttrsToList (pool: poolOpts: ''
+ Using config.services.phpfpm.pools.${pool}.listen is deprecated and will become unsupported in a future release. Please reference the read-only option config.services.phpfpm.pools.${pool}.socket to access the path of your socket.
+ '') (filterAttrs (pool: poolOpts: poolOpts.listen != "") cfg.pools) ++
+ mapAttrsToList (pool: poolOpts: ''
+ Using config.services.phpfpm.pools.${pool}.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.pools.${pool}.settings.
+ '') (filterAttrs (pool: poolOpts: poolOpts.extraConfig != null) cfg.pools) ++
+ optional (cfg.extraConfig != null) ''
+ Using config.services.phpfpm.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.settings.
+ ''
+ ;
+
+ services.phpfpm.settings = {
+ error_log = "syslog";
+ daemonize = false;
+ };
+
+ systemd.slices.phpfpm = {
+ description = "PHP FastCGI Process manager pools slice";
+ };
+
+ systemd.targets.phpfpm = {
+ description = "PHP FastCGI Process manager pools target";
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ systemd.services = mapAttrs' (pool: poolOpts:
+ nameValuePair "phpfpm-${pool}" {
+ description = "PHP FastCGI Process Manager service for pool ${pool}";
+ after = [ "network.target" ];
+ wantedBy = [ "phpfpm.target" ];
+ partOf = [ "phpfpm.target" ];
+ serviceConfig = let
+ cfgFile = fpmCfgFile pool poolOpts;
+ iniFile = phpIni poolOpts;
+ in {
+ Slice = "phpfpm.slice";
+ PrivateDevices = true;
+ ProtectSystem = "full";
+ ProtectHome = true;
+ # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work
+ RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+ Type = "notify";
+ ExecStart = "${poolOpts.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
+ RuntimeDirectory = "phpfpm";
+ RuntimeDirectoryPreserve = true; # Relevant when multiple processes are running
+ };
+ }
+ ) cfg.pools;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix b/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix
new file mode 100644
index 00000000000..58a02ac59c3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.shellinabox;
+
+ # If a certificate file is specified, shellinaboxd requires
+ # a file descriptor to retrieve it
+ fd = "3";
+ createFd = optionalString (cfg.certFile != null) "${fd}<${cfg.certFile}";
+
+ # Command line arguments for the shellinabox daemon
+ args = [ "--background" ]
+ ++ optional (! cfg.enableSSL) "--disable-ssl"
+ ++ optional (cfg.certFile != null) "--cert-fd=${fd}"
+ ++ optional (cfg.certDirectory != null) "--cert=${cfg.certDirectory}"
+ ++ cfg.extraOptions;
+
+ # Command to start shellinaboxd
+ cmd = "${pkgs.shellinabox}/bin/shellinaboxd ${concatStringsSep " " args}";
+
+ # Command to start shellinaboxd if certFile is specified
+ wrappedCmd = "${pkgs.bash}/bin/bash -c 'exec ${createFd} && ${cmd}'";
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.shellinabox = {
+ enable = mkEnableOption "shellinabox daemon";
+
+ user = mkOption {
+ type = types.str;
+ default = "root";
+ description = ''
+ User to run shellinaboxd as. If started as root, the server drops
+ privileges by changing to nobody, unless overridden by the
+ <literal>--user</literal> option.
+ '';
+ };
+
+ enableSSL = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether or not to enable SSL (https) support.
+ '';
+ };
+
+ certDirectory = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/var/certs";
+ description = ''
+ The daemon will look in this directory far any certificates.
+ If the browser negotiated a Server Name Identification the daemon
+ will look for a matching certificate-SERVERNAME.pem file. If no SNI
+ handshake takes place, it will fall back on using the certificate in the
+ certificate.pem file.
+
+ If no suitable certificate is installed, shellinaboxd will attempt to
+ create a new self-signed certificate. This will only succeed if, after
+ dropping privileges, shellinaboxd has write permissions for this
+ directory.
+ '';
+ };
+
+ certFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/var/certificate.pem";
+ description = "Path to server SSL certificate.";
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--port=443" "--service /:LOGIN" ];
+ description = ''
+ A list of strings to be appended to the command line arguments
+ for shellinaboxd. Please see the manual page
+ <link xlink:href="https://code.google.com/p/shellinabox/wiki/shellinaboxd_man"/>
+ for a full list of available arguments.
+ '';
+ };
+
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions =
+ [ { assertion = cfg.enableSSL == true
+ -> cfg.certDirectory != null || cfg.certFile != null;
+ message = "SSL is enabled for shellinabox, but no certDirectory or certFile has been specefied."; }
+ { assertion = ! (cfg.certDirectory != null && cfg.certFile != null);
+ message = "Cannot set both certDirectory and certFile for shellinabox."; }
+ ];
+
+ systemd.services.shellinaboxd = {
+ description = "Shellinabox Web Server Daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "sshd.service" ];
+ after = [ "sshd.service" ];
+
+ serviceConfig = {
+ Type = "forking";
+ User = "${cfg.user}";
+ ExecStart = "${if cfg.certFile == null then "${cmd}" else "${wrappedCmd}"}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/tomcat.nix b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix
new file mode 100644
index 00000000000..68261c50324
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix
@@ -0,0 +1,425 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.tomcat;
+ tomcat = cfg.package;
+in
+
+{
+
+ meta = {
+ maintainers = with maintainers; [ danbst ];
+ };
+
+ ###### interface
+
+ options = {
+
+ services.tomcat = {
+ enable = mkEnableOption "Apache Tomcat";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.tomcat85;
+ defaultText = "pkgs.tomcat85";
+ example = lib.literalExample "pkgs.tomcat9";
+ description = ''
+ Which tomcat package to use.
+ '';
+ };
+
+ purifyOnStart = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ On startup, the `baseDir` directory is populated with various files,
+ subdirectories and symlinks. If this option is enabled, these items
+ (except for the `logs` and `work` subdirectories) are first removed.
+ This prevents interference from remainders of an old configuration
+ (libraries, webapps, etc.), so it's recommended to enable this option.
+ '';
+ };
+
+ baseDir = mkOption {
+ type = lib.types.path;
+ default = "/var/tomcat";
+ description = ''
+ Location where Tomcat stores configuration files, web applications
+ and logfiles. Note that it is partially cleared on each service startup
+ if `purifyOnStart` is enabled.
+ '';
+ };
+
+ logDirs = mkOption {
+ default = [];
+ type = types.listOf types.path;
+ description = "Directories to create in baseDir/logs/";
+ };
+
+ extraConfigFiles = mkOption {
+ default = [];
+ type = types.listOf types.path;
+ description = "Extra configuration files to pull into the tomcat conf directory";
+ };
+
+ extraEnvironment = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "ENVIRONMENT=production" ];
+ description = "Environment Variables to pass to the tomcat service";
+ };
+
+ extraGroups = mkOption {
+ default = [];
+ example = [ "users" ];
+ description = "Defines extra groups to which the tomcat user belongs.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "tomcat";
+ description = "User account under which Apache Tomcat runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "tomcat";
+ description = "Group account under which Apache Tomcat runs.";
+ };
+
+ javaOpts = mkOption {
+ type = types.either (types.listOf types.str) types.str;
+ default = "";
+ description = "Parameters to pass to the Java Virtual Machine which spawns Apache Tomcat";
+ };
+
+ catalinaOpts = mkOption {
+ type = types.either (types.listOf types.str) types.str;
+ default = "";
+ description = "Parameters to pass to the Java Virtual Machine which spawns the Catalina servlet container";
+ };
+
+ sharedLibs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications";
+ };
+
+ serverXml = mkOption {
+ type = types.lines;
+ default = "";
+ description = "
+ Verbatim server.xml configuration.
+ This is mutually exclusive with the virtualHosts options.
+ ";
+ };
+
+ commonLibs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications and the servlet container";
+ };
+
+ webapps = mkOption {
+ type = types.listOf types.path;
+ default = [ tomcat.webapps ];
+ defaultText = "[ pkgs.tomcat85.webapps ]";
+ description = "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat";
+ };
+
+ virtualHosts = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ name = mkOption {
+ type = types.str;
+ description = "name of the virtualhost";
+ };
+ aliases = mkOption {
+ type = types.listOf types.str;
+ description = "aliases of the virtualhost";
+ default = [];
+ };
+ webapps = mkOption {
+ type = types.listOf types.path;
+ description = ''
+ List containing web application WAR files and/or directories containing
+ web applications and configuration files for the virtual host.
+ '';
+ default = [];
+ };
+ };
+ });
+ default = [];
+ description = "List consisting of a virtual host name and a list of web applications to deploy on each virtual host";
+ };
+
+ logPerVirtualHost = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable logging per virtual host.";
+ };
+
+ jdk = mkOption {
+ type = types.package;
+ default = pkgs.jdk;
+ defaultText = "pkgs.jdk";
+ description = "Which JDK to use.";
+ };
+
+ axis2 = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to enable an Apache Axis2 container";
+ };
+
+ services = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2";
+ };
+
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.tomcat.enable {
+
+ users.groups = singleton
+ { name = "tomcat";
+ gid = config.ids.gids.tomcat;
+ };
+
+ users.users = singleton
+ { name = "tomcat";
+ uid = config.ids.uids.tomcat;
+ description = "Tomcat user";
+ home = "/homeless-shelter";
+ extraGroups = cfg.extraGroups;
+ };
+
+ systemd.services.tomcat = {
+ description = "Apache Tomcat server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ preStart = ''
+ ${lib.optionalString cfg.purifyOnStart ''
+ # Delete most directories/symlinks we create from the existing base directory,
+ # to get rid of remainders of an old configuration.
+ # The list of directories to delete is taken from the "mkdir" command below,
+ # excluding "logs" (because logs are valuable) and "work" (because normally
+ # session files are there), and additionally including "bin".
+ rm -rf ${cfg.baseDir}/{conf,virtualhosts,temp,lib,shared/lib,webapps,bin}
+ ''}
+
+ # Create the base directory
+ mkdir -p \
+ ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work}
+ chown ${cfg.user}:${cfg.group} \
+ ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work}
+
+ # Create a symlink to the bin directory of the tomcat component
+ ln -sfn ${tomcat}/bin ${cfg.baseDir}/bin
+
+ # Symlink the config files in the conf/ directory (except for catalina.properties and server.xml)
+ for i in $(ls ${tomcat}/conf | grep -v catalina.properties | grep -v server.xml); do
+ ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i`
+ done
+
+ ${if cfg.extraConfigFiles != [] then ''
+ for i in ${toString cfg.extraConfigFiles}; do
+ ln -sfn $i ${cfg.baseDir}/conf/`basename $i`
+ done
+ '' else ""}
+
+ # Create a modified catalina.properties file
+ # Change all references from CATALINA_HOME to CATALINA_BASE and add support for shared libraries
+ sed -e 's|''${catalina.home}|''${catalina.base}|g' \
+ -e 's|shared.loader=|shared.loader=''${catalina.base}/shared/lib/*.jar|' \
+ ${tomcat}/conf/catalina.properties > ${cfg.baseDir}/conf/catalina.properties
+
+ ${if cfg.serverXml != "" then ''
+ cp -f ${pkgs.writeTextDir "server.xml" cfg.serverXml}/* ${cfg.baseDir}/conf/
+ '' else
+ let
+ hostElementForVirtualHost = virtualHost: ''
+ <Host name="${virtualHost.name}" appBase="virtualhosts/${virtualHost.name}/webapps"
+ unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
+ '' + concatStrings (innerElementsForVirtualHost virtualHost) + ''
+ </Host>
+ '';
+ innerElementsForVirtualHost = virtualHost:
+ (map (alias: ''
+ <Alias>${alias}</Alias>
+ '') virtualHost.aliases)
+ ++ (optional cfg.logPerVirtualHost ''
+ <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs/${virtualHost.name}"
+ prefix="${virtualHost.name}_access_log." pattern="combined" resolveHosts="false"/>
+ '');
+ hostElementsString = concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts;
+ hostElementsSedString = replaceStrings ["\n"] ["\\\n"] hostElementsString;
+ in ''
+ # Create a modified server.xml which also includes all virtual hosts
+ sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${escapeShellArg hostElementsSedString} \
+ ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
+ ''
+ }
+ ${optionalString (cfg.logDirs != []) ''
+ for i in ${toString cfg.logDirs}; do
+ mkdir -p ${cfg.baseDir}/logs/$i
+ chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/$i
+ done
+ ''}
+ ${optionalString cfg.logPerVirtualHost (toString (map (h: ''
+ mkdir -p ${cfg.baseDir}/logs/${h.name}
+ chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/${h.name}
+ '') cfg.virtualHosts))}
+
+ # Symlink all the given common libs files or paths into the lib/ directory
+ for i in ${tomcat} ${toString cfg.commonLibs}; do
+ if [ -f $i ]; then
+ # If the given web application is a file, symlink it into the common/lib/ directory
+ ln -sfn $i ${cfg.baseDir}/lib/`basename $i`
+ elif [ -d $i ]; then
+ # If the given web application is a directory, then iterate over the files
+ # in the special purpose directories and symlink them into the tomcat tree
+
+ for j in $i/lib/*; do
+ ln -sfn $j ${cfg.baseDir}/lib/`basename $j`
+ done
+ fi
+ done
+
+ # Symlink all the given shared libs files or paths into the shared/lib/ directory
+ for i in ${toString cfg.sharedLibs}; do
+ if [ -f $i ]; then
+ # If the given web application is a file, symlink it into the common/lib/ directory
+ ln -sfn $i ${cfg.baseDir}/shared/lib/`basename $i`
+ elif [ -d $i ]; then
+ # If the given web application is a directory, then iterate over the files
+ # in the special purpose directories and symlink them into the tomcat tree
+
+ for j in $i/shared/lib/*; do
+ ln -sfn $j ${cfg.baseDir}/shared/lib/`basename $j`
+ done
+ fi
+ done
+
+ # Symlink all the given web applications files or paths into the webapps/ directory
+ for i in ${toString cfg.webapps}; do
+ if [ -f $i ]; then
+ # If the given web application is a file, symlink it into the webapps/ directory
+ ln -sfn $i ${cfg.baseDir}/webapps/`basename $i`
+ elif [ -d $i ]; then
+ # If the given web application is a directory, then iterate over the files
+ # in the special purpose directories and symlink them into the tomcat tree
+
+ for j in $i/webapps/*; do
+ ln -sfn $j ${cfg.baseDir}/webapps/`basename $j`
+ done
+
+ # Also symlink the configuration files if they are included
+ if [ -d $i/conf/Catalina ]; then
+ for j in $i/conf/Catalina/*; do
+ mkdir -p ${cfg.baseDir}/conf/Catalina/localhost
+ ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j`
+ done
+ fi
+ fi
+ done
+
+ ${toString (map (virtualHost: ''
+ # Create webapps directory for the virtual host
+ mkdir -p ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps
+
+ # Modify ownership
+ chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps
+
+ # Symlink all the given web applications files or paths into the webapps/ directory
+ # of this virtual host
+ for i in "${if virtualHost ? webapps then toString virtualHost.webapps else ""}"; do
+ if [ -f $i ]; then
+ # If the given web application is a file, symlink it into the webapps/ directory
+ ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i`
+ elif [ -d $i ]; then
+ # If the given web application is a directory, then iterate over the files
+ # in the special purpose directories and symlink them into the tomcat tree
+
+ for j in $i/webapps/*; do
+ ln -sfn $j ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $j`
+ done
+
+ # Also symlink the configuration files if they are included
+ if [ -d $i/conf/Catalina ]; then
+ for j in $i/conf/Catalina/*; do
+ mkdir -p ${cfg.baseDir}/conf/Catalina/${virtualHost.name}
+ ln -sfn $j ${cfg.baseDir}/conf/Catalina/${virtualHost.name}/`basename $j`
+ done
+ fi
+ fi
+ done
+ '') cfg.virtualHosts)}
+
+ ${optionalString cfg.axis2.enable ''
+ # Copy the Axis2 web application
+ cp -av ${pkgs.axis2}/webapps/axis2 ${cfg.baseDir}/webapps
+
+ # Turn off addressing, which causes many errors
+ sed -i -e 's%<module ref="addressing"/>%<!-- <module ref="addressing"/> -->%' ${cfg.baseDir}/webapps/axis2/WEB-INF/conf/axis2.xml
+
+ # Modify permissions on the Axis2 application
+ chown -R ${cfg.user}:${cfg.group} ${cfg.baseDir}/webapps/axis2
+
+ # Symlink all the given web service files or paths into the webapps/axis2/WEB-INF/services directory
+ for i in ${toString cfg.axis2.services}; do
+ if [ -f $i ]; then
+ # If the given web service is a file, symlink it into the webapps/axis2/WEB-INF/services
+ ln -sfn $i ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $i`
+ elif [ -d $i ]; then
+ # If the given web application is a directory, then iterate over the files
+ # in the special purpose directories and symlink them into the tomcat tree
+
+ for j in $i/webapps/axis2/WEB-INF/services/*; do
+ ln -sfn $j ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $j`
+ done
+
+ # Also symlink the configuration files if they are included
+ if [ -d $i/conf/Catalina ]; then
+ for j in $i/conf/Catalina/*; do
+ ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j`
+ done
+ fi
+ fi
+ done
+ ''}
+ '';
+
+ serviceConfig = {
+ Type = "forking";
+ PermissionsStartOnly = true;
+ PIDFile="/run/tomcat/tomcat.pid";
+ RuntimeDirectory = "tomcat";
+ User = cfg.user;
+ Environment=[
+ "CATALINA_BASE=${cfg.baseDir}"
+ "CATALINA_PID=/run/tomcat/tomcat.pid"
+ "JAVA_HOME='${cfg.jdk}'"
+ "JAVA_OPTS='${builtins.toString cfg.javaOpts}'"
+ "CATALINA_OPTS='${builtins.toString cfg.catalinaOpts}'"
+ ] ++ cfg.extraEnvironment;
+ ExecStart = "${tomcat}/bin/startup.sh";
+ ExecStop = "${tomcat}/bin/shutdown.sh";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/traefik.nix b/nixpkgs/nixos/modules/services/web-servers/traefik.nix
new file mode 100644
index 00000000000..8de7df0d446
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/traefik.nix
@@ -0,0 +1,124 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.traefik;
+ configFile =
+ if cfg.configFile == null then
+ pkgs.runCommand "config.toml" {
+ buildInputs = [ pkgs.remarshal ];
+ preferLocalBuild = true;
+ } ''
+ remarshal -if json -of toml \
+ < ${pkgs.writeText "config.json" (builtins.toJSON cfg.configOptions)} \
+ > $out
+ ''
+ else cfg.configFile;
+
+in {
+ options.services.traefik = {
+ enable = mkEnableOption "Traefik web server";
+
+ configFile = mkOption {
+ default = null;
+ example = literalExample "/path/to/config.toml";
+ type = types.nullOr types.path;
+ description = ''
+ Path to verbatim traefik.toml to use.
+ (Using that option has precedence over <literal>configOptions</literal>)
+ '';
+ };
+
+ configOptions = mkOption {
+ description = ''
+ Config for Traefik.
+ '';
+ type = types.attrs;
+ default = {
+ defaultEntryPoints = ["http"];
+ entryPoints.http.address = ":80";
+ };
+ example = {
+ defaultEntrypoints = [ "http" ];
+ web.address = ":8080";
+ entryPoints.http.address = ":80";
+
+ file = {};
+ frontends = {
+ frontend1 = {
+ backend = "backend1";
+ routes.test_1.rule = "Host:localhost";
+ };
+ };
+ backends.backend1 = {
+ servers.server1.url = "http://localhost:8000";
+ };
+ };
+ };
+
+ dataDir = mkOption {
+ default = "/var/lib/traefik";
+ type = types.path;
+ description = ''
+ Location for any persistent data traefik creates, ie. acme
+ '';
+ };
+
+ group = mkOption {
+ default = "traefik";
+ type = types.str;
+ example = "docker";
+ description = ''
+ Set the group that traefik runs under.
+ For the docker backend this needs to be set to <literal>docker</literal> instead.
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.traefik;
+ defaultText = "pkgs.traefik";
+ type = types.package;
+ description = "Traefik package to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0700 traefik traefik - -"
+ ];
+
+ systemd.services.traefik = {
+ description = "Traefik web server";
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = ''${cfg.package.bin}/bin/traefik --configfile=${configFile}'';
+ Type = "simple";
+ User = "traefik";
+ Group = cfg.group;
+ Restart = "on-failure";
+ StartLimitInterval = 86400;
+ StartLimitBurst = 5;
+ AmbientCapabilities = "cap_net_bind_service";
+ CapabilityBoundingSet = "cap_net_bind_service";
+ NoNewPrivileges = true;
+ LimitNPROC = 64;
+ LimitNOFILE = 1048576;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectHome = true;
+ ProtectSystem = "full";
+ ReadWriteDirectories = cfg.dataDir;
+ };
+ };
+
+ users.users.traefik = {
+ group = "traefik";
+ home = cfg.dataDir;
+ createHome = true;
+ };
+
+ users.groups.traefik = {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/unit/default.nix b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix
new file mode 100644
index 00000000000..a4a9d370d64
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.unit;
+
+ configFile = pkgs.writeText "unit.json" cfg.config;
+
+in {
+ options = {
+ services.unit = {
+ enable = mkEnableOption "Unit App Server";
+ package = mkOption {
+ type = types.package;
+ default = pkgs.unit;
+ defaultText = "pkgs.unit";
+ description = "Unit package to use.";
+ };
+ user = mkOption {
+ type = types.str;
+ default = "unit";
+ description = "User account under which unit runs.";
+ };
+ group = mkOption {
+ type = types.str;
+ default = "unit";
+ description = "Group account under which unit runs.";
+ };
+ stateDir = mkOption {
+ default = "/var/spool/unit";
+ description = "Unit data directory.";
+ };
+ logDir = mkOption {
+ default = "/var/log/unit";
+ description = "Unit log directory.";
+ };
+ config = mkOption {
+ type = types.str;
+ default = ''
+ {
+ "listeners": {},
+ "applications": {}
+ }
+ '';
+ example = literalExample ''
+ {
+ "listeners": {
+ "*:8300": {
+ "application": "example-php-72"
+ }
+ },
+ "applications": {
+ "example-php-72": {
+ "type": "php 7.2",
+ "processes": 4,
+ "user": "nginx",
+ "group": "nginx",
+ "root": "/var/www",
+ "index": "index.php",
+ "options": {
+ "file": "/etc/php.d/default.ini",
+ "admin": {
+ "max_execution_time": "30",
+ "max_input_time": "30",
+ "display_errors": "off",
+ "display_startup_errors": "off",
+ "open_basedir": "/dev/urandom:/proc/cpuinfo:/proc/meminfo:/etc/ssl/certs:/var/www",
+ "disable_functions": "exec,passthru,shell_exec,system"
+ }
+ }
+ }
+ }
+ }
+ '';
+ description = "Unit configuration in JSON format. More details here https://unit.nginx.org/configuration";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+ "d '${cfg.logDir}' 0750 ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.unit = {
+ description = "Unit App Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [ curl ];
+ preStart = ''
+ test -f '/run/unit/control.unit.sock' || rm -f '/run/unit/control.unit.sock'
+ '';
+ postStart = ''
+ curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
+ '';
+ serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID";
+ CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID";
+ ExecStart = ''
+ ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \
+ --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' --no-daemon \
+ --user ${cfg.user} --group ${cfg.group}
+ '';
+ RuntimeDirectory = "unit";
+ RuntimeDirectoryMode = "0750";
+ };
+ };
+
+ users.users = optionalAttrs (cfg.user == "unit") (singleton {
+ name = "unit";
+ group = cfg.group;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "unit") (singleton {
+ name = "unit";
+ });
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
new file mode 100644
index 00000000000..af70f32f32d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
@@ -0,0 +1,160 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.uwsgi;
+
+ uwsgi = pkgs.uwsgi.override {
+ plugins = cfg.plugins;
+ };
+
+ buildCfg = name: c:
+ let
+ plugins =
+ if any (n: !any (m: m == n) cfg.plugins) (c.plugins or [])
+ then throw "`plugins` attribute in UWSGI configuration contains plugins not in config.services.uwsgi.plugins"
+ else c.plugins or cfg.plugins;
+
+ hasPython = v: filter (n: n == "python${v}") plugins != [];
+ hasPython2 = hasPython "2";
+ hasPython3 = hasPython "3";
+
+ python =
+ if hasPython2 && hasPython3 then
+ throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3"
+ else if hasPython2 then uwsgi.python2
+ else if hasPython3 then uwsgi.python3
+ else null;
+
+ pythonEnv = python.withPackages (c.pythonPackages or (self: []));
+
+ uwsgiCfg = {
+ uwsgi =
+ if c.type == "normal"
+ then {
+ inherit plugins;
+ } // removeAttrs c [ "type" "pythonPackages" ]
+ // optionalAttrs (python != null) {
+ pythonpath = "${pythonEnv}/${python.sitePackages}";
+ env =
+ # Argh, uwsgi expects list of key-values there instead of a dictionary.
+ let env' = c.env or [];
+ getPath =
+ x: if hasPrefix "PATH=" x
+ then substring (stringLength "PATH=") (stringLength x) x
+ else null;
+ oldPaths = filter (x: x != null) (map getPath env');
+ in env' ++ [ "PATH=${optionalString (oldPaths != []) "${last oldPaths}:"}${pythonEnv}/bin" ];
+ }
+ else if c.type == "emperor"
+ then {
+ emperor = if builtins.typeOf c.vassals != "set" then c.vassals
+ else pkgs.buildEnv {
+ name = "vassals";
+ paths = mapAttrsToList buildCfg c.vassals;
+ };
+ } // removeAttrs c [ "type" "vassals" ]
+ else throw "`type` attribute in UWSGI configuration should be either 'normal' or 'emperor'";
+ };
+
+ in pkgs.writeTextDir "${name}.json" (builtins.toJSON uwsgiCfg);
+
+in {
+
+ options = {
+ services.uwsgi = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable uWSGI";
+ };
+
+ runDir = mkOption {
+ type = types.path;
+ default = "/run/uwsgi";
+ description = "Where uWSGI communication sockets can live";
+ };
+
+ instance = mkOption {
+ type = types.attrs;
+ default = {
+ type = "normal";
+ };
+ example = literalExample ''
+ {
+ type = "emperor";
+ vassals = {
+ moin = {
+ type = "normal";
+ pythonPackages = self: with self; [ moinmoin ];
+ socket = "${config.services.uwsgi.runDir}/uwsgi.sock";
+ };
+ };
+ }
+ '';
+ description = ''
+ uWSGI configuration. It awaits an attribute <literal>type</literal> inside which can be either
+ <literal>normal</literal> or <literal>emperor</literal>.
+
+ For <literal>normal</literal> mode you can specify <literal>pythonPackages</literal> as a function
+ from libraries set into a list of libraries. <literal>pythonpath</literal> will be set accordingly.
+
+ For <literal>emperor</literal> mode, you should use <literal>vassals</literal> attribute
+ which should be either a set of names and configurations or a path to a directory.
+
+ Other attributes will be used in configuration file as-is. Notice that you can redefine
+ <literal>plugins</literal> setting here.
+ '';
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Plugins used with uWSGI";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "uwsgi";
+ description = "User account under which uwsgi runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "uwsgi";
+ description = "Group account under which uwsgi runs.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.uwsgi = {
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ mkdir -p ${cfg.runDir}
+ chown ${cfg.user}:${cfg.group} ${cfg.runDir}
+ '';
+ serviceConfig = {
+ Type = "notify";
+ ExecStart = "${uwsgi}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+ NotifyAccess = "main";
+ KillSignal = "SIGQUIT";
+ };
+ };
+
+ users.users = optionalAttrs (cfg.user == "uwsgi") (singleton
+ { name = "uwsgi";
+ group = cfg.group;
+ uid = config.ids.uids.uwsgi;
+ });
+
+ users.groups = optionalAttrs (cfg.group == "uwsgi") (singleton
+ { name = "uwsgi";
+ gid = config.ids.gids.uwsgi;
+ });
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
new file mode 100644
index 00000000000..63f967185c2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+ cfg = config.services.varnish;
+
+ commandLine = "-f ${pkgs.writeText "default.vcl" cfg.config}" +
+ optionalString (cfg.extraModules != []) " -p vmod_path='${makeSearchPathOutput "lib" "lib/varnish/vmods" ([cfg.package] ++ cfg.extraModules)}' -r vmod_path";
+in
+{
+ options = {
+ services.varnish = {
+ enable = mkEnableOption "Varnish Server";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.varnish5;
+ defaultText = "pkgs.varnish5";
+ description = ''
+ The package to use
+ '';
+ };
+
+ http_address = mkOption {
+ type = types.str;
+ default = "*:6081";
+ description = "
+ HTTP listen address and port.
+ ";
+ };
+
+ config = mkOption {
+ type = types.lines;
+ description = "
+ Verbatim default.vcl configuration.
+ ";
+ };
+
+ stateDir = mkOption {
+ type = types.path;
+ default = "/var/spool/varnish/${config.networking.hostName}";
+ description = "
+ Directory holding all state for Varnish to run.
+ ";
+ };
+
+ extraModules = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "[ pkgs.varnish5Packages.geoip ]";
+ description = "
+ Varnish modules (except 'std').
+ ";
+ };
+
+ extraCommandLine = mkOption {
+ type = types.str;
+ default = "";
+ example = "-s malloc,256M";
+ description = "
+ Command line switches for varnishd (run 'varnishd -?' to get list of options)
+ ";
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.varnish = {
+ description = "Varnish";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ preStart = ''
+ mkdir -p ${cfg.stateDir}
+ chown -R varnish:varnish ${cfg.stateDir}
+ '';
+ postStop = ''
+ rm -rf ${cfg.stateDir}
+ '';
+ serviceConfig = {
+ Type = "simple";
+ PermissionsStartOnly = true;
+ ExecStart = "${cfg.package}/sbin/varnishd -a ${cfg.http_address} -n ${cfg.stateDir} -F ${cfg.extraCommandLine} ${commandLine}";
+ Restart = "always";
+ RestartSec = "5s";
+ User = "varnish";
+ Group = "varnish";
+ AmbientCapabilities = "cap_net_bind_service";
+ NoNewPrivileges = true;
+ LimitNOFILE = 131072;
+ };
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ # check .vcl syntax at compile time (e.g. before nixops deployment)
+ system.extraDependencies = [
+ (pkgs.stdenv.mkDerivation {
+ name = "check-varnish-syntax";
+ buildCommand = "${cfg.package}/sbin/varnishd -C ${commandLine} 2> $out || (cat $out; exit 1)";
+ })
+ ];
+
+ users.users.varnish = {
+ group = "varnish";
+ uid = config.ids.uids.varnish;
+ };
+
+ users.groups.varnish.gid = config.ids.uids.varnish;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/zope2.nix b/nixpkgs/nixos/modules/services/web-servers/zope2.nix
new file mode 100644
index 00000000000..3abd506827c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/zope2.nix
@@ -0,0 +1,258 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.zope2;
+
+ zope2Opts = { name, ... }: {
+ options = {
+
+ name = mkOption {
+ default = "${name}";
+ type = types.str;
+ description = "The name of the zope2 instance. If undefined, the name of the attribute set will be used.";
+ };
+
+ threads = mkOption {
+ default = 2;
+ type = types.int;
+ description = "Specify the number of threads that Zope's ZServer web server will use to service requests. ";
+ };
+
+ http_address = mkOption {
+ default = "localhost:8080";
+ type = types.str;
+ description = "Give a port and address for the HTTP server.";
+ };
+
+ user = mkOption {
+ default = "zope2";
+ type = types.str;
+ description = "The name of the effective user for the Zope process.";
+ };
+
+ clientHome = mkOption {
+ default = "/var/lib/zope2/${name}";
+ type = types.path;
+ description = "Home directory of zope2 instance.";
+ };
+ extra = mkOption {
+ default =
+ ''
+ <zodb_db main>
+ mount-point /
+ cache-size 30000
+ <blobstorage>
+ blob-dir /var/lib/zope2/${name}/blobstorage
+ <filestorage>
+ path /var/lib/zope2/${name}/filestorage/Data.fs
+ </filestorage>
+ </blobstorage>
+ </zodb_db>
+ '';
+ type = types.lines;
+ description = "Extra zope.conf";
+ };
+
+ packages = mkOption {
+ type = types.listOf types.package;
+ description = "The list of packages you want to make available to the zope2 instance.";
+ };
+
+ };
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.zope2.instances = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule zope2Opts);
+ example = literalExample ''
+ {
+ plone01 = {
+ http_address = "127.0.0.1:8080";
+ extra =
+ '''
+ <zodb_db main>
+ mount-point /
+ cache-size 30000
+ <blobstorage>
+ blob-dir /var/lib/zope2/plone01/blobstorage
+ <filestorage>
+ path /var/lib/zope2/plone01/filestorage/Data.fs
+ </filestorage>
+ </blobstorage>
+ </zodb_db>
+ ''';
+ };
+ }
+ '';
+ description = "zope2 instances to be created automaticaly by the system.";
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf (cfg.instances != {}) {
+
+ users.users.zope2.uid = config.ids.uids.zope2;
+
+ systemd.services =
+ let
+
+ createZope2Instance = opts: name:
+ let
+ interpreter = pkgs.writeScript "interpreter"
+ ''
+ import sys
+
+ _interactive = True
+ if len(sys.argv) > 1:
+ _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
+ _interactive = False
+ for (_opt, _val) in _options:
+ if _opt == '-i':
+ _interactive = True
+ elif _opt == '-c':
+ exec _val
+ elif _opt == '-m':
+ sys.argv[1:] = _args
+ _args = []
+ __import__("runpy").run_module(
+ _val, {}, "__main__", alter_sys=True)
+
+ if _args:
+ sys.argv[:] = _args
+ __file__ = _args[0]
+ del _options, _args
+ execfile(__file__)
+
+ if _interactive:
+ del _interactive
+ __import__("code").interact(banner="", local=globals())
+ '';
+ env = pkgs.buildEnv {
+ name = "zope2-${name}-env";
+ paths = [
+ pkgs.python27
+ pkgs.python27Packages.recursivePthLoader
+ pkgs.python27Packages."plone.recipe.zope2instance"
+ ] ++ attrValues pkgs.python27.modules
+ ++ opts.packages;
+ postBuild =
+ ''
+ echo "#!$out/bin/python" > $out/bin/interpreter
+ cat ${interpreter} >> $out/bin/interpreter
+ '';
+ };
+ conf = pkgs.writeText "zope2-${name}-conf"
+ ''
+ %define INSTANCEHOME ${env}
+ instancehome $INSTANCEHOME
+ %define CLIENTHOME ${opts.clientHome}/${opts.name}
+ clienthome $CLIENTHOME
+
+ debug-mode off
+ security-policy-implementation C
+ verbose-security off
+ default-zpublisher-encoding utf-8
+ zserver-threads ${toString opts.threads}
+ effective-user ${opts.user}
+
+ pid-filename ${opts.clientHome}/${opts.name}/pid
+ lock-filename ${opts.clientHome}/${opts.name}/lock
+ python-check-interval 1000
+ enable-product-installation off
+
+ <environment>
+ zope_i18n_compile_mo_files false
+ </environment>
+
+ <eventlog>
+ level INFO
+ <logfile>
+ path /var/log/zope2/${name}.log
+ level INFO
+ </logfile>
+ </eventlog>
+
+ <logger access>
+ level WARN
+ <logfile>
+ path /var/log/zope2/${name}-Z2.log
+ format %(message)s
+ </logfile>
+ </logger>
+
+ <http-server>
+ address ${opts.http_address}
+ </http-server>
+
+ <zodb_db temporary>
+ <temporarystorage>
+ name temporary storage for sessioning
+ </temporarystorage>
+ mount-point /temp_folder
+ container-class Products.TemporaryFolder.TemporaryContainer
+ </zodb_db>
+
+ ${opts.extra}
+ '';
+ ctlScript = pkgs.writeScript "zope2-${name}-ctl-script"
+ ''
+ #!${env}/bin/python
+
+ import sys
+ import plone.recipe.zope2instance.ctl
+
+ if __name__ == '__main__':
+ sys.exit(plone.recipe.zope2instance.ctl.main(
+ ["-C", "${conf}"]
+ + sys.argv[1:]))
+ '';
+
+ ctl = pkgs.writeScript "zope2-${name}-ctl"
+ ''
+ #!${pkgs.bash}/bin/bash -e
+ export PYTHONHOME=${env}
+ exec ${ctlScript} "$@"
+ '';
+ in {
+ #description = "${name} instance";
+ after = [ "network.target" ]; # with RelStorage also add "postgresql.service"
+ wantedBy = [ "multi-user.target" ];
+ path = opts.packages;
+ preStart =
+ ''
+ mkdir -p /var/log/zope2/
+ touch /var/log/zope2/${name}.log
+ touch /var/log/zope2/${name}-Z2.log
+ chown ${opts.user} /var/log/zope2/${name}.log
+ chown ${opts.user} /var/log/zope2/${name}-Z2.log
+
+ mkdir -p ${opts.clientHome}/filestorage ${opts.clientHome}/blobstorage
+ mkdir -p ${opts.clientHome}/${opts.name}
+ chown ${opts.user} ${opts.clientHome} -R
+
+ ${ctl} adduser admin admin
+ '';
+
+ serviceConfig.Type = "forking";
+ serviceConfig.ExecStart = "${ctl} start";
+ serviceConfig.ExecStop = "${ctl} stop";
+ serviceConfig.ExecReload = "${ctl} restart";
+ };
+
+ in listToAttrs (map (name: { name = "zope2-${name}"; value = createZope2Instance (builtins.getAttr name cfg.instances) name; }) (builtins.attrNames cfg.instances));
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/clight.nix b/nixpkgs/nixos/modules/services/x11/clight.nix
new file mode 100644
index 00000000000..4daf6d8d9db
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/clight.nix
@@ -0,0 +1,115 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.clight;
+
+ toConf = v:
+ if builtins.isFloat v then toString v
+ else if isInt v then toString v
+ else if isBool v then boolToString v
+ else if isString v then ''"${escape [''"''] v}"''
+ else if isList v then "[ " + concatMapStringsSep ", " toConf v + " ]"
+ else abort "clight.toConf: unexpected type (v = ${v})";
+
+ clightConf = pkgs.writeText "clight.conf"
+ (concatStringsSep "\n" (mapAttrsToList
+ (name: value: "${toString name} = ${toConf value};")
+ (filterAttrs
+ (_: value: value != null)
+ cfg.settings)));
+in {
+ options.services.clight = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable clight or not.
+ '';
+ };
+
+ temperature = {
+ day = mkOption {
+ type = types.int;
+ default = 5500;
+ description = ''
+ Colour temperature to use during the day, between
+ <literal>1000</literal> and <literal>25000</literal> K.
+ '';
+ };
+ night = mkOption {
+ type = types.int;
+ default = 3700;
+ description = ''
+ Colour temperature to use at night, between
+ <literal>1000</literal> and <literal>25000</literal> K.
+ '';
+ };
+ };
+
+ settings = let
+ validConfigTypes = with types; either int (either str (either bool float));
+ in mkOption {
+ type = with types; attrsOf (nullOr (either validConfigTypes (listOf validConfigTypes)));
+ default = {};
+ example = { captures = 20; gamma_long_transition = true; ac_capture_timeouts = [ 120 300 60 ]; };
+ description = ''
+ Additional configuration to extend clight.conf. See
+ <link xlink:href="https://github.com/FedeDP/Clight/blob/master/Extra/clight.conf"/> for a
+ sample configuration file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ boot.kernelModules = [ "i2c_dev" ];
+ environment.systemPackages = with pkgs; [ clight clightd ];
+ services.dbus.packages = with pkgs; [ clight clightd ];
+ services.upower.enable = true;
+
+ services.clight.settings = {
+ gamma_temp = with cfg.temperature; mkDefault [ day night ];
+ } // (optionalAttrs (config.location.provider == "manual") {
+ latitude = mkDefault config.location.latitude;
+ longitude = mkDefault config.location.longitude;
+ });
+
+ services.geoclue2.appConfig.clightc = {
+ isAllowed = true;
+ isSystem = true;
+ };
+
+ systemd.services.clightd = {
+ requires = [ "polkit.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ description = "Bus service to manage various screen related properties (gamma, dpms, backlight)";
+ serviceConfig = {
+ Type = "dbus";
+ BusName = "org.clightd.clightd";
+ Restart = "on-failure";
+ RestartSec = 5;
+ ExecStart = ''
+ ${pkgs.clightd}/bin/clightd
+ '';
+ };
+ };
+
+ systemd.user.services.clight = {
+ after = [ "upower.service" "clightd.service" ];
+ wants = [ "upower.service" "clightd.service" ];
+ partOf = [ "graphical-session.target" ];
+ wantedBy = [ "graphical-session.target" ];
+
+ description = "C daemon to adjust screen brightness to match ambient brightness, as computed capturing frames from webcam";
+ serviceConfig = {
+ Restart = "on-failure";
+ RestartSec = 5;
+ ExecStart = ''
+ ${pkgs.clight}/bin/clight --conf-file ${clightConf}
+ '';
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/colord.nix b/nixpkgs/nixos/modules/services/x11/colord.nix
new file mode 100644
index 00000000000..cf113ad2af8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/colord.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.colord;
+
+in {
+
+ options = {
+
+ services.colord = {
+ enable = mkEnableOption "colord, the color management daemon";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.colord ];
+
+ services.dbus.packages = [ pkgs.colord ];
+
+ services.udev.packages = [ pkgs.colord ];
+
+ systemd.packages = [ pkgs.colord ];
+
+ environment.etc."tmpfiles.d/colord.conf".source = "${pkgs.colord}/lib/tmpfiles.d/colord.conf";
+
+ users.users.colord = {
+ isSystemUser = true;
+ home = "/var/lib/colord";
+ group = "colord";
+ };
+
+ users.groups.colord = {};
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/compton.nix b/nixpkgs/nixos/modules/services/x11/compton.nix
new file mode 100644
index 00000000000..a94a76ff0c0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/compton.nix
@@ -0,0 +1,287 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with builtins;
+
+let
+
+ cfg = config.services.compton;
+
+ pairOf = x: with types; addCheck (listOf x) (y: length y == 2);
+
+ floatBetween = a: b: with lib; with types;
+ addCheck str (x: versionAtLeast x a && versionOlder x b);
+
+ toConf = attrs: concatStringsSep "\n"
+ (mapAttrsToList
+ (k: v: let
+ sep = if isAttrs v then ":" else "=";
+ # Basically a tinkered lib.generators.mkKeyValueDefault
+ mkValueString = v:
+ if isBool v then boolToString v
+ else if isInt v then toString v
+ else if isFloat v then toString v
+ else if isString v then ''"${escape [ ''"'' ] v}"''
+ else if isList v then "[ "
+ + concatMapStringsSep " , " mkValueString v
+ + " ]"
+ else if isAttrs v then "{ "
+ + concatStringsSep " "
+ (mapAttrsToList
+ (key: value: "${toString key}=${mkValueString value};")
+ v)
+ + " }"
+ else abort "compton.mkValueString: unexpected type (v = ${v})";
+ in "${escape [ sep ] k}${sep}${mkValueString v};")
+ attrs);
+
+ configFile = pkgs.writeText "compton.conf" (toConf cfg.settings);
+
+in {
+
+ options.services.compton = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether of not to enable Compton as the X.org composite manager.
+ '';
+ };
+
+ fade = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Fade windows in and out.
+ '';
+ };
+
+ fadeDelta = mkOption {
+ type = types.addCheck types.int (x: x > 0);
+ default = 10;
+ example = 5;
+ description = ''
+ Time between fade animation step (in ms).
+ '';
+ };
+
+ fadeSteps = mkOption {
+ type = pairOf (floatBetween "0.01" "1.01");
+ default = [ "0.028" "0.03" ];
+ example = [ "0.04" "0.04" ];
+ description = ''
+ Opacity change between fade steps (in and out).
+ (numbers in range 0.01 - 1.0)
+ '';
+ };
+
+ fadeExclude = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [
+ "window_type *= 'menu'"
+ "name ~= 'Firefox$'"
+ "focused = 1"
+ ];
+ description = ''
+ List of conditions of windows that should not be faded.
+ See <literal>compton(1)</literal> man page for more examples.
+ '';
+ };
+
+ shadow = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Draw window shadows.
+ '';
+ };
+
+ shadowOffsets = mkOption {
+ type = pairOf types.int;
+ default = [ (-15) (-15) ];
+ example = [ (-10) (-15) ];
+ description = ''
+ Left and right offset for shadows (in pixels).
+ '';
+ };
+
+ shadowOpacity = mkOption {
+ type = floatBetween "0.0" "1.01";
+ default = "0.75";
+ example = "0.8";
+ description = ''
+ Window shadows opacity (number in range 0.0 - 1.0).
+ '';
+ };
+
+ shadowExclude = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [
+ "window_type *= 'menu'"
+ "name ~= 'Firefox$'"
+ "focused = 1"
+ ];
+ description = ''
+ List of conditions of windows that should have no shadow.
+ See <literal>compton(1)</literal> man page for more examples.
+ '';
+ };
+
+ activeOpacity = mkOption {
+ type = floatBetween "0.0" "1.01";
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of active windows (number in range 0.0 - 1.0).
+ '';
+ };
+
+ inactiveOpacity = mkOption {
+ type = floatBetween "0.1" "1.01";
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of inactive windows (number in range 0.1 - 1.0).
+ '';
+ };
+
+ menuOpacity = mkOption {
+ type = floatBetween "0.0" "1.01";
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of dropdown and popup menu (number in range 0.0 - 1.0).
+ '';
+ };
+
+ wintypes = mkOption {
+ type = types.attrs;
+ default = { popup_menu = { opacity = cfg.menuOpacity; }; dropdown_menu = { opacity = cfg.menuOpacity; }; };
+ example = {};
+ description = ''
+ Rules for specific window types.
+ '';
+ };
+
+ opacityRules = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [
+ "95:class_g = 'URxvt' && !_NET_WM_STATE@:32a"
+ "0:_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'"
+ ];
+ description = ''
+ Rules that control the opacity of windows, in format PERCENT:PATTERN.
+ '';
+ };
+
+ backend = mkOption {
+ type = types.enum [ "glx" "xrender" "xr_glx_hybrid" ];
+ default = "xrender";
+ description = ''
+ Backend to use: <literal>glx</literal>, <literal>xrender</literal> or <literal>xr_glx_hybrid</literal>.
+ '';
+ };
+
+ vSync = mkOption {
+ type = with types; either bool
+ (enum [ "none" "drm" "opengl" "opengl-oml" "opengl-swc" "opengl-mswc" ]);
+ default = false;
+ apply = x:
+ let
+ res = x != "none";
+ msg = "The type of services.compton.vSync has changed to bool:"
+ + " interpreting ${x} as ${boolToString res}";
+ in
+ if isBool x then x
+ else warn msg res;
+
+ description = ''
+ Enable vertical synchronization. Chooses the best method
+ (drm, opengl, opengl-oml, opengl-swc, opengl-mswc) automatically.
+ The bool value should be used, the others are just for backwards compatibility.
+ '';
+ };
+
+ refreshRate = mkOption {
+ type = types.addCheck types.int (x: x >= 0);
+ default = 0;
+ example = 60;
+ description = ''
+ Screen refresh rate (0 = automatically detect).
+ '';
+ };
+
+ settings = let
+ configTypes = with types; oneOf [ bool int float str ];
+ # types.loaOf converts lists to sets
+ loaOf = t: with types; either (listOf t) (attrsOf t);
+ in mkOption {
+ type = loaOf (types.either configTypes (loaOf (types.either configTypes (loaOf configTypes))));
+ default = {};
+ description = ''
+ Additional Compton configuration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.compton.settings = let
+ # Hard conversion to float, literally lib.toInt but toFloat
+ toFloat = str: let
+ may_be_float = builtins.fromJSON str;
+ in if builtins.isFloat may_be_float
+ then may_be_float
+ else throw "Could not convert ${str} to float.";
+ in {
+ # fading
+ fading = mkDefault cfg.fade;
+ fade-delta = mkDefault cfg.fadeDelta;
+ fade-in-step = mkDefault (toFloat (elemAt cfg.fadeSteps 0));
+ fade-out-step = mkDefault (toFloat (elemAt cfg.fadeSteps 1));
+ fade-exclude = mkDefault cfg.fadeExclude;
+
+ # shadows
+ shadow = mkDefault cfg.shadow;
+ shadow-offset-x = mkDefault (elemAt cfg.shadowOffsets 0);
+ shadow-offset-y = mkDefault (elemAt cfg.shadowOffsets 1);
+ shadow-opacity = mkDefault (toFloat cfg.shadowOpacity);
+ shadow-exclude = mkDefault cfg.shadowExclude;
+
+ # opacity
+ active-opacity = mkDefault (toFloat cfg.activeOpacity);
+ inactive-opacity = mkDefault (toFloat cfg.inactiveOpacity);
+
+ wintypes = mkDefault cfg.wintypes;
+
+ opacity-rule = mkDefault cfg.opacityRules;
+
+ # other options
+ backend = mkDefault cfg.backend;
+ vsync = mkDefault cfg.vSync;
+ refresh-rate = mkDefault cfg.refreshRate;
+ };
+
+ systemd.user.services.compton = {
+ description = "Compton composite manager";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+
+ # Temporarily fixes corrupt colours with Mesa 18
+ environment = mkIf (cfg.backend == "glx") {
+ allow_rgb10_configs = "false";
+ };
+
+ serviceConfig = {
+ ExecStart = "${pkgs.compton}/bin/compton --config ${configFile}";
+ RestartSec = 3;
+ Restart = "always";
+ };
+ };
+
+ environment.systemPackages = [ pkgs.compton ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix
new file mode 100644
index 00000000000..dfb84113e13
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ xcfg = config.services.xserver;
+ cfg = xcfg.desktopManager;
+
+ # If desktop manager `d' isn't capable of setting a background and
+ # the xserver is enabled, `feh' or `xsetroot' are used as a fallback.
+ needBGCond = d: ! (d ? bgSupport && d.bgSupport) && xcfg.enable;
+
+in
+
+{
+ # Note: the order in which desktop manager modules are imported here
+ # determines the default: later modules (if enabled) are preferred.
+ # E.g., if Plasma 5 is enabled, it supersedes xterm.
+ imports = [
+ ./none.nix ./xterm.nix ./xfce.nix ./xfce4-14.nix ./plasma5.nix ./lumina.nix
+ ./lxqt.nix ./enlightenment.nix ./gnome3.nix ./kodi.nix ./maxx.nix
+ ./mate.nix ./pantheon.nix ./surf-display.nix
+ ];
+
+ options = {
+
+ services.xserver.desktopManager = {
+
+ wallpaper = {
+ mode = mkOption {
+ type = types.enum [ "center" "fill" "max" "scale" "tile" ];
+ default = "scale";
+ example = "fill";
+ description = ''
+ The file <filename>~/.background-image</filename> is used as a background image.
+ This option specifies the placement of this image onto your desktop.
+
+ Possible values:
+ <literal>center</literal>: Center the image on the background. If it is too small, it will be surrounded by a black border.
+ <literal>fill</literal>: Like <literal>scale</literal>, but preserves aspect ratio by zooming the image until it fits. Either a horizontal or a vertical part of the image will be cut off.
+ <literal>max</literal>: Like <literal>fill</literal>, but scale the image to the maximum size that fits the screen with black borders on one side.
+ <literal>scale</literal>: Fit the file into the background without repeating it, cutting off stuff or using borders. But the aspect ratio is not preserved either.
+ <literal>tile</literal>: Tile (repeat) the image in case it is too small for the screen.
+ '';
+ };
+
+ combineScreens = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ When set to <literal>true</literal> the wallpaper will stretch across all screens.
+ When set to <literal>false</literal> the wallpaper is duplicated to all screens.
+ '';
+ };
+ };
+
+ session = mkOption {
+ internal = true;
+ default = [];
+ example = singleton
+ { name = "kde";
+ bgSupport = true;
+ start = "...";
+ };
+ description = ''
+ Internal option used to add some common line to desktop manager
+ scripts before forwarding the value to the
+ <varname>displayManager</varname>.
+ '';
+ apply = list: {
+ list = map (d: d // {
+ manage = "desktop";
+ start = d.start
+ + optionalString (needBGCond d) ''
+ if [ -e $HOME/.background-image ]; then
+ ${pkgs.feh}/bin/feh --bg-${cfg.wallpaper.mode} ${optionalString cfg.wallpaper.combineScreens "--no-xinerama"} $HOME/.background-image
+ else
+ # Use a solid black background as fallback
+ ${pkgs.xorg.xsetroot}/bin/xsetroot -solid black
+ fi
+ '';
+ }) list;
+ needBGPackages = [] != filter needBGCond list;
+ };
+ };
+
+ default = mkOption {
+ type = types.str;
+ default = "";
+ example = "none";
+ description = "Default desktop manager loaded if none have been chosen.";
+ apply = defaultDM:
+ if defaultDM == "" && cfg.session.list != [] then
+ (head cfg.session.list).name
+ else if any (w: w.name == defaultDM) cfg.session.list then
+ defaultDM
+ else
+ builtins.trace ''
+ Default desktop manager (${defaultDM}) not found at evaluation time.
+ These are the known valid session names:
+ ${concatMapStringsSep "\n " (w: "services.xserver.desktopManager.default = \"${w.name}\";") cfg.session.list}
+ It's also possible the default can be found in one of these packages:
+ ${concatMapStringsSep "\n " (p: p.name) config.services.xserver.displayManager.extraSessionFilePackages}
+ '' defaultDM;
+ };
+
+ };
+
+ };
+
+ config.services.xserver.displayManager.session = cfg.session.list;
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/enlightenment.nix
new file mode 100644
index 00000000000..3745069f6ea
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -0,0 +1,100 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ e = pkgs.enlightenment;
+ xcfg = config.services.xserver;
+ cfg = xcfg.desktopManager.enlightenment;
+ GST_PLUGIN_PATH = lib.makeSearchPathOutput "lib" "lib/gstreamer-1.0" [
+ pkgs.gst_all_1.gst-plugins-base
+ pkgs.gst_all_1.gst-plugins-good
+ pkgs.gst_all_1.gst-plugins-bad
+ pkgs.gst_all_1.gst-libav ];
+
+in
+
+{
+ options = {
+
+ services.xserver.desktopManager.enlightenment.enable = mkOption {
+ default = false;
+ description = "Enable the Enlightenment desktop environment.";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [
+ e.efl e.enlightenment
+ e.terminology e.econnman
+ pkgs.xorg.xauth # used by kdesu
+ pkgs.gtk2 # To get GTK's themes.
+ pkgs.tango-icon-theme
+
+ pkgs.gnome2.gnome_icon_theme
+ pkgs.xorg.xcursorthemes
+ ];
+
+ environment.pathsToLink = [
+ "/etc/enlightenment"
+ "/share/enlightenment"
+ "/share/elementary"
+ "/share/locale"
+ ];
+
+ services.xserver.desktopManager.session = [
+ { name = "Enlightenment";
+ start = ''
+ export XDG_MENU_PREFIX=e-
+
+ export GST_PLUGIN_PATH="${GST_PLUGIN_PATH}"
+
+ # make available for D-BUS user services
+ #export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}:${config.system.path}/share:${e.efl}/share
+
+ # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+ ${pkgs.xdg-user-dirs}/bin/xdg-user-dirs-update
+
+ exec ${e.enlightenment}/bin/enlightenment_start
+ '';
+ }];
+
+ security.wrappers = (import "${e.enlightenment}/e-wrappers.nix").security.wrappers;
+
+ environment.etc = singleton
+ { source = xcfg.xkbDir;
+ target = "X11/xkb";
+ };
+
+ fonts.fonts = [ pkgs.dejavu_fonts pkgs.ubuntu_font_family ];
+
+ services.udisks2.enable = true;
+ services.upower.enable = config.powerManagement.enable;
+
+ services.dbus.packages = [ e.efl ];
+
+ systemd.user.services.efreet =
+ { enable = true;
+ description = "org.enlightenment.Efreet";
+ serviceConfig =
+ { ExecStart = "${e.efl}/bin/efreetd";
+ StandardOutput = "null";
+ };
+ };
+
+ systemd.user.services.ethumb =
+ { enable = true;
+ description = "org.enlightenment.Ethumb";
+ serviceConfig =
+ { ExecStart = "${e.efl}/bin/ethumbd";
+ StandardOutput = "null";
+ };
+ };
+
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix
new file mode 100644
index 00000000000..30c5250221c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -0,0 +1,352 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver.desktopManager.gnome3;
+ serviceCfg = config.services.gnome3;
+
+ # Prioritize nautilus by default when opening directories
+ mimeAppsList = pkgs.writeTextFile {
+ name = "gnome-mimeapps";
+ destination = "/share/applications/mimeapps.list";
+ text = ''
+ [Default Applications]
+ inode/directory=nautilus.desktop;org.gnome.Nautilus.desktop
+ '';
+ };
+
+ nixos-gsettings-desktop-schemas = let
+ defaultPackages = with pkgs; [ gsettings-desktop-schemas gnome3.gnome-shell ];
+ in
+ pkgs.runCommand "nixos-gsettings-desktop-schemas" { preferLocalBuild = true; }
+ ''
+ mkdir -p $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
+
+ ${concatMapStrings
+ (pkg: "cp -rf ${pkg}/share/gsettings-schemas/*/glib-2.0/schemas/*.xml $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas\n")
+ (defaultPackages ++ cfg.extraGSettingsOverridePackages)}
+
+ chmod -R a+w $out/share/gsettings-schemas/nixos-gsettings-overrides
+ cat - > $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/nixos-defaults.gschema.override <<- EOF
+ [org.gnome.desktop.background]
+ picture-uri='file://${pkgs.nixos-artwork.wallpapers.simple-dark-gray}/share/artwork/gnome/nix-wallpaper-simple-dark-gray.png'
+
+ [org.gnome.desktop.screensaver]
+ picture-uri='file://${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bottom.png'
+
+ [org.gnome.shell]
+ favorite-apps=[ 'org.gnome.Epiphany.desktop', 'org.gnome.Geary.desktop', 'org.gnome.Music.desktop', 'org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop' ]
+
+ ${cfg.extraGSettingsOverrides}
+ EOF
+
+ ${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 = {
+
+ services.gnome3 = {
+ core-os-services.enable = mkEnableOption "essential services for GNOME3";
+ core-shell.enable = mkEnableOption "GNOME Shell services";
+ core-utilities.enable = mkEnableOption "GNOME core utilities";
+ games.enable = mkEnableOption "GNOME games";
+ };
+
+ services.xserver.desktopManager.gnome3 = {
+ enable = mkOption {
+ default = false;
+ description = "Enable Gnome 3 desktop manager.";
+ };
+
+ sessionPath = mkOption {
+ default = [];
+ example = literalExample "[ pkgs.gnome3.gpaste ]";
+ description = ''
+ Additional list of packages to be added to the session search path.
+ Useful for GNOME Shell extensions or GSettings-conditional autostart.
+
+ Note that this should be a last resort; patching the package is preferred (see GPaste).
+ '';
+ apply = list: list ++ [ pkgs.gnome3.gnome-shell pkgs.gnome3.gnome-shell-extensions ];
+ };
+
+ extraGSettingsOverrides = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Additional gsettings overrides.";
+ };
+
+ extraGSettingsOverridePackages = mkOption {
+ default = [];
+ type = types.listOf types.path;
+ description = "List of packages for which gsettings are overridden.";
+ };
+
+ debug = mkEnableOption "gnome-session debug messages";
+
+ flashback = {
+ enableMetacity = mkEnableOption "the standard GNOME Flashback session with Metacity";
+
+ customSessions = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ wmName = mkOption {
+ type = types.str;
+ description = "The filename-compatible name of the window manager to use.";
+ example = "xmonad";
+ };
+
+ wmLabel = mkOption {
+ type = types.str;
+ description = "The pretty name of the window manager to use.";
+ example = "XMonad";
+ };
+
+ wmCommand = mkOption {
+ type = types.str;
+ description = "The executable of the window manager to use.";
+ example = "\${pkgs.haskellPackages.xmonad}/bin/xmonad";
+ };
+ };
+ });
+ default = [];
+ description = "Other GNOME Flashback sessions to enable.";
+ };
+ };
+ };
+
+ environment.gnome3.excludePackages = mkOption {
+ default = [];
+ example = literalExample "[ pkgs.gnome3.totem ]";
+ type = types.listOf types.package;
+ description = "Which packages gnome should exclude from the default environment";
+ };
+
+ };
+
+ config = mkMerge [
+ (mkIf (cfg.enable || flashbackEnabled) {
+ services.gnome3.core-os-services.enable = true;
+ services.gnome3.core-shell.enable = true;
+ services.gnome3.core-utilities.enable = mkDefault true;
+
+ services.xserver.displayManager.extraSessionFilePackages = [ pkgs.gnome3.gnome-session ];
+
+ environment.extraInit = ''
+ ${concatMapStrings (p: ''
+ if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
+ export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
+ fi
+
+ if [ -d "${p}/lib/girepository-1.0" ]; then
+ export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
+ fi
+ '') cfg.sessionPath}
+ '';
+
+ environment.systemPackages = cfg.sessionPath;
+
+ environment.variables.GNOME_SESSION_DEBUG = mkIf cfg.debug "1";
+
+ # Override GSettings schemas
+ environment.variables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
+
+ # If gnome3 is installed, build vim for gtk3 too.
+ nixpkgs.config.vim.gui = "gtk3";
+ })
+
+ (mkIf flashbackEnabled {
+ services.xserver.displayManager.extraSessionFilePackages = map
+ (wm: pkgs.gnome3.gnome-flashback.mkSessionForWm {
+ inherit (wm) wmName wmLabel wmCommand;
+ }) (optional cfg.flashback.enableMetacity {
+ wmName = "metacity";
+ wmLabel = "Metacity";
+ wmCommand = "${pkgs.gnome3.metacity}/bin/metacity";
+ } ++ cfg.flashback.customSessions);
+
+ security.pam.services.gnome-screensaver = {
+ enableGnomeKeyring = true;
+ };
+
+ services.dbus.packages = [
+ pkgs.gnome3.gnome-screensaver
+ ];
+ })
+
+ (mkIf serviceCfg.core-os-services.enable {
+ hardware.bluetooth.enable = mkDefault true;
+ hardware.pulseaudio.enable = mkDefault true;
+ programs.dconf.enable = true;
+ security.polkit.enable = true;
+ services.accounts-daemon.enable = true;
+ services.dleyna-renderer.enable = mkDefault true;
+ services.dleyna-server.enable = mkDefault true;
+ services.gnome3.at-spi2-core.enable = true;
+ services.gnome3.evolution-data-server.enable = true;
+ services.gnome3.gnome-keyring.enable = true;
+ services.gnome3.gnome-online-accounts.enable = mkDefault true;
+ services.gnome3.gnome-online-miners.enable = true;
+ services.gnome3.tracker-miners.enable = mkDefault true;
+ services.gnome3.tracker.enable = mkDefault true;
+ services.hardware.bolt.enable = mkDefault true;
+ services.packagekit.enable = mkDefault true;
+ services.udisks2.enable = true;
+ services.upower.enable = config.powerManagement.enable;
+ services.xserver.libinput.enable = mkDefault true; # for controlling touchpad settings via gnome control center
+
+ xdg.portal.enable = true;
+ xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
+
+ networking.networkmanager.enable = mkDefault true;
+
+ # Use the correct gnome3 packageSet
+ networking.networkmanager.basePackages = {
+ inherit (pkgs) networkmanager modemmanager wpa_supplicant crda;
+ inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc
+ networkmanager-openconnect networkmanager-fortisslvpn
+ networkmanager-iodine networkmanager-l2tp;
+ };
+
+ services.xserver.updateDbusEnvironment = true;
+
+ # Needed for themes and backgrounds
+ environment.pathsToLink = [
+ "/share" # TODO: https://github.com/NixOS/nixpkgs/issues/47173
+ ];
+ })
+
+ (mkIf serviceCfg.core-shell.enable {
+ services.colord.enable = mkDefault true;
+ services.gnome3.chrome-gnome-shell.enable = mkDefault true;
+ services.gnome3.glib-networking.enable = true;
+ services.gnome3.gnome-remote-desktop.enable = mkDefault true;
+ services.gnome3.gnome-settings-daemon.enable = true;
+ services.gnome3.gnome-user-share.enable = mkDefault true;
+ services.gnome3.rygel.enable = mkDefault true;
+ services.gvfs.enable = true;
+ services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+ services.telepathy.enable = mkDefault true;
+ systemd.packages = [ pkgs.gnome3.vino ];
+
+ services.avahi.enable = mkDefault true;
+
+ xdg.portal.extraPortals = [ pkgs.gnome3.gnome-shell ];
+
+ services.geoclue2.enable = mkDefault true;
+ services.geoclue2.enableDemoAgent = false; # GNOME has its own geoclue agent
+
+ services.geoclue2.appConfig.gnome-datetime-panel = {
+ isAllowed = true;
+ isSystem = true;
+ };
+ services.geoclue2.appConfig.gnome-color-panel = {
+ isAllowed = true;
+ isSystem = true;
+ };
+ services.geoclue2.appConfig."org.gnome.Shell" = {
+ isAllowed = true;
+ isSystem = true;
+ };
+
+ fonts.fonts = with pkgs; [
+ cantarell-fonts
+ dejavu_fonts
+ source-code-pro # Default monospace font in 3.32
+ source-sans-pro
+ ];
+
+ # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-32/elements/core/meta-gnome-core-shell.bst
+ environment.systemPackages = with pkgs.gnome3; [
+ adwaita-icon-theme
+ gnome-backgrounds
+ gnome-bluetooth
+ gnome-color-manager
+ gnome-control-center
+ gnome-getting-started-docs
+ gnome-shell
+ gnome-shell-extensions
+ gnome-themes-extra
+ gnome-user-docs
+ pkgs.orca
+ pkgs.glib # for gsettings
+ pkgs.gnome-menus
+ pkgs.gtk3.out # for gtk-launch
+ pkgs.hicolor-icon-theme
+ pkgs.shared-mime-info # for update-mime-database
+ pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+ vino
+ ];
+ })
+
+ # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-32/elements/core/meta-gnome-core-utilities.bst
+ (mkIf serviceCfg.core-utilities.enable {
+ environment.systemPackages = (with pkgs.gnome3; removePackagesByName [
+ baobab
+ cheese
+ eog
+ epiphany
+ geary
+ gedit
+ gnome-calculator
+ gnome-calendar
+ gnome-characters
+ gnome-clocks
+ gnome-contacts
+ gnome-font-viewer
+ gnome-logs
+ gnome-maps
+ gnome-music
+ gnome-photos
+ gnome-screenshot
+ gnome-software
+ gnome-system-monitor
+ gnome-weather
+ nautilus
+ simple-scan
+ totem
+ yelp
+ # Unsure if sensible for NixOS
+ /* gnome-boxes */
+ ] config.environment.gnome3.excludePackages);
+
+ # Enable default programs
+ programs.evince.enable = mkDefault true;
+ programs.file-roller.enable = mkDefault true;
+ programs.gnome-disks.enable = mkDefault true;
+ programs.gnome-terminal.enable = mkDefault true;
+ programs.seahorse.enable = mkDefault true;
+ services.gnome3.sushi.enable = mkDefault true;
+
+ # Let nautilus find extensions
+ # TODO: Create nautilus-with-extensions package
+ environment.variables.NAUTILUS_EXTENSION_DIR = "${config.system.path}/lib/nautilus/extensions-3.0";
+
+ # Override default mimeapps for nautilus
+ environment.variables.XDG_DATA_DIRS = [ "${mimeAppsList}/share" ];
+
+ environment.pathsToLink = [
+ "/share/nautilus-python/extensions"
+ ];
+ })
+
+ (mkIf serviceCfg.games.enable {
+ environment.systemPackages = (with pkgs.gnome3; removePackagesByName [
+ aisleriot atomix five-or-more four-in-a-row gnome-chess gnome-klotski
+ gnome-mahjongg gnome-mines gnome-nibbles gnome-robots gnome-sudoku
+ gnome-taquin gnome-tetravex hitori iagno lightsoff quadrapassel
+ swell-foop tali
+ ] config.environment.gnome3.excludePackages);
+ })
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix
new file mode 100644
index 00000000000..65a7b9c628e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.desktopManager.kodi;
+in
+
+{
+ options = {
+ services.xserver.desktopManager.kodi = {
+ enable = mkOption {
+ default = false;
+ description = "Enable the kodi multimedia center.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.desktopManager.session = [{
+ name = "kodi";
+ start = ''
+ ${pkgs.kodi}/bin/kodi --lircdev /run/lirc/lircd --standalone &
+ waitPID=$!
+ '';
+ }];
+
+ environment.systemPackages = [ pkgs.kodi ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/lumina.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/lumina.nix
new file mode 100644
index 00000000000..2224bcd5a2a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/lumina.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ xcfg = config.services.xserver;
+ cfg = xcfg.desktopManager.lumina;
+
+in
+
+{
+ options = {
+
+ services.xserver.desktopManager.lumina.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the Lumina desktop manager";
+ };
+
+ };
+
+
+ config = mkIf cfg.enable {
+
+ services.xserver.desktopManager.session = singleton {
+ name = "lumina";
+ start = ''
+ exec ${pkgs.lumina.lumina}/bin/start-lumina-desktop
+ '';
+ };
+
+ environment.systemPackages =
+ pkgs.lumina.preRequisitePackages ++
+ pkgs.lumina.corePackages;
+
+ # Link some extra directories in /run/current-system/software/share
+ environment.pathsToLink = [
+ "/share/lumina"
+ # FIXME: modules should link subdirs of `/share` rather than relying on this
+ "/share"
+ ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/lxqt.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/lxqt.nix
new file mode 100644
index 00000000000..bf53082b267
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ xcfg = config.services.xserver;
+ cfg = xcfg.desktopManager.lxqt;
+
+in
+
+{
+ options = {
+
+ services.xserver.desktopManager.lxqt.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the LXQt desktop manager";
+ };
+
+ environment.lxqt.excludePackages = mkOption {
+ default = [];
+ example = literalExample "[ pkgs.lxqt.qterminal ]";
+ type = types.listOf types.package;
+ description = "Which LXQt packages to exclude from the default environment";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ services.xserver.desktopManager.session = singleton {
+ name = "lxqt";
+ bgSupport = true;
+ start = ''
+ # Upstream installs default configuration files in
+ # $prefix/share/lxqt instead of $prefix/etc/xdg, (arguably)
+ # giving distributors freedom to ship custom default
+ # configuration files more easily. In order to let the session
+ # manager find them the share subdirectory is added to the
+ # XDG_CONFIG_DIRS environment variable.
+ #
+ # For an explanation see
+ # https://github.com/lxqt/lxqt/issues/1521#issuecomment-405097453
+ #
+ export XDG_CONFIG_DIRS=$XDG_CONFIG_DIRS''${XDG_CONFIG_DIRS:+:}${config.system.path}/share
+
+ exec ${pkgs.lxqt.lxqt-session}/bin/startlxqt
+ '';
+ };
+
+ environment.systemPackages =
+ pkgs.lxqt.preRequisitePackages ++
+ pkgs.lxqt.corePackages ++
+ (pkgs.gnome3.removePackagesByName
+ pkgs.lxqt.optionalPackages
+ config.environment.lxqt.excludePackages);
+
+ # Link some extra directories in /run/current-system/software/share
+ environment.pathsToLink = [ "/share" ];
+
+ services.gvfs.enable = true;
+ services.gvfs.package = pkgs.gvfs;
+
+ services.upower.enable = config.powerManagement.enable;
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix
new file mode 100644
index 00000000000..4563583e070
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -0,0 +1,110 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ addToXDGDirs = p: ''
+ if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
+ export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
+ fi
+
+ if [ -d "${p}/lib/girepository-1.0" ]; then
+ export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
+ fi
+ '';
+
+ xcfg = config.services.xserver;
+ cfg = xcfg.desktopManager.mate;
+
+in
+
+{
+ options = {
+
+ services.xserver.desktopManager.mate = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the MATE desktop environment";
+ };
+
+ debug = mkEnableOption "mate-session debug messages";
+ };
+
+ environment.mate.excludePackages = mkOption {
+ default = [];
+ example = literalExample "[ pkgs.mate.mate-terminal pkgs.mate.pluma ]";
+ type = types.listOf types.package;
+ description = "Which MATE packages to exclude from the default environment";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ services.xserver.desktopManager.session = singleton {
+ name = "mate";
+ bgSupport = true;
+ start = ''
+ export XDG_MENU_PREFIX=mate-
+
+ # Let caja find extensions
+ export CAJA_EXTENSION_DIRS=$CAJA_EXTENSION_DIRS''${CAJA_EXTENSION_DIRS:+:}${config.system.path}/lib/caja/extensions-2.0
+
+ # Let caja extensions find gsettings schemas
+ ${concatMapStrings (p: ''
+ if [ -d "${p}/lib/caja/extensions-2.0" ]; then
+ ${addToXDGDirs p}
+ fi
+ '')
+ config.environment.systemPackages
+ }
+
+ # Let mate-panel find applets
+ export MATE_PANEL_APPLETS_DIR=$MATE_PANEL_APPLETS_DIR''${MATE_PANEL_APPLETS_DIR:+:}${config.system.path}/share/mate-panel/applets
+ export MATE_PANEL_EXTRA_MODULES=$MATE_PANEL_EXTRA_MODULES''${MATE_PANEL_EXTRA_MODULES:+:}${config.system.path}/lib/mate-panel/applets
+
+ # Add mate-control-center paths to some XDG variables because its schemas are needed by mate-settings-daemon, and mate-settings-daemon is a dependency for mate-control-center (that is, they are mutually recursive)
+ ${addToXDGDirs pkgs.mate.mate-control-center}
+
+ ${pkgs.mate.mate-session-manager}/bin/mate-session ${optionalString cfg.debug "--debug"} &
+ waitPID=$!
+ '';
+ };
+
+ environment.systemPackages =
+ pkgs.mate.basePackages ++
+ (pkgs.gnome3.removePackagesByName
+ pkgs.mate.extraPackages
+ config.environment.mate.excludePackages) ++
+ [
+ pkgs.desktop-file-utils
+ pkgs.glib
+ pkgs.gtk3.out
+ pkgs.shared-mime-info
+ pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
+ ];
+
+ programs.dconf.enable = true;
+ # Shell integration for VTE terminals
+ programs.bash.vteIntegration = mkDefault true;
+ programs.zsh.vteIntegration = mkDefault true;
+
+ # Mate uses this for printing
+ programs.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+
+ services.gnome3.at-spi2-core.enable = true;
+ services.gnome3.gnome-keyring.enable = true;
+ services.gnome3.gnome-settings-daemon.enable = true;
+ services.gnome3.gnome-settings-daemon.package = pkgs.mate.mate-settings-daemon;
+ services.gvfs.enable = true;
+ services.upower.enable = config.powerManagement.enable;
+
+ security.pam.services.mate-screensaver.unixAuth = true;
+
+ environment.pathsToLink = [ "/share" ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/maxx.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/maxx.nix
new file mode 100644
index 00000000000..1c04104df41
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/maxx.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ xcfg = config.services.xserver;
+ cfg = xcfg.desktopManager.maxx;
+in {
+ options.services.xserver.desktopManager.maxx = {
+ enable = mkEnableOption "MaXX desktop environment";
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.maxx ];
+
+ # there is hardcoded path in binaries
+ system.activationScripts.setup-maxx = ''
+ mkdir -p /opt
+ ln -sfn ${pkgs.maxx}/opt/MaXX /opt
+ '';
+
+ services.xserver.desktopManager.session = [
+ { name = "MaXX";
+ start = ''
+ exec ${pkgs.maxx}/opt/MaXX/etc/skel/Xsession.dt
+ '';
+ }];
+ };
+
+ meta.maintainers = [ maintainers.gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix
new file mode 100644
index 00000000000..af7a376ae02
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix
@@ -0,0 +1,7 @@
+{
+ services.xserver.desktopManager.session =
+ [ { name = "none";
+ start = "";
+ }
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix
new file mode 100644
index 00000000000..d80ea9a53e8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -0,0 +1,218 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver.desktopManager.pantheon;
+
+ nixos-gsettings-desktop-schemas = pkgs.pantheon.elementary-gsettings-schemas.override {
+ extraGSettingsOverridePackages = cfg.extraGSettingsOverridePackages;
+ extraGSettingsOverrides = cfg.extraGSettingsOverrides;
+ };
+
+in
+
+{
+
+ meta.maintainers = pkgs.pantheon.maintainers;
+
+ options = {
+
+ services.xserver.desktopManager.pantheon = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the pantheon desktop manager";
+ };
+
+ sessionPath = mkOption {
+ default = [];
+ example = literalExample "[ pkgs.gnome3.gpaste ]";
+ description = ''
+ Additional list of packages to be added to the session search path.
+ Useful for GSettings-conditional autostart.
+
+ Note that this should be a last resort; patching the package is preferred (see GPaste).
+ '';
+ apply = list: list ++
+ [
+ pkgs.pantheon.pantheon-agent-geoclue2
+ ];
+ };
+
+ extraGSettingsOverrides = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Additional gsettings overrides.";
+ };
+
+ extraGSettingsOverridePackages = mkOption {
+ default = [];
+ type = types.listOf types.path;
+ description = "List of packages for which gsettings are overridden.";
+ };
+
+ debug = mkEnableOption "gnome-session debug messages";
+
+ };
+
+ environment.pantheon.excludePackages = mkOption {
+ default = [];
+ example = literalExample "[ pkgs.pantheon.elementary-camera ]";
+ type = types.listOf types.package;
+ description = "Which packages pantheon should exclude from the default environment";
+ };
+
+ };
+
+
+ config = mkIf cfg.enable {
+
+ services.xserver.displayManager.extraSessionFilePackages = [ pkgs.pantheon.elementary-session-settings ];
+
+ # Ensure lightdm is used when Pantheon is enabled
+ # Without it screen locking will be nonfunctional because of the use of lightlocker
+
+ warnings = optional (config.services.xserver.displayManager.lightdm.enable != true)
+ ''
+ Using Pantheon without LightDM as a displayManager will break screenlocking from the UI.
+ '';
+
+ services.xserver.displayManager.lightdm.greeters.pantheon.enable = mkDefault true;
+
+ # If not set manually Pantheon session cannot be started
+ # Known issue of https://github.com/NixOS/nixpkgs/pull/43992
+ services.xserver.desktopManager.default = mkForce "pantheon";
+
+ services.xserver.displayManager.sessionCommands = ''
+ if test "$XDG_CURRENT_DESKTOP" = "Pantheon"; then
+ ${concatMapStrings (p: ''
+ if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
+ export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
+ fi
+
+ if [ -d "${p}/lib/girepository-1.0" ]; then
+ export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
+ fi
+ '') cfg.sessionPath}
+ fi
+ '';
+
+ hardware.bluetooth.enable = mkDefault true;
+ hardware.pulseaudio.enable = mkDefault true;
+ security.polkit.enable = true;
+ services.accounts-daemon.enable = true;
+ services.bamf.enable = true;
+ services.colord.enable = mkDefault true;
+ services.pantheon.files.enable = mkDefault true;
+ services.tumbler.enable = mkDefault true;
+ services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+ services.dbus.packages = [
+ pkgs.pantheon.switchboard-plug-power
+ ];
+ services.pantheon.contractor.enable = mkDefault true;
+ services.gnome3.at-spi2-core.enable = true;
+ services.gnome3.evolution-data-server.enable = true;
+ services.gnome3.glib-networking.enable = true;
+ # TODO: gnome-keyring's xdg autostarts will still be in the environment (from elementary-session-settings) if disabled forcefully
+ services.gnome3.gnome-keyring.enable = true;
+ services.gnome3.gnome-settings-daemon.enable = true;
+ services.gnome3.gnome-settings-daemon.package = pkgs.pantheon.elementary-settings-daemon;
+ services.gvfs.enable = true;
+ services.gnome3.rygel.enable = mkDefault true;
+ services.gsignond.enable = mkDefault true;
+ services.gsignond.plugins = with pkgs.gsignondPlugins; [ lastfm mail oauth ];
+ services.udisks2.enable = true;
+ services.upower.enable = config.powerManagement.enable;
+ services.xserver.libinput.enable = mkDefault true;
+ services.xserver.updateDbusEnvironment = true;
+ services.zeitgeist.enable = mkDefault true;
+ services.geoclue2.enable = mkDefault true;
+ # pantheon has pantheon-agent-geoclue2
+ services.geoclue2.enableDemoAgent = false;
+ services.geoclue2.appConfig."io.elementary.desktop.agent-geoclue2" = {
+ isAllowed = true;
+ isSystem = true;
+ };
+
+ programs.dconf.enable = true;
+ programs.evince.enable = mkDefault true;
+ programs.file-roller.enable = mkDefault true;
+ # Otherwise you can't store NetworkManager Secrets with
+ # "Store the password only for this user"
+ programs.nm-applet.enable = true;
+
+ # Shell integration for VTE terminals
+ programs.bash.vteIntegration = mkDefault true;
+ programs.zsh.vteIntegration = mkDefault true;
+
+ # Harmonize Qt5 applications under Pantheon
+ qt5.enable = true;
+ qt5.platformTheme = "gnome";
+ qt5.style = "adwaita";
+
+ networking.networkmanager.enable = mkDefault true;
+ networking.networkmanager.basePackages =
+ { inherit (pkgs) networkmanager modemmanager wpa_supplicant crda;
+ inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc
+ networkmanager-openconnect networkmanager-fortisslvpn
+ networkmanager-iodine networkmanager-l2tp; };
+
+ # Override GSettings schemas
+ environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
+
+ environment.sessionVariables.GNOME_SESSION_DEBUG = optionalString cfg.debug "1";
+
+ # Settings from elementary-default-settings
+ environment.sessionVariables.GTK_CSD = "1";
+ environment.sessionVariables.GTK_MODULES = "pantheon-filechooser-module";
+ environment.etc."gtk-3.0/settings.ini".source = "${pkgs.pantheon.elementary-default-settings}/etc/gtk-3.0/settings.ini";
+
+ environment.pathsToLink = [
+ # FIXME: modules should link subdirs of `/share` rather than relying on this
+ "/share"
+ ];
+
+ environment.systemPackages =
+ pkgs.pantheon.artwork ++ pkgs.pantheon.desktop ++ pkgs.pantheon.services ++ cfg.sessionPath
+ ++ (with pkgs; gnome3.removePackagesByName
+ ([
+ gnome3.geary
+ gnome3.epiphany
+ gnome3.gnome-font-viewer
+ ] ++ pantheon.apps) config.environment.pantheon.excludePackages)
+ ++ (with pkgs;
+ [
+ adwaita-qt
+ desktop-file-utils
+ glib
+ glib-networking
+ gnome-menus
+ gnome3.adwaita-icon-theme
+ gtk3.out
+ hicolor-icon-theme
+ lightlocker
+ onboard
+ plank
+ qgnomeplatform
+ shared-mime-info
+ sound-theme-freedesktop
+ xdg-user-dirs
+ ]);
+
+ fonts.fonts = with pkgs; [
+ open-sans
+ roboto-mono
+ pantheon.elementary-redacted-script # needed by screenshot-tool
+ ];
+
+ fonts.fontconfig.defaultFonts = {
+ monospace = [ "Roboto Mono" ];
+ sansSerif = [ "Open Sans" ];
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix
new file mode 100644
index 00000000000..b10755df4dc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -0,0 +1,266 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ xcfg = config.services.xserver;
+ cfg = xcfg.desktopManager.plasma5;
+
+ inherit (pkgs) kdeApplications plasma5 libsForQt5 qt5;
+
+in
+
+{
+ options = {
+
+ services.xserver.desktopManager.plasma5 = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the Plasma 5 (KDE 5) desktop environment.";
+ };
+
+ phononBackend = mkOption {
+ type = types.enum [ "gstreamer" "vlc" ];
+ default = "gstreamer";
+ example = "vlc";
+ description = "Phonon audio backend to install.";
+ };
+
+ enableQt4Support = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable support for Qt 4-based applications. Particularly, install a
+ default backend for Phonon.
+ '';
+ };
+
+ };
+
+ };
+
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ services.xserver.desktopManager.session = singleton {
+ name = "plasma5";
+ bgSupport = true;
+ start = ''
+ # Load PulseAudio module for routing support.
+ # See http://colin.guthr.ie/2009/10/so-how-does-the-kde-pulseaudio-support-work-anyway/
+ ${optionalString config.hardware.pulseaudio.enable ''
+ ${getBin config.hardware.pulseaudio.package}/bin/pactl load-module module-device-manager "do_routing=1"
+ ''}
+
+ if [ -f "$HOME/.config/kdeglobals" ]
+ then
+ # Remove extraneous font style names.
+ # See also: https://phabricator.kde.org/D9070
+ ${getBin pkgs.gnused}/bin/sed -i "$HOME/.config/kdeglobals" \
+ -e '/^fixed=/ s/,Regular$//' \
+ -e '/^font=/ s/,Regular$//' \
+ -e '/^menuFont=/ s/,Regular$//' \
+ -e '/^smallestReadableFont=/ s/,Regular$//' \
+ -e '/^toolBarFont=/ s/,Regular$//'
+ fi
+
+ exec "${getBin plasma5.plasma-workspace}/bin/startkde"
+ '';
+ };
+
+ security.wrappers = {
+ kcheckpass.source = "${lib.getBin plasma5.kscreenlocker}/libexec/kcheckpass";
+ start_kdeinit.source = "${lib.getBin pkgs.kinit}/libexec/kf5/start_kdeinit";
+ kwin_wayland = {
+ source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland";
+ capabilities = "cap_sys_nice+ep";
+ };
+ };
+
+ environment.systemPackages = with pkgs; with qt5; with libsForQt5; with plasma5; with kdeApplications;
+ [
+ frameworkintegration
+ kactivities
+ kauth
+ kcmutils
+ kconfig
+ kconfigwidgets
+ kcoreaddons
+ kdoctools
+ kdbusaddons
+ kdeclarative
+ kded
+ kdesu
+ kdnssd
+ kemoticons
+ kfilemetadata
+ kglobalaccel
+ kguiaddons
+ kiconthemes
+ kidletime
+ kimageformats
+ kinit
+ kio
+ kjobwidgets
+ knewstuff
+ knotifications
+ knotifyconfig
+ kpackage
+ kparts
+ kpeople
+ krunner
+ kservice
+ ktextwidgets
+ kwallet
+ kwallet-pam
+ kwalletmanager
+ kwayland
+ kwidgetsaddons
+ kxmlgui
+ kxmlrpcclient
+ plasma-framework
+ solid
+ sonnet
+ threadweaver
+
+ breeze-qt5
+ kactivitymanagerd
+ kde-cli-tools
+ kdecoration
+ kdeplasma-addons
+ kgamma5
+ khotkeys
+ kinfocenter
+ kmenuedit
+ kscreen
+ kscreenlocker
+ ksysguard
+ kwayland
+ kwin
+ kwrited
+ libkscreen
+ libksysguard
+ milou
+ plasma-integration
+ polkit-kde-agent
+ systemsettings
+
+ plasma-desktop
+ plasma-workspace
+ plasma-workspace-wallpapers
+
+ dolphin
+ dolphin-plugins
+ ffmpegthumbs
+ kdegraphics-thumbnailers
+ khelpcenter
+ kio-extras
+ konsole
+ oxygen
+ print-manager
+
+ breeze-icons
+ pkgs.hicolor-icon-theme
+
+ kde-gtk-config breeze-gtk
+
+ qtvirtualkeyboard
+
+ xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
+ ]
+
+ # Phonon audio backend
+ ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer
+ ++ lib.optional (cfg.phononBackend == "gstreamer" && cfg.enableQt4Support) pkgs.phonon-backend-gstreamer
+ ++ lib.optional (cfg.phononBackend == "vlc") libsForQt5.phonon-backend-vlc
+ ++ lib.optional (cfg.phononBackend == "vlc" && cfg.enableQt4Support) pkgs.phonon-backend-vlc
+
+ # Optional hardware support features
+ ++ lib.optionals config.hardware.bluetooth.enable [ bluedevil bluez-qt ]
+ ++ lib.optional config.networking.networkmanager.enable plasma-nm
+ ++ lib.optional config.hardware.pulseaudio.enable plasma-pa
+ ++ lib.optional config.powerManagement.enable powerdevil
+ ++ lib.optional config.services.colord.enable colord-kde
+ ++ lib.optionals config.services.samba.enable [ kdenetwork-filesharing pkgs.samba ];
+
+ environment.pathsToLink = [
+ # FIXME: modules should link subdirs of `/share` rather than relying on this
+ "/share"
+ ];
+
+ environment.etc = singleton {
+ source = xcfg.xkbDir;
+ target = "X11/xkb";
+ };
+
+ # Enable GTK applications to load SVG icons
+ services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
+
+ fonts.fonts = with pkgs; [ noto-fonts hack-font ];
+ fonts.fontconfig.defaultFonts = {
+ monospace = [ "Hack" "Noto Mono" ];
+ sansSerif = [ "Noto Sans" ];
+ serif = [ "Noto Serif" ];
+ };
+
+ programs.ssh.askPassword = mkDefault "${plasma5.ksshaskpass.out}/bin/ksshaskpass";
+
+ # Enable helpful DBus services.
+ services.udisks2.enable = true;
+ services.upower.enable = config.powerManagement.enable;
+ services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+ services.xserver.libinput.enable = mkDefault true;
+
+ # Extra UDEV rules used by Solid
+ services.udev.packages = [
+ pkgs.libmtp
+ pkgs.media-player-info
+ ];
+
+ services.xserver.displayManager.sddm = {
+ theme = mkDefault "breeze";
+ };
+
+ security.pam.services.kde = { allowNullPassword = true; };
+
+ # Doing these one by one seems silly, but we currently lack a better
+ # construct for handling common pam configs.
+ security.pam.services.gdm.enableKwallet = true;
+ security.pam.services.kdm.enableKwallet = true;
+ security.pam.services.lightdm.enableKwallet = true;
+ security.pam.services.sddm.enableKwallet = true;
+ security.pam.services.slim.enableKwallet = true;
+
+ xdg.portal.enable = true;
+ xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-kde ];
+
+ # Update the start menu for each user that is currently logged in
+ system.userActivationScripts.plasmaSetup = ''
+ # The KDE icon cache is supposed to update itself
+ # automatically, but it uses the timestamp on the icon
+ # theme directory as a trigger. Since in Nix the
+ # timestamp is always the same, this doesn't work. So as
+ # a workaround, nuke the icon cache on login. This isn't
+ # perfect, since it may require logging out after
+ # installing new applications to update the cache.
+ # See http://lists-archives.org/kde-devel/26175-what-when-will-icon-cache-refresh.html
+ rm -fv $HOME/.cache/icon-cache.kcache
+
+ # xdg-desktop-settings generates this empty file but
+ # it makes kbuildsyscoca5 fail silently. To fix this
+ # remove that menu if it exists.
+ rm -fv $HOME/.config/menus/applications-merged/xdg-desktop-menu-dummy.menu
+
+ # Remove the kbuildsyscoca5 cache. It will be regenerated
+ # immediately after. This is necessary for kbuildsyscoca5 to
+ # recognize that software that has been removed.
+ rm -fv $HOME/.cache/ksycoca*
+
+ ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5
+ '';
+ })
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix
new file mode 100644
index 00000000000..140dde828da
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.desktopManager.surf-display;
+
+ surfDisplayConf = ''
+ # Surf Kiosk Display: Wrap around surf browser and turn your
+ # system into a browser screen in KIOSK-mode.
+
+ # default download URI for all display screens if not configured individually
+ DEFAULT_WWW_URI="${cfg.defaultWwwUri}"
+
+ # Enforce fixed resolution for all displays (default: not set):
+ #DEFAULT_RESOLUTION="1920x1080"
+
+ # HTTP proxy URL, if needed (default: not set).
+ #HTTP_PROXY_URL="http://webcache:3128"
+
+ # Setting for internal inactivity timer to restart surf-display
+ # if the user goes inactive/idle.
+ INACTIVITY_INTERVAL="${builtins.toString cfg.inactivityInterval}"
+
+ # log to syslog instead of .xsession-errors
+ LOG_TO_SYSLOG="yes"
+
+ # Launch pulseaudio daemon if not already running.
+ WITH_PULSEAUDIO="yes"
+
+ # screensaver settings, see "man 1 xset" for possible options
+ SCREENSAVER_SETTINGS="${cfg.screensaverSettings}"
+
+ # disable right and middle pointer device click in browser sessions while keeping
+ # scrolling wheels' functionality intact... (consider "pointer" subcommand on
+ # xmodmap man page for details).
+ POINTER_BUTTON_MAP="${cfg.pointerButtonMap}"
+
+ # Hide idle mouse pointer.
+ HIDE_IDLE_POINTER="${cfg.hideIdlePointer}"
+
+ ${cfg.extraConfig}
+ '';
+
+in {
+ options = {
+ services.xserver.desktopManager.surf-display = {
+ enable = mkEnableOption "surf-display as a kiosk browser session";
+
+ defaultWwwUri = mkOption {
+ type = types.str;
+ default = "${pkgs.surf-display}/share/surf-display/empty-page.html";
+ example = "https://www.example.com/";
+ description = "Default URI to display.";
+ };
+
+ inactivityInterval = mkOption {
+ type = types.int;
+ default = 300;
+ example = "0";
+ description = ''
+ Setting for internal inactivity timer to restart surf-display if the
+ user goes inactive/idle to get a fresh session for the next user of
+ the kiosk.
+
+ If this value is set to zero, the whole feature of restarting due to
+ inactivity is disabled.
+ '';
+ };
+
+ screensaverSettings = mkOption {
+ type = types.separatedString " ";
+ default = "";
+ description = ''
+ Screensaver settings, see <literal>man 1 xset</literal> for possible options.
+ '';
+ };
+
+ pointerButtonMap = mkOption {
+ type = types.str;
+ default = "1 0 0 4 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+ description = ''
+ Disable right and middle pointer device click in browser sessions
+ while keeping scrolling wheels' functionality intact. See pointer
+ subcommand on <literal>man xmodmap</literal> for details.
+ '';
+ };
+
+ hideIdlePointer = mkOption {
+ type = types.str;
+ default = "yes";
+ example = "no";
+ description = "Hide idle mouse pointer.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ # Enforce fixed resolution for all displays (default: not set):
+ DEFAULT_RESOLUTION="1920x1080"
+
+ # HTTP proxy URL, if needed (default: not set).
+ HTTP_PROXY_URL="http://webcache:3128"
+
+ # Configure individual display screens with host specific parameters:
+ DISPLAYS['display-host-0']="www_uri=https://www.displayserver.comany.net/display-1/index.html"
+ DISPLAYS['display-host-1']="www_uri=https://www.displayserver.comany.net/display-2/index.html"
+ DISPLAYS['display-host-2']="www_uri=https://www.displayserver.comany.net/display-3/index.html|res=1920x1280"
+ DISPLAYS['display-host-3']="www_uri=https://www.displayserver.comany.net/display-4/index.html"|res=1280x1024"
+ DISPLAYS['display-host-local-file']="www_uri=file:///usr/share/doc/surf-display/empty-page.html"
+ '';
+ description = ''
+ Extra configuration options to append to <literal>/etc/default/surf-display</literal>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.displayManager.extraSessionFilePackages = [
+ pkgs.surf-display
+ ];
+
+ environment.etc."default/surf-display".text = surfDisplayConf;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix
new file mode 100644
index 00000000000..6965c6d2646
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -0,0 +1,123 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.desktopManager.xfce;
+in
+
+{
+ options = {
+ services.xserver.desktopManager.xfce = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the Xfce desktop environment.";
+ };
+
+ thunarPlugins = mkOption {
+ default = [];
+ type = types.listOf types.package;
+ example = literalExample "[ pkgs.xfce.thunar-archive-plugin ]";
+ description = ''
+ A list of plugin that should be installed with Thunar.
+ '';
+ };
+
+ noDesktop = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Don't install XFCE desktop components (xfdesktop, panel and notification daemon).";
+ };
+
+ extraSessionCommands = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands executed just before XFCE is started.
+ '';
+ };
+
+ enableXfwm = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Enable the XFWM (default) window manager.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs.xfce // pkgs; [
+ # Get GTK themes and gtk-update-icon-cache
+ gtk2.out
+
+ # Supplies some abstract icons such as:
+ # utilities-terminal, accessories-text-editor
+ gnome3.adwaita-icon-theme
+
+ hicolor-icon-theme
+ tango-icon-theme
+ xfce4-icon-theme
+
+ # Needed by Xfce's xinitrc script
+ # TODO: replace with command -v
+ which
+
+ exo
+ garcon
+ gtk-xfce-engine
+ libxfce4ui
+ tumbler
+ xfconf
+
+ mousepad
+ ristretto
+ xfce4-appfinder
+ xfce4-screenshooter
+ xfce4-session
+ xfce4-settings
+ xfce4-terminal
+
+ (thunar.override { thunarPlugins = cfg.thunarPlugins; })
+ thunar-volman # TODO: drop
+ ] ++ (if config.hardware.pulseaudio.enable
+ then [ xfce4-mixer-pulse xfce4-volumed-pulse ]
+ else [ xfce4-mixer xfce4-volumed ])
+ # TODO: NetworkManager doesn't belong here
+ ++ optionals config.networking.networkmanager.enable [ networkmanagerapplet ]
+ ++ optionals config.powerManagement.enable [ xfce4-power-manager ]
+ ++ optionals cfg.enableXfwm [ xfwm4 ]
+ ++ optionals (!cfg.noDesktop) [
+ xfce4-panel
+ xfce4-notifyd
+ xfdesktop
+ ];
+
+ environment.pathsToLink = [
+ "/share/xfce4"
+ "/share/themes"
+ "/share/gtksourceview-2.0"
+ ];
+
+ services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
+
+ services.xserver.desktopManager.session = [{
+ name = "xfce";
+ bgSupport = true;
+ start = ''
+ ${cfg.extraSessionCommands}
+
+ ${pkgs.runtimeShell} ${pkgs.xfce.xinitrc} &
+ waitPID=$!
+ '';
+ }];
+
+ services.xserver.updateDbusEnvironment = true;
+
+ # Enable helpful DBus services.
+ services.udisks2.enable = true;
+ services.upower.enable = config.powerManagement.enable;
+ services.gvfs.enable = true;
+ services.gvfs.package = pkgs.xfce.gvfs;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce4-14.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce4-14.nix
new file mode 100644
index 00000000000..130e865a1fb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce4-14.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.desktopManager.xfce4-14;
+in
+
+{
+ # added 2019-08-18
+ # needed to preserve some semblance of UI familarity
+ # with original XFCE module
+ imports = [
+ (mkRenamedOptionModule
+ [ "services" "xserver" "desktopManager" "xfce4-14" "extraSessionCommands" ]
+ [ "services" "xserver" "displayManager" "sessionCommands" ])
+ ];
+
+ options = {
+ services.xserver.desktopManager.xfce4-14 = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable the Xfce desktop environment.";
+ };
+
+ # TODO: support thunar plugins
+ # thunarPlugins = mkOption {
+ # default = [];
+ # type = types.listOf types.package;
+ # example = literalExample "[ pkgs.xfce4-14.thunar-archive-plugin ]";
+ # description = ''
+ # A list of plugin that should be installed with Thunar.
+ # '';
+ # };
+
+ noDesktop = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Don't install XFCE desktop components (xfdesktop, panel and notification daemon).";
+ };
+
+ enableXfwm = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Enable the XFWM (default) window manager.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs.xfce4-14 // pkgs; [
+ glib # for gsettings
+ gtk3.out # gtk-update-icon-cache
+
+ gnome3.gnome-themes-extra
+ gnome3.adwaita-icon-theme
+ hicolor-icon-theme
+ tango-icon-theme
+ xfce4-icon-theme
+
+ desktop-file-utils
+ shared-mime-info # for update-mime-database
+
+ # For a polkit authentication agent
+ polkit_gnome
+
+ # Needed by Xfce's xinitrc script
+ xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
+
+ exo
+ garcon
+ libxfce4ui
+ xfconf
+
+ mousepad
+ parole
+ ristretto
+ xfce4-appfinder
+ xfce4-screenshooter
+ xfce4-session
+ xfce4-settings
+ xfce4-taskmanager
+ xfce4-terminal
+
+ # TODO: resync patch for plugins
+ #(thunar.override { thunarPlugins = cfg.thunarPlugins; })
+ thunar
+ ] # TODO: NetworkManager doesn't belong here
+ ++ optional config.networking.networkmanager.enable networkmanagerapplet
+ ++ optional config.powerManagement.enable xfce4-power-manager
+ ++ optionals config.hardware.pulseaudio.enable [
+ pavucontrol
+ xfce4-pulseaudio-plugin
+ ] ++ optionals cfg.enableXfwm [
+ xfwm4
+ xfwm4-themes
+ ] ++ optionals (!cfg.noDesktop) [
+ xfce4-notifyd
+ xfce4-panel
+ xfdesktop
+ ];
+
+ environment.pathsToLink = [
+ "/share/xfce4"
+ "/lib/xfce4"
+ "/share/gtksourceview-3.0"
+ "/share/gtksourceview-4.0"
+ ];
+
+ # Use the correct gnome3 packageSet
+ networking.networkmanager.basePackages = mkIf config.networking.networkmanager.enable {
+ inherit (pkgs) networkmanager modemmanager wpa_supplicant crda;
+ inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc
+ networkmanager-openconnect networkmanager-fortisslvpn
+ networkmanager-iodine networkmanager-l2tp;
+ };
+
+ services.xserver.desktopManager.session = [{
+ name = "xfce4-14";
+ bgSupport = true;
+ start = ''
+ ${pkgs.runtimeShell} ${pkgs.xfce4-14.xinitrc} &
+ waitPID=$!
+ '';
+ }];
+
+ services.xserver.updateDbusEnvironment = true;
+ services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
+
+ # Enable helpful DBus services.
+ services.udisks2.enable = true;
+ security.polkit.enable = true;
+ services.accounts-daemon.enable = true;
+ services.upower.enable = config.powerManagement.enable;
+ services.gnome3.glib-networking.enable = true;
+ services.gvfs.enable = true;
+ services.gvfs.package = pkgs.xfce.gvfs;
+ services.tumbler.enable = true;
+ services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+ services.xserver.libinput.enable = mkDefault true; # used in xfce4-settings-manager
+
+ # Enable default programs
+ programs.dconf.enable = true;
+
+ # Shell integration for VTE terminals
+ programs.bash.vteIntegration = mkDefault true;
+ programs.zsh.vteIntegration = mkDefault true;
+
+ # Systemd services
+ systemd.packages = with pkgs.xfce4-14; [
+ thunar
+ ] ++ optional (!cfg.noDesktop) xfce4-notifyd;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/xterm.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/xterm.nix
new file mode 100644
index 00000000000..f76db278a92
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/xterm.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver.desktopManager.xterm;
+ xSessionEnabled = config.services.xserver.enable;
+
+in
+
+{
+ options = {
+
+ services.xserver.desktopManager.xterm.enable = mkOption {
+ type = types.bool;
+ default = (versionOlder config.system.stateVersion "19.09") && xSessionEnabled;
+ defaultText = if versionOlder config.system.stateVersion "19.09" then "config.services.xserver.enable" else "false";
+ description = "Enable a xterm terminal as a desktop manager.";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ services.xserver.desktopManager.session = singleton
+ { name = "xterm";
+ start = ''
+ ${pkgs.xterm}/bin/xterm -ls &
+ waitPID=$!
+ '';
+ };
+
+ environment.systemPackages = [ pkgs.xterm ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/auto.nix b/nixpkgs/nixos/modules/services/x11/display-managers/auto.nix
new file mode 100644
index 00000000000..1068a344e0c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/auto.nix
@@ -0,0 +1,68 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+
+ dmcfg = config.services.xserver.displayManager;
+ cfg = dmcfg.auto;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.xserver.displayManager.auto = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable the fake "auto" display manager, which
+ automatically logs in the user specified in the
+ <option>user</option> option. This is mostly useful for
+ automated tests.
+ '';
+ };
+
+ user = mkOption {
+ default = "root";
+ description = "The user account to login automatically.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.xserver.displayManager.lightdm = {
+ enable = true;
+ autoLogin = {
+ enable = true;
+ user = cfg.user;
+ };
+ };
+
+ # lightdm by default doesn't allow auto login for root, which is
+ # required by some nixos tests. Override it here.
+ security.pam.services.lightdm-autologin.text = lib.mkForce ''
+ auth requisite pam_nologin.so
+ auth required pam_succeed_if.so quiet
+ auth required pam_permit.so
+
+ account include lightdm
+
+ password include lightdm
+
+ session include lightdm
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/default.nix b/nixpkgs/nixos/modules/services/x11/display-managers/default.nix
new file mode 100644
index 00000000000..bf6b048654b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/default.nix
@@ -0,0 +1,377 @@
+# This module declares the options to define a *display manager*, the
+# program responsible for handling X logins (such as xdm, gdb, or
+# SLiM). The display manager allows the user to select a *session
+# type*. When the user logs in, the display manager starts the
+# *session script* ("xsession" below) to launch the selected session
+# type. The session type defines two things: the *desktop manager*
+# (e.g., KDE, Gnome or a plain xterm), and optionally the *window
+# manager* (e.g. kwin or twm).
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver;
+ xorg = pkgs.xorg;
+
+ fontconfig = config.fonts.fontconfig;
+ xresourcesXft = pkgs.writeText "Xresources-Xft" ''
+ ${optionalString (fontconfig.dpi != 0) ''Xft.dpi: ${toString fontconfig.dpi}''}
+ Xft.antialias: ${if fontconfig.antialias then "1" else "0"}
+ Xft.rgba: ${fontconfig.subpixel.rgba}
+ Xft.lcdfilter: lcd${fontconfig.subpixel.lcdfilter}
+ Xft.hinting: ${if fontconfig.hinting.enable then "1" else "0"}
+ Xft.autohint: ${if fontconfig.hinting.autohint then "1" else "0"}
+ Xft.hintstyle: hintslight
+ '';
+
+ mkCases = session:
+ concatStrings (
+ mapAttrsToList (name: starts: ''
+ (${name})
+ ${concatMapStringsSep "\n " (n: n.start) starts}
+ ;;
+ '') (lib.groupBy (n: n.name) session)
+ );
+
+ # file provided by services.xserver.displayManager.session.wrapper
+ xsessionWrapper = pkgs.writeScript "xsession-wrapper"
+ ''
+ #! ${pkgs.bash}/bin/bash
+
+ # Shared environment setup for graphical sessions.
+
+ . /etc/profile
+ cd "$HOME"
+
+ ${optionalString cfg.startDbusSession ''
+ if test -z "$DBUS_SESSION_BUS_ADDRESS"; then
+ exec ${pkgs.dbus.dbus-launch} --exit-with-session "$0" "$@"
+ fi
+ ''}
+
+ ${optionalString cfg.displayManager.job.logToJournal ''
+ if [ -z "$_DID_SYSTEMD_CAT" ]; then
+ export _DID_SYSTEMD_CAT=1
+ exec ${config.systemd.package}/bin/systemd-cat -t xsession "$0" "$@"
+ fi
+ ''}
+
+ ${optionalString cfg.displayManager.job.logToFile ''
+ exec &> >(tee ~/.xsession-errors)
+ ''}
+
+ # Start PulseAudio if enabled.
+ ${optionalString (config.hardware.pulseaudio.enable) ''
+ # Publish access credentials in the root window.
+ if ${config.hardware.pulseaudio.package.out}/bin/pulseaudio --dump-modules | grep module-x11-publish &> /dev/null; then
+ ${config.hardware.pulseaudio.package.out}/bin/pactl load-module module-x11-publish "display=$DISPLAY"
+ fi
+ ''}
+
+ # Tell systemd about our $DISPLAY and $XAUTHORITY.
+ # This is needed by the ssh-agent unit.
+ #
+ # Also tell systemd about the dbus session bus address.
+ # This is required by user units using the session bus.
+ ${config.systemd.package}/bin/systemctl --user import-environment DISPLAY XAUTHORITY DBUS_SESSION_BUS_ADDRESS
+
+ # Load X defaults. This should probably be safe on wayland too.
+ ${xorg.xrdb}/bin/xrdb -merge ${xresourcesXft}
+ if test -e ~/.Xresources; then
+ ${xorg.xrdb}/bin/xrdb -merge ~/.Xresources
+ elif test -e ~/.Xdefaults; then
+ ${xorg.xrdb}/bin/xrdb -merge ~/.Xdefaults
+ fi
+
+ # Speed up application start by 50-150ms according to
+ # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html
+ rm -rf "$HOME/.compose-cache"
+ mkdir "$HOME/.compose-cache"
+
+ # Work around KDE errors when a user first logs in and
+ # .local/share doesn't exist yet.
+ mkdir -p "$HOME/.local/share"
+
+ unset _DID_SYSTEMD_CAT
+
+ ${cfg.displayManager.sessionCommands}
+
+ # Allow the user to execute commands at the beginning of the X session.
+ if test -f ~/.xprofile; then
+ source ~/.xprofile
+ fi
+
+ # Start systemd user services for graphical sessions
+ ${config.systemd.package}/bin/systemctl --user start graphical-session.target
+
+ # Allow the user to setup a custom session type.
+ if test -x ~/.xsession; then
+ eval exec ~/.xsession "$@"
+ fi
+
+ if test "$1"; then
+ # Run the supplied session command. Remove any double quotes with eval.
+ eval exec "$@"
+ else
+ # Fall back to the default window/desktopManager
+ exec ${cfg.displayManager.session.script}
+ fi
+ '';
+
+ # file provided by services.xserver.displayManager.session.script
+ xsession = wm: dm: pkgs.writeScript "xsession"
+ ''
+ #! ${pkgs.bash}/bin/bash
+
+ # Legacy session script used to construct .desktop files from
+ # `services.xserver.displayManager.session` entries. Called from
+ # `sessionWrapper`.
+
+ # Expected parameters:
+ # $1 = <desktop-manager>+<window-manager>
+
+ # The first argument of this script is the session type.
+ sessionType="$1"
+ if [ "$sessionType" = default ]; then sessionType=""; fi
+
+ # The session type is "<desktop-manager>+<window-manager>", so
+ # extract those (see:
+ # http://wiki.bash-hackers.org/syntax/pe#substring_removal).
+ windowManager="''${sessionType##*+}"
+ : ''${windowManager:=${cfg.windowManager.default}}
+ desktopManager="''${sessionType%%+*}"
+ : ''${desktopManager:=${cfg.desktopManager.default}}
+
+ # Start the window manager.
+ case "$windowManager" in
+ ${mkCases wm}
+ (*) echo "$0: Window manager '$windowManager' not found.";;
+ esac
+
+ # Start the desktop manager.
+ case "$desktopManager" in
+ ${mkCases dm}
+ (*) echo "$0: Desktop manager '$desktopManager' not found.";;
+ esac
+
+ ${optionalString cfg.updateDbusEnvironment ''
+ ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all
+ ''}
+
+ test -n "$waitPID" && wait "$waitPID"
+
+ ${config.systemd.package}/bin/systemctl --user stop graphical-session.target
+
+ exit 0
+ '';
+
+ # Desktop Entry Specification:
+ # - https://standards.freedesktop.org/desktop-entry-spec/latest/
+ # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
+ mkDesktops = names: pkgs.runCommand "desktops"
+ { # trivial derivation
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ }
+ ''
+ mkdir -p "$out/share/xsessions"
+ ${concatMapStrings (n: ''
+ cat - > "$out/share/xsessions/${n}.desktop" << EODESKTOP
+ [Desktop Entry]
+ Version=1.0
+ Type=XSession
+ TryExec=${cfg.displayManager.session.script}
+ Exec=${cfg.displayManager.session.script} "${n}"
+ Name=${n}
+ Comment=
+ EODESKTOP
+ '') names}
+
+ ${concatMapStrings (pkg: ''
+ 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}
+ '';
+
+in
+
+{
+
+ options = {
+
+ services.xserver.displayManager = {
+
+ xauthBin = mkOption {
+ internal = true;
+ default = "${xorg.xauth}/bin/xauth";
+ description = "Path to the <command>xauth</command> program used by display managers.";
+ };
+
+ xserverBin = mkOption {
+ type = types.path;
+ description = "Path to the X server used by display managers.";
+ };
+
+ xserverArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ];
+ description = "List of arguments for the X server.";
+ };
+
+ setupCommands = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed just after the X server has started.
+
+ This option is only effective for display managers for which this feature
+ is supported; currently these are LightDM, GDM and SDDM.
+ '';
+ };
+
+ sessionCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ xmessage "Hello World!" &
+ '';
+ description = ''
+ Shell commands executed just before the window or desktop manager is
+ started. These commands are not currently sourced for Wayland sessions.
+ '';
+ };
+
+ hiddenUsers = mkOption {
+ type = types.listOf types.str;
+ default = [ "nobody" ];
+ description = ''
+ A list of users which will not be shown in the display manager.
+ '';
+ };
+
+ extraSessionFilePackages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = ''
+ A list of packages containing xsession files to be passed to the display manager.
+ '';
+ };
+
+ session = mkOption {
+ default = [];
+ example = literalExample
+ ''
+ [ { manage = "desktop";
+ name = "xterm";
+ start = '''
+ ''${pkgs.xterm}/bin/xterm -ls &
+ waitPID=$!
+ ''';
+ }
+ ]
+ '';
+ description = ''
+ List of sessions supported with the command used to start each
+ session. Each session script can set the
+ <varname>waitPID</varname> shell variable to make this script
+ wait until the end of the user session. Each script is used
+ to define either a window manager or a desktop manager. These
+ can be differentiated by setting the attribute
+ <varname>manage</varname> either to <literal>"window"</literal>
+ or <literal>"desktop"</literal>.
+
+ The list of desktop manager and window manager should appear
+ inside the display manager with the desktop manager name
+ followed by the window manager name.
+ '';
+ apply = list: rec {
+ wm = filter (s: s.manage == "window") list;
+ dm = filter (s: s.manage == "desktop") list;
+ names = flip concatMap dm
+ (d: map (w: d.name + optionalString (w.name != "none") ("+" + w.name))
+ (filter (w: d.name != "none" || w.name != "none") wm));
+ desktops = mkDesktops names;
+ script = xsession wm dm;
+ wrapper = xsessionWrapper;
+ };
+ };
+
+ job = {
+
+ preStart = mkOption {
+ type = types.lines;
+ default = "";
+ example = "rm -f /var/log/my-display-manager.log";
+ description = "Script executed before the display manager is started.";
+ };
+
+ execCmd = mkOption {
+ type = types.str;
+ example = literalExample ''
+ "''${pkgs.slim}/bin/slim"
+ '';
+ description = "Command to start the display manager.";
+ };
+
+ environment = mkOption {
+ type = types.attrsOf types.unspecified;
+ default = {};
+ example = { SLIM_CFGFILE = "/etc/slim.conf"; };
+ description = "Additional environment variables needed by the display manager.";
+ };
+
+ logToFile = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether the display manager redirects the output of the
+ session script to <filename>~/.xsession-errors</filename>.
+ '';
+ };
+
+ logToJournal = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether the display manager redirects the output of the
+ session script to the systemd journal.
+ '';
+ };
+
+ };
+
+ };
+
+ };
+
+ config = {
+ services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X";
+
+ systemd.user.targets.graphical-session = {
+ unitConfig = {
+ RefuseManualStart = false;
+ StopWhenUnneeded = false;
+ };
+ };
+ };
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ]
+ "The option is no longer necessary because all display managers have already delegated lid management to systemd.")
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix
new file mode 100644
index 00000000000..0a5d52e319e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix
@@ -0,0 +1,294 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver.displayManager;
+ gdm = pkgs.gnome3.gdm;
+
+ xSessionWrapper = if (cfg.setupCommands == "") then null else
+ pkgs.writeScript "gdm-x-session-wrapper" ''
+ #!${pkgs.bash}/bin/bash
+ ${cfg.setupCommands}
+ exec "$@"
+ '';
+
+ # Solves problems like:
+ # https://wiki.archlinux.org/index.php/Talk:Bluetooth_headset#GDMs_pulseaudio_instance_captures_bluetooth_headset
+ # Instead of blacklisting plugins, we use Fedora's PulseAudio configuration for GDM:
+ # https://src.fedoraproject.org/rpms/gdm/blob/master/f/default.pa-for-gdm
+ pulseConfig = pkgs.writeText "default.pa" ''
+ load-module module-device-restore
+ load-module module-card-restore
+ load-module module-udev-detect
+ load-module module-native-protocol-unix
+ load-module module-default-device-restore
+ load-module module-rescue-streams
+ load-module module-always-sink
+ load-module module-intended-roles
+ load-module module-suspend-on-idle
+ load-module module-position-event-sounds
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.xserver.displayManager.gdm = {
+
+ enable = mkEnableOption ''
+ GDM as the display manager.
+ <emphasis>GDM in NixOS is not well-tested with desktops other
+ than GNOME, so use with caution, as it could render the
+ system unusable.</emphasis>
+ '';
+
+ debug = mkEnableOption ''
+ debugging messages in GDM
+ '';
+
+ autoLogin = mkOption {
+ default = {};
+ description = ''
+ Auto login configuration attrset.
+ '';
+
+ type = types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Automatically log in as the sepecified <option>autoLogin.user</option>.
+ '';
+ };
+
+ user = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ User to be used for the autologin.
+ '';
+ };
+
+ delay = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Seconds of inactivity after which the autologin will be performed.
+ '';
+ };
+
+ };
+ };
+ };
+
+ wayland = mkOption {
+ default = true;
+ description = ''
+ Allow GDM run on Wayland instead of Xserver
+ '';
+ type = types.bool;
+ };
+
+ autoSuspend = mkOption {
+ default = true;
+ description = ''
+ Suspend the machine after inactivity.
+ '';
+ type = types.bool;
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.gdm.enable {
+
+ assertions = [
+ { assertion = cfg.gdm.autoLogin.enable -> cfg.gdm.autoLogin.user != null;
+ message = "GDM auto-login requires services.xserver.displayManager.gdm.autoLogin.user to be set";
+ }
+ ];
+
+ services.xserver.displayManager.lightdm.enable = false;
+
+ users.users.gdm =
+ { name = "gdm";
+ uid = config.ids.uids.gdm;
+ group = "gdm";
+ home = "/run/gdm";
+ description = "GDM user";
+ };
+
+ users.groups.gdm.gid = config.ids.gids.gdm;
+
+ # GDM needs different xserverArgs, presumable because using wayland by default.
+ services.xserver.tty = null;
+ services.xserver.display = null;
+ services.xserver.verbose = null;
+
+ services.xserver.displayManager.job =
+ {
+ environment = {
+ GDM_X_SERVER_EXTRA_ARGS = toString
+ (filter (arg: arg != "-terminate") cfg.xserverArgs);
+ XDG_DATA_DIRS = "${cfg.session.desktops}/share/";
+ } // optionalAttrs (xSessionWrapper != null) {
+ # Make GDM use this wrapper before running the session, which runs the
+ # configured setupCommands. This relies on a patched GDM which supports
+ # this environment variable.
+ GDM_X_SESSION_WRAPPER = "${xSessionWrapper}";
+ };
+ execCmd = "exec ${gdm}/bin/gdm";
+ preStart = optionalString config.hardware.pulseaudio.enable ''
+ mkdir -p /run/gdm/.config/pulse
+ ln -sf ${pulseConfig} /run/gdm/.config/pulse/default.pa
+ chown -R gdm:gdm /run/gdm/.config
+ '';
+ };
+
+ # Because sd_login_monitor_new requires /run/systemd/machines
+ systemd.services.display-manager.wants = [ "systemd-machined.service" ];
+ systemd.services.display-manager.after = [
+ "rc-local.service"
+ "systemd-machined.service"
+ "systemd-user-sessions.service"
+ ];
+
+ systemd.services.display-manager.serviceConfig = {
+ # Restart = "always"; - already defined in xserver.nix
+ KillMode = "mixed";
+ IgnoreSIGPIPE = "no";
+ BusName = "org.gnome.DisplayManager";
+ StandardOutput = "syslog";
+ StandardError = "inherit";
+ };
+
+ systemd.services.display-manager.path = [ pkgs.gnome3.gnome-session ];
+
+ # Allow choosing an user account
+ services.accounts-daemon.enable = true;
+
+ services.dbus.packages = [ gdm ];
+
+ systemd.user.services.dbus.wantedBy = [ "default.target" ];
+
+ programs.dconf.profiles.gdm =
+ let
+ customDconf = pkgs.writeTextFile {
+ name = "gdm-dconf";
+ destination = "/dconf/gdm-custom";
+ text = ''
+ ${optionalString (!cfg.gdm.autoSuspend) ''
+ [org/gnome/settings-daemon/plugins/power]
+ sleep-inactive-ac-type='nothing'
+ sleep-inactive-battery-type='nothing'
+ sleep-inactive-ac-timeout=0
+ sleep-inactive-battery-timeout=0
+ ''}
+ '';
+ };
+
+ customDconfDb = pkgs.stdenv.mkDerivation {
+ name = "gdm-dconf-db";
+ buildCommand = ''
+ ${pkgs.gnome3.dconf}/bin/dconf compile $out ${customDconf}/dconf
+ '';
+ };
+ in pkgs.stdenv.mkDerivation {
+ name = "dconf-gdm-profile";
+ buildCommand = ''
+ # Check that the GDM profile starts with what we expect.
+ if [ $(head -n 1 ${gdm}/share/dconf/profile/gdm) != "user-db:user" ]; then
+ echo "GDM dconf profile changed, please update gdm.nix"
+ exit 1
+ fi
+ # Insert our custom DB behind it.
+ sed '2ifile-db:${customDconfDb}' ${gdm}/share/dconf/profile/gdm > $out
+ '';
+ };
+
+ # Use AutomaticLogin if delay is zero, because it's immediate.
+ # Otherwise with TimedLogin with zero seconds the prompt is still
+ # presented and there's a little delay.
+ environment.etc."gdm/custom.conf".text = ''
+ [daemon]
+ WaylandEnable=${if cfg.gdm.wayland then "true" else "false"}
+ ${optionalString cfg.gdm.autoLogin.enable (
+ if cfg.gdm.autoLogin.delay > 0 then ''
+ TimedLoginEnable=true
+ TimedLogin=${cfg.gdm.autoLogin.user}
+ TimedLoginDelay=${toString cfg.gdm.autoLogin.delay}
+ '' else ''
+ AutomaticLoginEnable=true
+ AutomaticLogin=${cfg.gdm.autoLogin.user}
+ '')
+ }
+
+ [security]
+
+ [xdmcp]
+
+ [greeter]
+
+ [chooser]
+
+ [debug]
+ ${optionalString cfg.gdm.debug "Enable=true"}
+ '';
+
+ environment.etc."gdm/Xsession".source = config.services.xserver.displayManager.session.wrapper;
+
+ # GDM LFS PAM modules, adapted somehow to NixOS
+ security.pam.services = {
+ gdm-launch-environment.text = ''
+ auth required pam_succeed_if.so audit quiet_success user = gdm
+ auth optional pam_permit.so
+
+ account required pam_succeed_if.so audit quiet_success user = gdm
+ account sufficient pam_unix.so
+
+ password required pam_deny.so
+
+ session required pam_succeed_if.so audit quiet_success user = gdm
+ session required pam_env.so conffile=${config.system.build.pamEnvironment} readenv=0
+ session optional ${pkgs.systemd}/lib/security/pam_systemd.so
+ session optional pam_keyinit.so force revoke
+ session optional pam_permit.so
+ '';
+
+ gdm-password.text = ''
+ auth substack login
+ account include login
+ password substack login
+ session include login
+ '';
+
+ gdm-autologin.text = ''
+ auth requisite pam_nologin.so
+
+ auth required pam_succeed_if.so uid >= 1000 quiet
+ auth required pam_permit.so
+
+ account sufficient pam_unix.so
+
+ password requisite pam_unix.so nullok sha512
+
+ session optional pam_keyinit.so revoke
+ session include login
+ '';
+
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
new file mode 100644
index 00000000000..129df139c61
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
@@ -0,0 +1,140 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ dmcfg = config.services.xserver.displayManager;
+ ldmcfg = dmcfg.lightdm;
+ cfg = ldmcfg.greeters.enso;
+
+ theme = cfg.theme.package;
+ icons = cfg.iconTheme.package;
+ cursors = cfg.cursorTheme.package;
+
+ ensoGreeterConf = pkgs.writeText "lightdm-enso-os-greeter.conf" ''
+ [greeter]
+ default-wallpaper=${ldmcfg.background}
+ gtk-theme=${cfg.theme.name}
+ icon-theme=${cfg.iconTheme.name}
+ cursor-theme=${cfg.cursorTheme.name}
+ blur=${toString cfg.blur}
+ brightness=${toString cfg.brightness}
+ ${cfg.extraConfig}
+ '';
+in {
+ options = {
+ services.xserver.displayManager.lightdm.greeters.enso = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable enso-os-greeter as the lightdm greeter
+ '';
+ };
+
+ theme = {
+ package = mkOption {
+ type = types.package;
+ default = pkgs.gnome3.gnome-themes-extra;
+ defaultText = "pkgs.gnome3.gnome-themes-extra";
+ description = ''
+ The package path that contains the theme given in the name option.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "Adwaita";
+ description = ''
+ Name of the theme to use for the lightdm-enso-os-greeter
+ '';
+ };
+ };
+
+ iconTheme = {
+ package = mkOption {
+ type = types.package;
+ default = pkgs.papirus-icon-theme;
+ defaultText = "pkgs.papirus-icon-theme";
+ description = ''
+ The package path that contains the icon theme given in the name option.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "ePapirus";
+ description = ''
+ Name of the icon theme to use for the lightdm-enso-os-greeter
+ '';
+ };
+ };
+
+ cursorTheme = {
+ package = mkOption {
+ type = types.package;
+ default = pkgs.capitaine-cursors;
+ defaultText = "pkgs.capitaine-cursors";
+ description = ''
+ The package path that contains the cursor theme given in the name option.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "capitane-cursors";
+ description = ''
+ Name of the cursor theme to use for the lightdm-enso-os-greeter
+ '';
+ };
+ };
+
+ blur = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether or not to enable blur
+ '';
+ };
+
+ brightness = mkOption {
+ type = types.int;
+ default = 7;
+ description = ''
+ Brightness
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration that should be put in the greeter.conf
+ configuration file
+ '';
+ };
+ };
+ };
+
+ config = mkIf (ldmcfg.enable && cfg.enable) {
+ environment.etc."lightdm/greeter.conf".source = ensoGreeterConf;
+
+ environment.systemPackages = [
+ cursors
+ icons
+ theme
+ ];
+
+ services.xserver.displayManager.lightdm = {
+ greeter = mkDefault {
+ package = pkgs.lightdm-enso-os-greeter.xgreeters;
+ name = "pantheon-greeter";
+ };
+
+ greeters = {
+ gtk = {
+ enable = mkDefault false;
+ };
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
new file mode 100644
index 00000000000..de932e6e840
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
@@ -0,0 +1,173 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dmcfg = config.services.xserver.displayManager;
+ ldmcfg = dmcfg.lightdm;
+ xcfg = config.services.xserver;
+ cfg = ldmcfg.greeters.gtk;
+
+ inherit (pkgs) writeText;
+
+ theme = cfg.theme.package;
+ icons = cfg.iconTheme.package;
+ cursors = cfg.cursorTheme.package;
+
+ gtkGreeterConf = writeText "lightdm-gtk-greeter.conf"
+ ''
+ [greeter]
+ theme-name = ${cfg.theme.name}
+ icon-theme-name = ${cfg.iconTheme.name}
+ cursor-theme-name = ${cfg.cursorTheme.name}
+ cursor-theme-size = ${toString cfg.cursorTheme.size}
+ background = ${ldmcfg.background}
+ ${optionalString (cfg.clock-format != null) "clock-format = ${cfg.clock-format}"}
+ ${optionalString (cfg.indicators != null) "indicators = ${concatStringsSep ";" cfg.indicators}"}
+ ${optionalString (xcfg.dpi != null) "xft-dpi=${toString xcfg.dpi}"}
+ ${cfg.extraConfig}
+ '';
+
+in
+{
+ options = {
+
+ services.xserver.displayManager.lightdm.greeters.gtk = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable lightdm-gtk-greeter as the lightdm greeter.
+ '';
+ };
+
+ theme = {
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.gnome3.gnome-themes-extra;
+ defaultText = "pkgs.gnome3.gnome-themes-extra";
+ description = ''
+ The package path that contains the theme given in the name option.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "Adwaita";
+ description = ''
+ Name of the theme to use for the lightdm-gtk-greeter.
+ '';
+ };
+
+ };
+
+ iconTheme = {
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.gnome3.adwaita-icon-theme;
+ defaultText = "pkgs.gnome3.adwaita-icon-theme";
+ description = ''
+ The package path that contains the icon theme given in the name option.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "Adwaita";
+ description = ''
+ Name of the icon theme to use for the lightdm-gtk-greeter.
+ '';
+ };
+
+ };
+
+ cursorTheme = {
+
+ package = mkOption {
+ default = pkgs.gnome3.adwaita-icon-theme;
+ defaultText = "pkgs.gnome3.adwaita-icon-theme";
+ description = ''
+ The package path that contains the cursor theme given in the name option.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "Adwaita";
+ description = ''
+ Name of the cursor theme to use for the lightdm-gtk-greeter.
+ '';
+ };
+
+ size = mkOption {
+ type = types.int;
+ default = 16;
+ description = ''
+ Size of the cursor theme to use for the lightdm-gtk-greeter.
+ '';
+ };
+ };
+
+ clock-format = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "%F";
+ description = ''
+ Clock format string (as expected by strftime, e.g. "%H:%M")
+ to use with the lightdm gtk greeter panel.
+
+ If set to null the default clock format is used.
+ '';
+ };
+
+ indicators = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ example = [ "~host" "~spacer" "~clock" "~spacer" "~session" "~language" "~a11y" "~power" ];
+ description = ''
+ List of allowed indicator modules to use for the lightdm gtk
+ greeter panel.
+
+ Built-in indicators include "~a11y", "~language", "~session",
+ "~power", "~clock", "~host", "~spacer". Unity indicators can be
+ represented by short name (e.g. "sound", "power"), service file name,
+ or absolute path.
+
+ If set to null the default indicators are used.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration that should be put in the lightdm-gtk-greeter.conf
+ configuration file.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf (ldmcfg.enable && cfg.enable) {
+
+ services.xserver.displayManager.lightdm.greeter = mkDefault {
+ package = pkgs.lightdm_gtk_greeter.xgreeters;
+ name = "lightdm-gtk-greeter";
+ };
+
+ environment.systemPackages = [
+ cursors
+ icons
+ theme
+ ];
+
+ environment.etc."lightdm/lightdm-gtk-greeter.conf".source = gtkGreeterConf;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix
new file mode 100644
index 00000000000..fa9445af32e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dmcfg = config.services.xserver.displayManager;
+ ldmcfg = dmcfg.lightdm;
+ cfg = ldmcfg.greeters.mini;
+
+ miniGreeterConf = pkgs.writeText "lightdm-mini-greeter.conf"
+ ''
+ [greeter]
+ user = ${cfg.user}
+ show-password-label = true
+ password-label-text = Password:
+ show-input-cursor = true
+
+ [greeter-hotkeys]
+ mod-key = meta
+ shutdown-key = s
+ restart-key = r
+ hibernate-key = h
+ suspend-key = u
+
+ [greeter-theme]
+ font = Sans
+ font-size = 1em
+ text-color = "#080800"
+ error-color = "#F8F8F0"
+ background-image = "${ldmcfg.background}"
+ background-color = "#1B1D1E"
+ window-color = "#F92672"
+ border-color = "#080800"
+ border-width = 2px
+ layout-space = 15
+ password-color = "#F8F8F0"
+ password-background-color = "#1B1D1E"
+
+ ${cfg.extraConfig}
+ '';
+
+in
+{
+ options = {
+
+ services.xserver.displayManager.lightdm.greeters.mini = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable lightdm-mini-greeter as the lightdm greeter.
+
+ Note that this greeter starts only the default X session.
+ You can configure the default X session by
+ <option>services.xserver.desktopManager.default</option> and
+ <option>services.xserver.windowManager.default</option>.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "root";
+ description = ''
+ The user to login as.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration that should be put in the lightdm-mini-greeter.conf
+ configuration file.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf (ldmcfg.enable && cfg.enable) {
+
+ services.xserver.displayManager.lightdm.greeters.gtk.enable = false;
+
+ services.xserver.displayManager.lightdm.greeter = mkDefault {
+ package = pkgs.lightdm-mini-greeter.xgreeters;
+ name = "lightdm-mini-greeter";
+ };
+
+ environment.etc."lightdm/lightdm-mini-greeter.conf".source = miniGreeterConf;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
new file mode 100644
index 00000000000..29cb6ccbc06
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dmcfg = config.services.xserver.displayManager;
+ ldmcfg = dmcfg.lightdm;
+ cfg = ldmcfg.greeters.pantheon;
+
+in
+{
+ options = {
+
+ services.xserver.displayManager.lightdm.greeters.pantheon = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable elementary-greeter as the lightdm greeter.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf (ldmcfg.enable && cfg.enable) {
+
+ services.xserver.displayManager.lightdm.greeters.gtk.enable = false;
+
+ services.xserver.displayManager.lightdm.greeter = mkDefault {
+ package = pkgs.pantheon.elementary-greeter.xgreeters;
+ name = "io.elementary.greeter";
+ };
+
+ environment.etc."lightdm/io.elementary.greeter.conf".source = "${pkgs.pantheon.elementary-greeter}/etc/lightdm/io.elementary.greeter.conf";
+ environment.etc."wingpanel.d/io.elementary.greeter.whitelist".source = "${pkgs.pantheon.elementary-default-settings}/etc/wingpanel.d/io.elementary.greeter.whitelist";
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix
new file mode 100644
index 00000000000..f105cb496e6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -0,0 +1,290 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ xcfg = config.services.xserver;
+ dmcfg = xcfg.displayManager;
+ xEnv = config.systemd.services.display-manager.environment;
+ cfg = dmcfg.lightdm;
+
+ dmDefault = xcfg.desktopManager.default;
+ wmDefault = xcfg.windowManager.default;
+ hasDefaultUserSession = dmDefault != "none" || wmDefault != "none";
+
+ inherit (pkgs) lightdm writeScript writeText;
+
+ # lightdm runs with clearenv(), but we need a few things in the environment for X to startup
+ xserverWrapper = writeScript "xserver-wrapper"
+ ''
+ #! ${pkgs.bash}/bin/bash
+ ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)}
+
+ display=$(echo "$@" | xargs -n 1 | grep -P ^:\\d\$ | head -n 1 | sed s/^://)
+ if [ -z "$display" ]
+ then additionalArgs=":0 -logfile /var/log/X.0.log"
+ else additionalArgs="-logfile /var/log/X.$display.log"
+ fi
+
+ exec ${dmcfg.xserverBin} ${toString dmcfg.xserverArgs} $additionalArgs "$@"
+ '';
+
+ usersConf = writeText "users.conf"
+ ''
+ [UserList]
+ minimum-uid=500
+ hidden-users=${concatStringsSep " " dmcfg.hiddenUsers}
+ hidden-shells=/run/current-system/sw/bin/nologin
+ '';
+
+ lightdmConf = writeText "lightdm.conf"
+ ''
+ [LightDM]
+ ${optionalString cfg.greeter.enable ''
+ greeter-user = ${config.users.users.lightdm.name}
+ greeters-directory = ${cfg.greeter.package}
+ ''}
+ sessions-directory = ${dmcfg.session.desktops}/share/xsessions
+ ${cfg.extraConfig}
+
+ [Seat:*]
+ xserver-command = ${xserverWrapper}
+ session-wrapper = ${dmcfg.session.wrapper}
+ ${optionalString cfg.greeter.enable ''
+ greeter-session = ${cfg.greeter.name}
+ ''}
+ ${optionalString cfg.autoLogin.enable ''
+ autologin-user = ${cfg.autoLogin.user}
+ autologin-user-timeout = ${toString cfg.autoLogin.timeout}
+ autologin-session = ${defaultSessionName}
+ ''}
+ ${optionalString hasDefaultUserSession ''
+ user-session=${defaultSessionName}
+ ''}
+ ${optionalString (dmcfg.setupCommands != "") ''
+ display-setup-script=${pkgs.writeScript "lightdm-display-setup" ''
+ #!${pkgs.bash}/bin/bash
+ ${dmcfg.setupCommands}
+ ''}
+ ''}
+ ${cfg.extraSeatDefaults}
+ '';
+
+ defaultSessionName = dmDefault + optionalString (wmDefault != "none") ("+" + wmDefault);
+in
+{
+ # Note: the order in which lightdm greeter modules are imported
+ # here determines the default: later modules (if enable) are
+ # preferred.
+ imports = [
+ ./lightdm-greeters/gtk.nix
+ ./lightdm-greeters/mini.nix
+ ./lightdm-greeters/enso-os.nix
+ ./lightdm-greeters/pantheon.nix
+ ];
+
+ options = {
+
+ services.xserver.displayManager.lightdm = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable lightdm as the display manager.
+ '';
+ };
+
+ greeter = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If set to false, run lightdm in greeterless mode. This only works if autologin
+ is enabled and autoLogin.timeout is zero.
+ '';
+ };
+ package = mkOption {
+ type = types.package;
+ description = ''
+ The LightDM greeter to login via. The package should be a directory
+ containing a .desktop file matching the name in the 'name' option.
+ '';
+
+ };
+ name = mkOption {
+ type = types.str;
+ description = ''
+ The name of a .desktop file in the directory specified
+ in the 'package' option.
+ '';
+ };
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ user-authority-in-system-dir = true
+ '';
+ description = "Extra lines to append to LightDM section.";
+ };
+
+ background = mkOption {
+ type = types.str;
+ default = "${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bottom.png";
+ description = ''
+ The background image or color to use.
+ '';
+ };
+
+ extraSeatDefaults = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ greeter-show-manual-login=true
+ '';
+ description = "Extra lines to append to SeatDefaults section.";
+ };
+
+ autoLogin = mkOption {
+ default = {};
+ description = ''
+ Configuration for automatic login.
+ '';
+
+ type = types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Automatically log in as the specified <option>autoLogin.user</option>.
+ '';
+ };
+
+ user = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ User to be used for the automatic login.
+ '';
+ };
+
+ timeout = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Show the greeter for this many seconds before automatic login occurs.
+ '';
+ };
+ };
+ };
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = xcfg.enable;
+ message = ''
+ LightDM requires services.xserver.enable to be true
+ '';
+ }
+ { assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null;
+ message = ''
+ LightDM auto-login requires services.xserver.displayManager.lightdm.autoLogin.user to be set
+ '';
+ }
+ { assertion = cfg.autoLogin.enable -> dmDefault != "none" || wmDefault != "none";
+ message = ''
+ LightDM auto-login requires that services.xserver.desktopManager.default and
+ services.xserver.windowManager.default are set to valid values. The current
+ default session: ${defaultSessionName} is not valid.
+ '';
+ }
+ { assertion = !cfg.greeter.enable -> (cfg.autoLogin.enable && cfg.autoLogin.timeout == 0);
+ message = ''
+ LightDM can only run without greeter if automatic login is enabled and the timeout for it
+ is set to zero.
+ '';
+ }
+ ];
+
+ # 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;
+
+ services.dbus.enable = true;
+ services.dbus.packages = [ lightdm ];
+
+ # lightdm uses the accounts daemon to remember language/window-manager per user
+ services.accounts-daemon.enable = true;
+
+ # Enable the accounts daemon to find lightdm's dbus interface
+ environment.systemPackages = [ lightdm ];
+
+ security.pam.services.lightdm.text = ''
+ auth substack login
+ account include login
+ password substack login
+ session include login
+ '';
+
+ security.pam.services.lightdm-greeter.text = ''
+ auth required pam_succeed_if.so audit quiet_success user = lightdm
+ auth optional pam_permit.so
+
+ account required pam_succeed_if.so audit quiet_success user = lightdm
+ account sufficient pam_unix.so
+
+ password required pam_deny.so
+
+ session required pam_succeed_if.so audit quiet_success user = lightdm
+ session required pam_env.so conffile=${config.system.build.pamEnvironment} readenv=0
+ session optional ${pkgs.systemd}/lib/security/pam_systemd.so
+ session optional pam_keyinit.so force revoke
+ session optional pam_permit.so
+ '';
+
+ security.pam.services.lightdm-autologin.text = ''
+ auth requisite pam_nologin.so
+
+ auth required pam_succeed_if.so uid >= 1000 quiet
+ auth required pam_permit.so
+
+ account sufficient pam_unix.so
+
+ password requisite pam_unix.so nullok sha512
+
+ session optional pam_keyinit.so revoke
+ session include login
+ '';
+
+ users.users.lightdm = {
+ home = "/var/lib/lightdm";
+ group = "lightdm";
+ uid = config.ids.uids.lightdm;
+ };
+
+ systemd.tmpfiles.rules = [
+ "d /run/lightdm 0711 lightdm lightdm 0"
+ "d /var/cache/lightdm 0711 root lightdm -"
+ "d /var/lib/lightdm 1770 lightdm lightdm -"
+ "d /var/lib/lightdm-data 1775 lightdm lightdm -"
+ "d /var/log/lightdm 0711 root lightdm -"
+ ];
+
+ users.groups.lightdm.gid = config.ids.gids.lightdm;
+ services.xserver.tty = null; # We might start multiple X servers so let the tty increment themselves..
+ services.xserver.display = null; # We specify our own display (and logfile) in xserver-wrapper up there
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix
new file mode 100644
index 00000000000..8847acb0c60
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix
@@ -0,0 +1,298 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ xcfg = config.services.xserver;
+ dmcfg = xcfg.displayManager;
+ cfg = dmcfg.sddm;
+ xEnv = config.systemd.services.display-manager.environment;
+
+ inherit (pkgs) sddm;
+
+ xserverWrapper = pkgs.writeScript "xserver-wrapper" ''
+ #!/bin/sh
+ ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)}
+ exec systemd-cat ${dmcfg.xserverBin} ${toString dmcfg.xserverArgs} "$@"
+ '';
+
+ Xsetup = pkgs.writeScript "Xsetup" ''
+ #!/bin/sh
+ ${cfg.setupScript}
+ ${dmcfg.setupCommands}
+ '';
+
+ Xstop = pkgs.writeScript "Xstop" ''
+ #!/bin/sh
+ ${cfg.stopScript}
+ '';
+
+ cfgFile = pkgs.writeText "sddm.conf" ''
+ [General]
+ HaltCommand=${pkgs.systemd}/bin/systemctl poweroff
+ RebootCommand=${pkgs.systemd}/bin/systemctl reboot
+ ${optionalString cfg.autoNumlock ''
+ Numlock=on
+ ''}
+
+ [Theme]
+ Current=${cfg.theme}
+ ThemeDir=/run/current-system/sw/share/sddm/themes
+ FacesDir=/run/current-system/sw/share/sddm/faces
+
+ [Users]
+ MaximumUid=${toString config.ids.uids.nixbld}
+ HideUsers=${concatStringsSep "," dmcfg.hiddenUsers}
+ HideShells=/run/current-system/sw/bin/nologin
+
+ [X11]
+ MinimumVT=${toString (if xcfg.tty != null then xcfg.tty else 7)}
+ ServerPath=${xserverWrapper}
+ XephyrPath=${pkgs.xorg.xorgserver.out}/bin/Xephyr
+ SessionCommand=${dmcfg.session.wrapper}
+ SessionDir=${dmcfg.session.desktops}/share/xsessions
+ XauthPath=${pkgs.xorg.xauth}/bin/xauth
+ DisplayCommand=${Xsetup}
+ DisplayStopCommand=${Xstop}
+ EnableHidpi=${if cfg.enableHidpi then "true" else "false"}
+
+ [Wayland]
+ EnableHidpi=${if cfg.enableHidpi then "true" else "false"}
+ SessionDir=${dmcfg.session.desktops}/share/wayland-sessions
+
+ ${optionalString cfg.autoLogin.enable ''
+ [Autologin]
+ User=${cfg.autoLogin.user}
+ Session=${defaultSessionName}.desktop
+ Relogin=${boolToString cfg.autoLogin.relogin}
+ ''}
+
+ ${cfg.extraConfig}
+ '';
+
+ defaultSessionName =
+ let
+ dm = xcfg.desktopManager.default;
+ wm = xcfg.windowManager.default;
+ in dm + optionalString (wm != "none") ("+" + wm);
+
+in
+{
+ options = {
+
+ services.xserver.displayManager.sddm = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable sddm as the display manager.
+ '';
+ };
+
+ enableHidpi = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable automatic HiDPI mode.
+ </para>
+ <para>
+ Versions up to 0.17 are broken so this only works from 0.18 onwards.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ [Autologin]
+ User=john
+ Session=plasma.desktop
+ '';
+ description = ''
+ Extra lines appended to the configuration of SDDM.
+ '';
+ };
+
+ theme = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Greeter theme to use.
+ '';
+ };
+
+ autoNumlock = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable numlock at login.
+ '';
+ };
+
+ setupScript = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ # workaround for using NVIDIA Optimus without Bumblebee
+ xrandr --setprovideroutputsource modesetting NVIDIA-0
+ xrandr --auto
+ '';
+ description = ''
+ A script to execute when starting the display server. DEPRECATED, please
+ use <option>services.xserver.displayManager.setupCommands</option>.
+ '';
+ };
+
+ stopScript = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ A script to execute when stopping the display server.
+ '';
+ };
+
+ autoLogin = mkOption {
+ default = {};
+ description = ''
+ Configuration for automatic login.
+ '';
+
+ type = types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Automatically log in as <option>autoLogin.user</option>.
+ '';
+ };
+
+ user = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ User to be used for the automatic login.
+ '';
+ };
+
+ relogin = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If true automatic login will kick in again on session exit (logout), otherwise it
+ will only log in automatically when the display-manager is started.
+ '';
+ };
+ };
+ };
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = xcfg.enable;
+ message = ''
+ SDDM requires services.xserver.enable to be true
+ '';
+ }
+ { assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null;
+ message = ''
+ SDDM auto-login requires services.xserver.displayManager.sddm.autoLogin.user to be set
+ '';
+ }
+ { assertion = cfg.autoLogin.enable -> elem defaultSessionName dmcfg.session.names;
+ message = ''
+ SDDM auto-login requires that services.xserver.desktopManager.default and
+ services.xserver.windowManager.default are set to valid values. The current
+ default session: ${defaultSessionName} is not valid.
+ '';
+ }
+ ];
+
+ services.xserver.displayManager.job = {
+ environment = {
+ # Load themes from system environment
+ QT_PLUGIN_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtPluginPrefix;
+ QML2_IMPORT_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtQmlPrefix;
+ };
+
+ execCmd = "exec /run/current-system/sw/bin/sddm";
+ };
+
+ security.pam.services = {
+ sddm = {
+ allowNullPassword = true;
+ startSession = true;
+ };
+
+ sddm-greeter.text = ''
+ auth required pam_succeed_if.so audit quiet_success user = sddm
+ auth optional pam_permit.so
+
+ account required pam_succeed_if.so audit quiet_success user = sddm
+ account sufficient pam_unix.so
+
+ password required pam_deny.so
+
+ session required pam_succeed_if.so audit quiet_success user = sddm
+ session required pam_env.so conffile=${config.system.build.pamEnvironment} readenv=0
+ session optional ${pkgs.systemd}/lib/security/pam_systemd.so
+ session optional pam_keyinit.so force revoke
+ session optional pam_permit.so
+ '';
+
+ sddm-autologin.text = ''
+ auth requisite pam_nologin.so
+ auth required pam_succeed_if.so uid >= 1000 quiet
+ auth required pam_permit.so
+
+ account include sddm
+
+ password include sddm
+
+ session include sddm
+ '';
+ };
+
+ users.users.sddm = {
+ createHome = true;
+ home = "/var/lib/sddm";
+ group = "sddm";
+ uid = config.ids.uids.sddm;
+ };
+
+ environment.etc."sddm.conf".source = cfgFile;
+ environment.pathsToLink = [
+ "/share/sddm"
+ ];
+
+ users.groups.sddm.gid = config.ids.gids.sddm;
+
+ environment.systemPackages = [ sddm ];
+ services.dbus.packages = [ sddm ];
+
+ # To enable user switching, allow sddm to allocate TTYs/displays dynamically.
+ services.xserver.tty = null;
+ services.xserver.display = null;
+
+ systemd.tmpfiles.rules = [
+ # Prior to Qt 5.9.2, there is a QML cache invalidation bug which sometimes
+ # strikes new Plasma 5 releases. If the QML cache is not invalidated, SDDM
+ # will segfault without explanation. We really tore our hair out for awhile
+ # before finding the bug:
+ # https://bugreports.qt.io/browse/QTBUG-62302
+ # We work around the problem by deleting the QML cache before startup.
+ # This was supposedly fixed in Qt 5.9.2 however it has been reported with
+ # 5.10 and 5.11 as well. The initial workaround was to delete the directory
+ # in the Xsetup script but that doesn't do anything.
+ # Instead we use tmpfiles.d to ensure it gets wiped.
+ # This causes a small but perceptible delay when SDDM starts.
+ "e ${config.users.users.sddm.home}/.cache - - - 0"
+ ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/slim.nix b/nixpkgs/nixos/modules/services/x11/display-managers/slim.nix
new file mode 100644
index 00000000000..124660a43f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/slim.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dmcfg = config.services.xserver.displayManager;
+
+ cfg = dmcfg.slim;
+
+ slimConfig = pkgs.writeText "slim.cfg"
+ ''
+ xauth_path ${dmcfg.xauthBin}
+ default_xserver ${dmcfg.xserverBin}
+ xserver_arguments ${toString dmcfg.xserverArgs}
+ sessiondir ${dmcfg.session.desktops}/share/xsessions
+ login_cmd exec ${pkgs.runtimeShell} ${dmcfg.session.wrapper} "%session"
+ halt_cmd ${config.systemd.package}/sbin/shutdown -h now
+ reboot_cmd ${config.systemd.package}/sbin/shutdown -r now
+ logfile /dev/stderr
+ ${optionalString (cfg.defaultUser != null) ("default_user " + cfg.defaultUser)}
+ ${optionalString (cfg.defaultUser != null) ("focus_password yes")}
+ ${optionalString cfg.autoLogin "auto_login yes"}
+ ${optionalString (cfg.consoleCmd != null) "console_cmd ${cfg.consoleCmd}"}
+ ${cfg.extraConfig}
+ '';
+
+ # Unpack the SLiM theme, or use the default.
+ slimThemesDir =
+ let
+ unpackedTheme = pkgs.runCommand "slim-theme" { preferLocalBuild = true; }
+ ''
+ mkdir -p $out
+ cd $out
+ unpackFile ${cfg.theme}
+ ln -s * default
+ '';
+ in if cfg.theme == null then "${pkgs.slim}/share/slim/themes" else unpackedTheme;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.xserver.displayManager.slim = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable SLiM as the display manager.
+ '';
+ };
+
+ theme = mkOption {
+ type = types.nullOr types.path;
+ default = pkgs.fetchurl {
+ url = "https://github.com/jagajaga/nixos-slim-theme/archive/2.0.tar.gz";
+ sha256 = "0lldizhigx7bjhxkipii87y432hlf5wdvamnfxrryf9z7zkfypc8";
+ };
+ defaultText = ''pkgs.fetchurl {
+ url = "https://github.com/jagajaga/nixos-slim-theme/archive/2.0.tar.gz";
+ sha256 = "0lldizhigx7bjhxkipii87y432hlf5wdvamnfxrryf9z7zkfypc8";
+ }'';
+ example = literalExample ''
+ pkgs.fetchurl {
+ url = "mirror://sourceforge/slim.berlios/slim-wave.tar.gz";
+ sha256 = "0ndr419i5myzcylvxb89m9grl2xyq6fbnyc3lkd711mzlmnnfxdy";
+ }
+ '';
+ description = ''
+ The theme for the SLiM login manager. If not specified, SLiM's
+ default theme is used. See <link
+ xlink:href='http://slim.berlios.de/themes01.php'/> for a
+ collection of themes. TODO: berlios shut down.
+ '';
+ };
+
+ defaultUser = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "login";
+ description = ''
+ The default user to load. If you put a username here you
+ get it automatically loaded into the username field, and
+ the focus is placed on the password.
+ '';
+ };
+
+ autoLogin = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Automatically log in as the default user.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration options for SLiM login manager. Do not
+ add options that can be configured directly.
+ '';
+ };
+
+ consoleCmd = mkOption {
+ type = types.nullOr types.str;
+ default = ''
+ ${pkgs.xterm}/bin/xterm -C -fg white -bg black +sb -T "Console login" -e ${pkgs.shadow}/bin/login
+ '';
+ defaultText = ''
+ ''${pkgs.xterm}/bin/xterm -C -fg white -bg black +sb -T "Console login" -e ''${pkgs.shadow}/bin/login
+ '';
+ description = ''
+ The command to run when "console" is given as the username.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.xserver.displayManager.job =
+ { environment =
+ { SLIM_CFGFILE = slimConfig;
+ SLIM_THEMESDIR = slimThemesDir;
+ };
+ execCmd = "exec ${pkgs.slim}/bin/slim";
+ };
+
+ services.xserver.displayManager.sessionCommands =
+ ''
+ # Export the config/themes for slimlock.
+ export SLIM_THEMESDIR=${slimThemesDir}
+ '';
+
+ # Allow null passwords so that the user can login as root on the
+ # installation CD.
+ security.pam.services.slim = { allowNullPassword = true; startSession = true; };
+
+ # Allow slimlock to work.
+ security.pam.services.slimlock = {};
+
+ environment.systemPackages = [ pkgs.slim ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/startx.nix b/nixpkgs/nixos/modules/services/x11/display-managers/startx.nix
new file mode 100644
index 00000000000..57046984358
--- /dev/null
+++ b/nixpkgs/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 ~/.xinitrc file containing session startup
+ commands, see startx(1). This is not automatically generated
+ from the desktopManager and windowManager settings.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ services.xserver = {
+ exportConfiguration = true;
+ displayManager.job.execCmd = "";
+ displayManager.lightdm.enable = lib.mkForce false;
+ };
+ systemd.services.display-manager.enable = false;
+ environment.systemPackages = with pkgs; [ xorg.xinit ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix b/nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix
new file mode 100644
index 00000000000..c23e479140f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix
@@ -0,0 +1,252 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.displayManager.xpra;
+ dmcfg = config.services.xserver.displayManager;
+
+in
+
+{
+ ###### interface
+
+ options = {
+ services.xserver.displayManager.xpra = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable xpra as display manager.";
+ };
+
+ bindTcp = mkOption {
+ default = "127.0.0.1:10000";
+ example = "0.0.0.0:10000";
+ type = types.nullOr types.str;
+ description = "Bind xpra to TCP";
+ };
+
+ auth = mkOption {
+ type = types.str;
+ default = "pam";
+ example = "password:value=mysecret";
+ description = "Authentication to use when connecting to xpra";
+ };
+
+ pulseaudio = mkEnableOption "pulseaudio audio streaming";
+
+ extraOptions = mkOption {
+ description = "Extra xpra options";
+ default = [];
+ type = types.listOf types.str;
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ services.xserver.videoDrivers = ["dummy"];
+
+ services.xserver.monitorSection = ''
+ HorizSync 1.0 - 2000.0
+ VertRefresh 1.0 - 200.0
+ #To add your own modes here, use a modeline calculator, like:
+ # cvt:
+ # http://www.x.org/archive/X11R7.5/doc/man/man1/cvt.1.html
+ # xtiming:
+ # http://xtiming.sourceforge.net/cgi-bin/xtiming.pl
+ # gtf:
+ # http://gtf.sourceforge.net/
+ #This can be used to get a specific DPI, but only for the default resolution:
+ #DisplaySize 508 317
+ #NOTE: the highest modes will not work without increasing the VideoRam
+ # for the dummy video card.
+ #Modeline "16000x15000" 300.00 16000 16408 18000 20000 15000 15003 15013 15016
+ #Modeline "15000x15000" 281.25 15000 15376 16872 18744 15000 15003 15013 15016
+ #Modeline "16384x8192" 167.75 16384 16800 18432 20480 8192 8195 8205 8208
+ #Modeline "15360x8640" 249.00 15360 15752 17280 19200 8640 8643 8648 8651
+ Modeline "8192x4096" 193.35 8192 8224 8952 8984 4096 4196 4200 4301
+ Modeline "7680x4320" 208.00 7680 7880 8640 9600 4320 4323 4328 4335
+ Modeline "6400x4096" 151.38 6400 6432 7000 7032 4096 4196 4200 4301
+ Modeline "6400x2560" 91.59 6400 6432 6776 6808 2560 2623 2626 2689
+ Modeline "6400x2160" 160.51 6400 6432 7040 7072 2160 2212 2216 2269
+ Modeline "5760x2160" 149.50 5760 5768 6320 6880 2160 2161 2164 2173
+ Modeline "5680x1440" 142.66 5680 5712 6248 6280 1440 1474 1478 1513
+ Modeline "5496x1200" 199.13 5496 5528 6280 6312 1200 1228 1233 1261
+ Modeline "5280x2560" 75.72 5280 5312 5592 5624 2560 2623 2626 2689
+ Modeline "5280x1920" 56.04 5280 5312 5520 5552 1920 1967 1969 2017
+ Modeline "5280x1200" 191.40 5280 5312 6032 6064 1200 1228 1233 1261
+ Modeline "5280x1080" 169.96 5280 5312 5952 5984 1080 1105 1110 1135
+ Modeline "5120x3200" 199.75 5120 5152 5904 5936 3200 3277 3283 3361
+ Modeline "5120x2560" 73.45 5120 5152 5424 5456 2560 2623 2626 2689
+ Modeline "5120x2880" 185.50 5120 5256 5760 6400 2880 2883 2888 2899
+ Modeline "4800x1200" 64.42 4800 4832 5072 5104 1200 1229 1231 1261
+ Modeline "4720x3840" 227.86 4720 4752 5616 5648 3840 3933 3940 4033
+ Modeline "4400x2560" 133.70 4400 4432 4936 4968 2560 2622 2627 2689
+ Modeline "4480x1440" 72.94 4480 4512 4784 4816 1440 1475 1478 1513
+ Modeline "4240x1440" 69.09 4240 4272 4528 4560 1440 1475 1478 1513
+ Modeline "4160x1440" 67.81 4160 4192 4448 4480 1440 1475 1478 1513
+ Modeline "4096x2304" 249.25 4096 4296 4720 5344 2304 2307 2312 2333
+ Modeline "4096x2160" 111.25 4096 4200 4608 5120 2160 2163 2173 2176
+ Modeline "4000x1660" 170.32 4000 4128 4536 5072 1660 1661 1664 1679
+ Modeline "4000x1440" 145.00 4000 4088 4488 4976 1440 1441 1444 1457
+ Modeline "3904x1440" 63.70 3904 3936 4176 4208 1440 1475 1478 1513
+ Modeline "3840x2880" 133.43 3840 3872 4376 4408 2880 2950 2955 3025
+ Modeline "3840x2560" 116.93 3840 3872 4312 4344 2560 2622 2627 2689
+ Modeline "3840x2160" 104.25 3840 3944 4320 4800 2160 2163 2168 2175
+ Modeline "3840x2048" 91.45 3840 3872 4216 4248 2048 2097 2101 2151
+ Modeline "3840x1200" 108.89 3840 3872 4280 4312 1200 1228 1232 1261
+ Modeline "3840x1080" 100.38 3840 3848 4216 4592 1080 1081 1084 1093
+ Modeline "3864x1050" 94.58 3864 3896 4248 4280 1050 1074 1078 1103
+ Modeline "3600x1200" 106.06 3600 3632 3984 4368 1200 1201 1204 1214
+ Modeline "3600x1080" 91.02 3600 3632 3976 4008 1080 1105 1109 1135
+ Modeline "3520x1196" 99.53 3520 3552 3928 3960 1196 1224 1228 1256
+ Modeline "3360x2560" 102.55 3360 3392 3776 3808 2560 2622 2627 2689
+ Modeline "3360x1050" 293.75 3360 3576 3928 4496 1050 1053 1063 1089
+ Modeline "3288x1080" 39.76 3288 3320 3464 3496 1080 1106 1108 1135
+ Modeline "3200x1800" 233.00 3200 3384 3720 4240 1800 1803 1808 1834
+ Modeline "3200x1080" 236.16 3200 3232 4128 4160 1080 1103 1112 1135
+ Modeline "3120x2560" 95.36 3120 3152 3512 3544 2560 2622 2627 2689
+ Modeline "3120x1050" 272.75 3120 3320 3648 4176 1050 1053 1063 1089
+ Modeline "3072x2560" 93.92 3072 3104 3456 3488 2560 2622 2627 2689
+ Modeline "3008x1692" 130.93 3008 3112 3416 3824 1692 1693 1696 1712
+ Modeline "3000x2560" 91.77 3000 3032 3376 3408 2560 2622 2627 2689
+ Modeline "2880x1620" 396.25 2880 3096 3408 3936 1620 1623 1628 1679
+ Modeline "2728x1680" 148.02 2728 2760 3320 3352 1680 1719 1726 1765
+ Modeline "2560x2240" 151.55 2560 2688 2952 3344 2240 2241 2244 2266
+ Modeline "2560x1600" 47.12 2560 2592 2768 2800 1600 1639 1642 1681
+ Modeline "2560x1440" 42.12 2560 2592 2752 2784 1440 1475 1478 1513
+ Modeline "2560x1400" 267.86 2560 2592 3608 3640 1400 1429 1441 1471
+ Modeline "2048x2048" 49.47 2048 2080 2264 2296 2048 2097 2101 2151
+ Modeline "2048x1536" 80.06 2048 2104 2312 2576 1536 1537 1540 1554
+ Modeline "2048x1152" 197.97 2048 2184 2408 2768 1152 1153 1156 1192
+ Modeline "2048x1152" 165.92 2048 2080 2704 2736 1152 1176 1186 1210
+ Modeline "1920x1440" 69.47 1920 1960 2152 2384 1440 1441 1444 1457
+ Modeline "1920x1200" 26.28 1920 1952 2048 2080 1200 1229 1231 1261
+ Modeline "1920x1080" 23.53 1920 1952 2040 2072 1080 1106 1108 1135
+ Modeline "1728x1520" 205.42 1728 1760 2536 2568 1520 1552 1564 1597
+ Modeline "1680x1050" 20.08 1680 1712 1784 1816 1050 1075 1077 1103
+ Modeline "1600x1200" 22.04 1600 1632 1712 1744 1200 1229 1231 1261
+ Modeline "1600x900" 33.92 1600 1632 1760 1792 900 921 924 946
+ Modeline "1440x900" 30.66 1440 1472 1584 1616 900 921 924 946
+ Modeline "1400x900" 103.50 1400 1480 1624 1848 900 903 913 934
+ ModeLine "1366x768" 72.00 1366 1414 1446 1494 768 771 777 803
+ Modeline "1360x768" 24.49 1360 1392 1480 1512 768 786 789 807
+ Modeline "1280x1024" 31.50 1280 1312 1424 1456 1024 1048 1052 1076
+ Modeline "1280x800" 24.15 1280 1312 1400 1432 800 819 822 841
+ Modeline "1280x768" 23.11 1280 1312 1392 1424 768 786 789 807
+ Modeline "1280x720" 59.42 1280 1312 1536 1568 720 735 741 757
+ Modeline "1024x768" 18.71 1024 1056 1120 1152 768 786 789 807
+ Modeline "1024x640" 41.98 1024 1056 1208 1240 640 653 659 673
+ Modeline "1024x576" 46.50 1024 1064 1160 1296 576 579 584 599
+ Modeline "768x1024" 19.50 768 800 872 904 1024 1048 1052 1076
+ Modeline "960x540" 40.75 960 992 1088 1216 540 543 548 562
+ Modeline "864x486" 32.50 864 888 968 1072 486 489 494 506
+ Modeline "720x405" 22.50 720 744 808 896 405 408 413 422
+ Modeline "640x360" 14.75 640 664 720 800 360 363 368 374
+ #common resolutions for android devices (both orientations):
+ Modeline "800x1280" 25.89 800 832 928 960 1280 1310 1315 1345
+ Modeline "1280x800" 24.15 1280 1312 1400 1432 800 819 822 841
+ Modeline "720x1280" 30.22 720 752 864 896 1280 1309 1315 1345
+ Modeline "1280x720" 27.41 1280 1312 1416 1448 720 737 740 757
+ Modeline "768x1024" 24.93 768 800 888 920 1024 1047 1052 1076
+ Modeline "1024x768" 23.77 1024 1056 1144 1176 768 785 789 807
+ Modeline "600x1024" 19.90 600 632 704 736 1024 1047 1052 1076
+ Modeline "1024x600" 18.26 1024 1056 1120 1152 600 614 617 631
+ Modeline "536x960" 16.74 536 568 624 656 960 982 986 1009
+ Modeline "960x536" 15.23 960 992 1048 1080 536 548 551 563
+ Modeline "600x800" 15.17 600 632 688 720 800 818 822 841
+ Modeline "800x600" 14.50 800 832 880 912 600 614 617 631
+ Modeline "480x854" 13.34 480 512 560 592 854 873 877 897
+ Modeline "848x480" 12.09 848 880 920 952 480 491 493 505
+ Modeline "480x800" 12.43 480 512 552 584 800 818 822 841
+ Modeline "800x480" 11.46 800 832 872 904 480 491 493 505
+ #resolutions for android devices (both orientations)
+ #minus the status bar
+ #38px status bar (and width rounded up)
+ Modeline "800x1242" 25.03 800 832 920 952 1242 1271 1275 1305
+ Modeline "1280x762" 22.93 1280 1312 1392 1424 762 780 783 801
+ Modeline "720x1242" 29.20 720 752 856 888 1242 1271 1276 1305
+ Modeline "1280x682" 25.85 1280 1312 1408 1440 682 698 701 717
+ Modeline "768x986" 23.90 768 800 888 920 986 1009 1013 1036
+ Modeline "1024x730" 22.50 1024 1056 1136 1168 730 747 750 767
+ Modeline "600x986" 19.07 600 632 704 736 986 1009 1013 1036
+ Modeline "1024x562" 17.03 1024 1056 1120 1152 562 575 578 591
+ Modeline "536x922" 16.01 536 568 624 656 922 943 947 969
+ Modeline "960x498" 14.09 960 992 1040 1072 498 509 511 523
+ Modeline "600x762" 14.39 600 632 680 712 762 779 783 801
+ Modeline "800x562" 13.52 800 832 880 912 562 575 578 591
+ Modeline "480x810" 12.59 480 512 552 584 810 828 832 851
+ Modeline "848x442" 11.09 848 880 920 952 442 452 454 465
+ Modeline "480x762" 11.79 480 512 552 584 762 779 783 801
+ '';
+
+ services.xserver.resolutions = [
+ {x="8192"; y="4096";}
+ {x="5120"; y="3200";}
+ {x="3840"; y="2880";}
+ {x="3840"; y="2560";}
+ {x="3840"; y="2048";}
+ {x="3840"; y="2160";}
+ {x="2048"; y="2048";}
+ {x="2560"; y="1600";}
+ {x="1920"; y="1440";}
+ {x="1920"; y="1200";}
+ {x="1920"; y="1080";}
+ {x="1600"; y="1200";}
+ {x="1680"; y="1050";}
+ {x="1600"; y="900";}
+ {x="1400"; y="1050";}
+ {x="1440"; y="900";}
+ {x="1280"; y="1024";}
+ {x="1366"; y="768";}
+ {x="1280"; y="800";}
+ {x="1024"; y="768";}
+ {x="1024"; y="600";}
+ {x="800"; y="600";}
+ {x="320"; y="200";}
+ ];
+
+ services.xserver.serverFlagsSection = ''
+ Option "DontVTSwitch" "true"
+ Option "PciForceNone" "true"
+ Option "AutoEnableDevices" "false"
+ Option "AutoAddDevices" "false"
+ '';
+
+ services.xserver.deviceSection = ''
+ VideoRam 192000
+ '';
+
+ services.xserver.displayManager.job.execCmd = ''
+ ${optionalString (cfg.pulseaudio)
+ "export PULSE_COOKIE=/run/pulse/.config/pulse/cookie"}
+ exec ${pkgs.xpra}/bin/xpra start \
+ --daemon=off \
+ --log-dir=/var/log \
+ --log-file=xpra.log \
+ --opengl=on \
+ --clipboard=on \
+ --notifications=on \
+ --speaker=yes \
+ --mdns=no \
+ --pulseaudio=no \
+ ${optionalString (cfg.pulseaudio) "--sound-source=pulse"} \
+ --socket-dirs=/run/xpra \
+ --xvfb="xpra_Xdummy ${concatStringsSep " " dmcfg.xserverArgs}" \
+ ${optionalString (cfg.bindTcp != null) "--bind-tcp=${cfg.bindTcp}"} \
+ --auth=${cfg.auth} \
+ ${concatStringsSep " " cfg.extraOptions}
+ '';
+
+ services.xserver.terminateOnReset = false;
+
+ environment.systemPackages = [pkgs.xpra];
+
+ virtualisation.virtualbox.guest.x11 = false;
+ hardware.pulseaudio.enable = mkDefault cfg.pulseaudio;
+ hardware.pulseaudio.systemWide = mkDefault cfg.pulseaudio;
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/extra-layouts.nix b/nixpkgs/nixos/modules/services/x11/extra-layouts.nix
new file mode 100644
index 00000000000..1af98a1318b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/extra-layouts.nix
@@ -0,0 +1,168 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ layouts = config.services.xserver.extraLayouts;
+
+ layoutOpts = {
+ options = {
+ description = mkOption {
+ type = types.str;
+ description = "A short description of the layout.";
+ };
+
+ languages = mkOption {
+ type = types.listOf types.str;
+ description =
+ ''
+ A list of languages provided by the layout.
+ (Use ISO 639-2 codes, for example: "eng" for english)
+ '';
+ };
+
+ compatFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The path to the xkb compat file.
+ This file sets the compatibility state, used to preserve
+ compatibility with xkb-unaware programs.
+ It must contain a <literal>xkb_compat "name" { ... }</literal> block.
+ '';
+ };
+
+ geometryFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The path to the xkb geometry file.
+ This (completely optional) file describes the physical layout of
+ keyboard, which maybe be used by programs to depict it.
+ It must contain a <literal>xkb_geometry "name" { ... }</literal> block.
+ '';
+ };
+
+ keycodesFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The path to the xkb keycodes file.
+ This file specifies the range and the interpretation of the raw
+ keycodes sent by the keyboard.
+ It must contain a <literal>xkb_keycodes "name" { ... }</literal> block.
+ '';
+ };
+
+ symbolsFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The path to the xkb symbols file.
+ This is the most important file: it defines which symbol or action
+ maps to each key and must contain a
+ <literal>xkb_symbols "name" { ... }</literal> block.
+ '';
+ };
+
+ typesFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ The path to the xkb types file.
+ This file specifies the key types that can be associated with
+ the various keyboard keys.
+ It must contain a <literal>xkb_types "name" { ... }</literal> block.
+ '';
+ };
+
+ };
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options.services.xserver = {
+ extraLayouts = mkOption {
+ type = types.attrsOf (types.submodule layoutOpts);
+ default = {};
+ example = literalExample
+ ''
+ {
+ mine = {
+ description = "My custom xkb layout.";
+ languages = [ "eng" ];
+ symbolsFile = /path/to/my/layout;
+ };
+ }
+ '';
+ description = ''
+ Extra custom layouts that will be included in the xkb configuration.
+ Information on how to create a new layout can be found here:
+ <link xlink:href="https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts"></link>.
+ For more examples see
+ <link xlink:href="https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples"></link>
+ '';
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf (layouts != { }) {
+
+ # We don't override xkeyboard_config directly to
+ # reduce the amount of packages to be recompiled.
+ # Only the following packages are necessary to set
+ # a custom layout anyway:
+ nixpkgs.overlays = lib.singleton (self: super: {
+
+ xkb_patched = self.xorg.xkeyboardconfig_custom {
+ layouts = config.services.xserver.extraLayouts;
+ };
+
+ xorg = super.xorg // {
+ xorgserver = super.xorg.xorgserver.overrideAttrs (old: {
+ configureFlags = old.configureFlags ++ [
+ "--with-xkb-bin-directory=${self.xorg.xkbcomp}/bin"
+ "--with-xkb-path=${self.xkb_patched}/share/X11/xkb"
+ ];
+ });
+
+ setxkbmap = super.xorg.setxkbmap.overrideAttrs (old: {
+ postInstall =
+ ''
+ mkdir -p $out/share
+ ln -sfn ${self.xkb_patched}/etc/X11 $out/share/X11
+ '';
+ });
+
+ xkbcomp = super.xorg.xkbcomp.overrideAttrs (old: {
+ configureFlags = "--with-xkb-config-root=${self.xkb_patched}/share/X11/xkb";
+ });
+
+ };
+
+ ckbcomp = super.ckbcomp.override {
+ xkeyboard_config = self.xkb_patched;
+ };
+
+ xkbvalidate = super.xkbvalidate.override {
+ libxkbcommon = self.libxkbcommon.override {
+ xkeyboard_config = self.xkb_patched;
+ };
+ };
+
+ });
+
+ services.xserver = {
+ xkbDir = "${pkgs.xkb_patched}/etc/X11/xkb";
+ exportConfiguration = config.services.xserver.displayManager.startx.enable;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/fractalart.nix b/nixpkgs/nixos/modules/services/x11/fractalart.nix
new file mode 100644
index 00000000000..448248a5879
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/fractalart.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.fractalart;
+in {
+ options.services.fractalart = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = "Enable FractalArt for generating colorful wallpapers on login";
+ };
+
+ width = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 1920;
+ description = "Screen width";
+ };
+
+ height = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 1080;
+ description = "Screen height";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.haskellPackages.FractalArt ];
+ services.xserver.displayManager.sessionCommands =
+ "${pkgs.haskellPackages.FractalArt}/bin/FractalArt --no-bg -f .background-image"
+ + optionalString (cfg.width != null) " -w ${toString cfg.width}"
+ + optionalString (cfg.height != null) " -h ${toString cfg.height}";
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix b/nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix
new file mode 100644
index 00000000000..9ad926369ec
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.gdk-pixbuf;
+
+ # Get packages to generate the cache for. We always include gdk-pixbuf.
+ effectivePackages = unique ([pkgs.gdk-pixbuf] ++ cfg.modulePackages);
+
+ # Generate the cache file by running gdk-pixbuf-query-loaders for each
+ # package and concatenating the results.
+ loadersCache = pkgs.runCommand "gdk-pixbuf-loaders.cache" { preferLocalBuild = true; } ''
+ (
+ for package in ${concatStringsSep " " effectivePackages}; do
+ module_dir="$package/${pkgs.gdk-pixbuf.moduleDir}"
+ if [[ ! -d $module_dir ]]; then
+ echo "Warning (services.xserver.gdk-pixbuf): missing module directory $module_dir" 1>&2
+ continue
+ fi
+ GDK_PIXBUF_MODULEDIR="$module_dir" \
+ ${pkgs.gdk-pixbuf.dev}/bin/gdk-pixbuf-query-loaders
+ done
+ ) > "$out"
+ '';
+in
+
+{
+ options = {
+ services.xserver.gdk-pixbuf.modulePackages = mkOption {
+ type = types.listOf types.package;
+ default = [ ];
+ description = "Packages providing GDK-Pixbuf modules, for cache generation.";
+ };
+ };
+
+ # If there is any package configured in modulePackages, we generate the
+ # loaders.cache based on that and set the environment variable
+ # GDK_PIXBUF_MODULE_FILE to point to it.
+ config = mkIf (cfg.modulePackages != []) {
+ environment.variables = {
+ GDK_PIXBUF_MODULE_FILE = "${loadersCache}";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/cmt.nix b/nixpkgs/nixos/modules/services/x11/hardware/cmt.nix
new file mode 100644
index 00000000000..95353e92098
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/hardware/cmt.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+cfg = config.services.xserver.cmt;
+etcPath = "X11/xorg.conf.d";
+
+in {
+
+ options = {
+
+ services.xserver.cmt = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable chrome multitouch input (cmt). Touchpad drivers that are configured for chromebooks.";
+ };
+ models = mkOption {
+ type = types.enum [ "atlas" "banjo" "candy" "caroline" "cave" "celes" "clapper" "cyan" "daisy" "elan" "elm" "enguarde" "eve" "expresso" "falco" "gandof" "glimmer" "gnawty" "heli" "kevin" "kip" "leon" "lulu" "orco" "pbody" "peppy" "pi" "pit" "puppy" "quawks" "rambi" "samus" "snappy" "spring" "squawks" "swanky" "winky" "wolf" "auron_paine" "auron_yuna" "daisy_skate" "nyan_big" "nyan_blaze" "veyron_jaq" "veyron_jerry" "veyron_mighty" "veyron_minnie" "veyron_speedy" ];
+ example = "banjo";
+ description = ''
+ Which models to enable cmt for. Enter the Code Name for your Chromebook.
+ Code Name can be found at <link xlink:href="https://www.chromium.org/chromium-os/developer-information-for-chrome-os-devices" />.
+ '';
+ };
+ }; #closes services
+ }; #closes options
+
+ config = mkIf cfg.enable {
+
+ services.xserver.modules = [ pkgs.xf86_input_cmt ];
+
+ environment.etc = {
+ "${etcPath}/40-touchpad-cmt.conf" = {
+ source = "${pkgs.chromium-xorg-conf}/40-touchpad-cmt.conf";
+ };
+ "${etcPath}/50-touchpad-cmt-${cfg.models}.conf" = {
+ source = "${pkgs.chromium-xorg-conf}/50-touchpad-cmt-${cfg.models}.conf";
+ };
+ "${etcPath}/60-touchpad-cmt-${cfg.models}.conf" = {
+ source = "${pkgs.chromium-xorg-conf}/60-touchpad-cmt-${cfg.models}.conf";
+ };
+ };
+
+ assertions = [
+ {
+ assertion = !config.services.xserver.libinput.enable;
+ message = "cmt and libinput are incompatible, you cannot enable both (in services.xserver).";
+ }
+ ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix b/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix
new file mode 100644
index 00000000000..bd289976532
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix
@@ -0,0 +1,247 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.xserver.libinput;
+ xorgBool = v: if v then "on" else "off";
+in {
+
+ options = {
+
+ services.xserver.libinput = {
+
+ enable = mkEnableOption "libinput";
+
+ dev = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/dev/input/event0";
+ description =
+ ''
+ Path for touchpad device. Set to null to apply to any
+ auto-detected touchpad.
+ '';
+ };
+
+ accelProfile = mkOption {
+ type = types.enum [ "flat" "adaptive" ];
+ default = "adaptive";
+ example = "flat";
+ description =
+ ''
+ Sets the pointer acceleration profile to the given profile.
+ Permitted values are adaptive, flat.
+ Not all devices support this option or all profiles.
+ If a profile is unsupported, the default profile for this is used.
+ <literal>flat</literal>: Pointer motion is accelerated by a constant
+ (device-specific) factor, depending on the current speed.
+ <literal>adaptive</literal>: Pointer acceleration depends on the input speed.
+ This is the default profile for most devices.
+ '';
+ };
+
+ accelSpeed = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
+ };
+
+ buttonMapping = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description =
+ ''
+ Sets the logical button mapping for this device, see XSetPointerMapping(3). The string must
+ be a space-separated list of button mappings in the order of the logical buttons on the
+ device, starting with button 1. The default mapping is "1 2 3 ... 32". A mapping of 0 deac‐
+ tivates the button. Multiple buttons can have the same mapping. Invalid mapping strings are
+ discarded and the default mapping is used for all buttons. Buttons not specified in the
+ user's mapping use the default mapping. See section BUTTON MAPPING for more details.
+ '';
+ };
+
+ calibrationMatrix = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description =
+ ''
+ A string of 9 space-separated floating point numbers. Sets the calibration matrix to the
+ 3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
+ '';
+ };
+
+ clickMethod = mkOption {
+ type = types.nullOr (types.enum [ "none" "buttonareas" "clickfinger" ]);
+ default = null;
+ description =
+ ''
+ Enables a click method. Permitted values are <literal>none</literal>,
+ <literal>buttonareas</literal>, <literal>clickfinger</literal>.
+ Not all devices support all methods, if an option is unsupported,
+ the default click method for this device is used.
+ '';
+ };
+
+ leftHanded = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enables left-handed button orientation, i.e. swapping left and right buttons.";
+ };
+
+ middleEmulation = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Enables middle button emulation. When enabled, pressing the left and right buttons
+ simultaneously produces a middle mouse button click.
+ '';
+ };
+
+ naturalScrolling = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enables or disables natural scrolling behavior.";
+ };
+
+ scrollButton = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 1;
+ description =
+ ''
+ Designates a button as scroll button. If the ScrollMethod is button and the button is logically
+ held down, x/y axis movement is converted into scroll events.
+ '';
+ };
+
+ scrollMethod = mkOption {
+ type = types.enum [ "twofinger" "edge" "button" "none" ];
+ default = "twofinger";
+ example = "edge";
+ description =
+ ''
+ Specify the scrolling method: <literal>twofinger</literal>, <literal>edge</literal>,
+ or <literal>none</literal>
+ '';
+ };
+
+ horizontalScrolling = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Disables horizontal scrolling. When disabled, this driver will discard any horizontal scroll
+ events from libinput. Note that this does not disable horizontal scrolling, it merely
+ discards the horizontal axis from any scroll events.
+ '';
+ };
+
+ sendEventsMode = mkOption {
+ type = types.enum [ "disabled" "enabled" "disabled-on-external-mouse" ];
+ default = "enabled";
+ example = "disabled";
+ description =
+ ''
+ Sets the send events mode to <literal>disabled</literal>, <literal>enabled</literal>,
+ or <literal>disabled-on-external-mouse</literal>
+ '';
+ };
+
+ tapping = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Enables or disables tap-to-click behavior.
+ '';
+ };
+
+ tappingDragLock = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Enables or disables drag lock during tapping behavior. When enabled, a finger up during tap-
+ and-drag will not immediately release the button. If the finger is set down again within the
+ timeout, the draging process continues.
+ '';
+ };
+
+ disableWhileTyping = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ Disable input method while typing.
+ '';
+ };
+
+ additionalOptions = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ Option "DragLockButtons" "L1 B1 L2 B2"
+ '';
+ description = "Additional options for libinput touchpad driver.";
+ };
+
+ };
+
+ };
+
+
+ config = mkIf cfg.enable {
+
+ services.xserver.modules = [ pkgs.xorg.xf86inputlibinput ];
+
+ environment.systemPackages = [ pkgs.xorg.xf86inputlibinput ];
+
+ environment.etc = [
+ (let cfgPath = "X11/xorg.conf.d/40-libinput.conf"; in {
+ source = pkgs.xorg.xf86inputlibinput.out + "/share/" + cfgPath;
+ target = cfgPath;
+ })
+ ];
+
+ services.udev.packages = [ pkgs.libinput.out ];
+
+ services.xserver.config =
+ ''
+ # Automatically enable the libinput driver for all touchpads.
+ Section "InputClass"
+ Identifier "libinputConfiguration"
+ MatchIsTouchpad "on"
+ ${optionalString (cfg.dev != null) ''MatchDevicePath "${cfg.dev}"''}
+ Driver "libinput"
+ Option "AccelProfile" "${cfg.accelProfile}"
+ ${optionalString (cfg.accelSpeed != null) ''Option "AccelSpeed" "${cfg.accelSpeed}"''}
+ ${optionalString (cfg.buttonMapping != null) ''Option "ButtonMapping" "${cfg.buttonMapping}"''}
+ ${optionalString (cfg.calibrationMatrix != null) ''Option "CalibrationMatrix" "${cfg.calibrationMatrix}"''}
+ ${optionalString (cfg.clickMethod != null) ''Option "ClickMethod" "${cfg.clickMethod}"''}
+ Option "LeftHanded" "${xorgBool cfg.leftHanded}"
+ Option "MiddleEmulation" "${xorgBool cfg.middleEmulation}"
+ Option "NaturalScrolling" "${xorgBool cfg.naturalScrolling}"
+ ${optionalString (cfg.scrollButton != null) ''Option "ScrollButton" "${toString cfg.scrollButton}"''}
+ Option "ScrollMethod" "${cfg.scrollMethod}"
+ Option "HorizontalScrolling" "${xorgBool cfg.horizontalScrolling}"
+ Option "SendEventsMode" "${cfg.sendEventsMode}"
+ Option "Tapping" "${xorgBool cfg.tapping}"
+ Option "TappingDragLock" "${xorgBool cfg.tappingDragLock}"
+ Option "DisableWhileTyping" "${xorgBool cfg.disableWhileTyping}"
+ ${cfg.additionalOptions}
+ EndSection
+ '';
+
+ assertions = [
+ # already present in synaptics.nix
+ /* {
+ assertion = !config.services.xserver.synaptics.enable;
+ message = "Synaptics and libinput are incompatible, you cannot enable both (in services.xserver).";
+ } */
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/multitouch.nix b/nixpkgs/nixos/modules/services/x11/hardware/multitouch.nix
new file mode 100644
index 00000000000..c03bb3b494f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/hardware/multitouch.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.xserver.multitouch;
+ disabledTapConfig = ''
+ Option "MaxTapTime" "0"
+ Option "MaxTapMove" "0"
+ Option "TapButton1" "0"
+ Option "TapButton2" "0"
+ Option "TapButton3" "0"
+ '';
+in {
+
+ options = {
+
+ services.xserver.multitouch = {
+
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable multitouch touchpad support.";
+ };
+
+ invertScroll = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to invert scrolling direction à la OSX Lion";
+ };
+
+ ignorePalm = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to ignore touches detected as being the palm (i.e when typing)";
+ };
+
+ tapButtons = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to enable tap buttons.";
+ };
+
+ buttonsMap = mkOption {
+ type = types.listOf types.int;
+ default = [3 2 0];
+ example = [1 3 2];
+ description = "Remap touchpad buttons.";
+ apply = map toString;
+ };
+
+ additionalOptions = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ Option "ScaleDistance" "50"
+ Option "RotateDistance" "60"
+ '';
+ description = ''
+ Additional options for mtrack touchpad driver.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ services.xserver.modules = [ pkgs.xf86_input_mtrack ];
+
+ services.xserver.config =
+ ''
+ # Automatically enable the multitouch driver
+ Section "InputClass"
+ MatchIsTouchpad "on"
+ Identifier "Touchpads"
+ Driver "mtrack"
+ Option "IgnorePalm" "${boolToString cfg.ignorePalm}"
+ Option "ClickFinger1" "${builtins.elemAt cfg.buttonsMap 0}"
+ Option "ClickFinger2" "${builtins.elemAt cfg.buttonsMap 1}"
+ Option "ClickFinger3" "${builtins.elemAt cfg.buttonsMap 2}"
+ ${optionalString (!cfg.tapButtons) disabledTapConfig}
+ ${optionalString cfg.invertScroll ''
+ Option "ScrollUpButton" "5"
+ Option "ScrollDownButton" "4"
+ Option "ScrollLeftButton" "7"
+ Option "ScrollRightButton" "6"
+ ''}
+ ${cfg.additionalOptions}
+ EndSection
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix b/nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix
new file mode 100644
index 00000000000..22af869f1f8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix
@@ -0,0 +1,213 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.xserver.synaptics;
+ tapConfig = if cfg.tapButtons then enabledTapConfig else disabledTapConfig;
+ enabledTapConfig = ''
+ Option "MaxTapTime" "180"
+ Option "MaxTapMove" "220"
+ Option "TapButton1" "${builtins.elemAt cfg.fingersMap 0}"
+ Option "TapButton2" "${builtins.elemAt cfg.fingersMap 1}"
+ Option "TapButton3" "${builtins.elemAt cfg.fingersMap 2}"
+ '';
+ disabledTapConfig = ''
+ Option "MaxTapTime" "0"
+ Option "MaxTapMove" "0"
+ Option "TapButton1" "0"
+ Option "TapButton2" "0"
+ Option "TapButton3" "0"
+ '';
+ pkg = pkgs.xorg.xf86inputsynaptics;
+ etcFile = "X11/xorg.conf.d/70-synaptics.conf";
+in {
+
+ options = {
+
+ services.xserver.synaptics = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable touchpad support. Deprecated: Consider services.xserver.libinput.enable.";
+ };
+
+ dev = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/dev/input/event0";
+ description =
+ ''
+ Path for touchpad device. Set to null to apply to any
+ auto-detected touchpad.
+ '';
+ };
+
+ accelFactor = mkOption {
+ type = types.nullOr types.str;
+ default = "0.001";
+ description = "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
+ };
+
+ minSpeed = mkOption {
+ type = types.nullOr types.str;
+ default = "0.6";
+ description = "Cursor speed factor for precision finger motion.";
+ };
+
+ maxSpeed = mkOption {
+ type = types.nullOr types.str;
+ default = "1.0";
+ description = "Cursor speed factor for highest-speed finger motion.";
+ };
+
+ scrollDelta = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 75;
+ description = "Move distance of the finger for a scroll event.";
+ };
+
+ twoFingerScroll = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable two-finger drag-scrolling. Overridden by horizTwoFingerScroll and vertTwoFingerScroll.";
+ };
+
+ horizTwoFingerScroll = mkOption {
+ type = types.bool;
+ default = cfg.twoFingerScroll;
+ description = "Whether to enable horizontal two-finger drag-scrolling.";
+ };
+
+ vertTwoFingerScroll = mkOption {
+ type = types.bool;
+ default = cfg.twoFingerScroll;
+ description = "Whether to enable vertical two-finger drag-scrolling.";
+ };
+
+ horizEdgeScroll = mkOption {
+ type = types.bool;
+ default = ! cfg.horizTwoFingerScroll;
+ description = "Whether to enable horizontal edge drag-scrolling.";
+ };
+
+ vertEdgeScroll = mkOption {
+ type = types.bool;
+ default = ! cfg.vertTwoFingerScroll;
+ description = "Whether to enable vertical edge drag-scrolling.";
+ };
+
+ tapButtons = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to enable tap buttons.";
+ };
+
+ buttonsMap = mkOption {
+ type = types.listOf types.int;
+ default = [1 2 3];
+ example = [1 3 2];
+ description = "Remap touchpad buttons.";
+ apply = map toString;
+ };
+
+ fingersMap = mkOption {
+ type = types.listOf types.int;
+ default = [1 2 3];
+ example = [1 3 2];
+ description = "Remap several-fingers taps.";
+ apply = map toString;
+ };
+
+ palmDetect = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable palm detection (hardware support required)";
+ };
+
+ palmMinWidth = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 5;
+ description = "Minimum finger width at which touch is considered a palm";
+ };
+
+ palmMinZ = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 20;
+ description = "Minimum finger pressure at which touch is considered a palm";
+ };
+
+ horizontalScroll = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to enable horizontal scrolling (on touchpad)";
+ };
+
+ additionalOptions = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ Option "RTCornerButton" "2"
+ Option "RBCornerButton" "3"
+ '';
+ description = ''
+ Additional options for synaptics touchpad driver.
+ '';
+ };
+
+ };
+
+ };
+
+
+ config = mkIf cfg.enable {
+
+ services.xserver.modules = [ pkg.out ];
+
+ environment.etc.${etcFile}.source =
+ "${pkg.out}/share/X11/xorg.conf.d/70-synaptics.conf";
+
+ environment.systemPackages = [ pkg ];
+
+ services.xserver.config =
+ ''
+ # Automatically enable the synaptics driver for all touchpads.
+ Section "InputClass"
+ Identifier "synaptics touchpad catchall"
+ MatchIsTouchpad "on"
+ ${optionalString (cfg.dev != null) ''MatchDevicePath "${cfg.dev}"''}
+ Driver "synaptics"
+ ${optionalString (cfg.minSpeed != null) ''Option "MinSpeed" "${cfg.minSpeed}"''}
+ ${optionalString (cfg.maxSpeed != null) ''Option "MaxSpeed" "${cfg.maxSpeed}"''}
+ ${optionalString (cfg.accelFactor != null) ''Option "AccelFactor" "${cfg.accelFactor}"''}
+ ${optionalString cfg.tapButtons tapConfig}
+ Option "ClickFinger1" "${builtins.elemAt cfg.buttonsMap 0}"
+ Option "ClickFinger2" "${builtins.elemAt cfg.buttonsMap 1}"
+ Option "ClickFinger3" "${builtins.elemAt cfg.buttonsMap 2}"
+ Option "VertTwoFingerScroll" "${if cfg.vertTwoFingerScroll then "1" else "0"}"
+ Option "HorizTwoFingerScroll" "${if cfg.horizTwoFingerScroll then "1" else "0"}"
+ Option "VertEdgeScroll" "${if cfg.vertEdgeScroll then "1" else "0"}"
+ Option "HorizEdgeScroll" "${if cfg.horizEdgeScroll then "1" else "0"}"
+ ${optionalString cfg.palmDetect ''Option "PalmDetect" "1"''}
+ ${optionalString (cfg.palmMinWidth != null) ''Option "PalmMinWidth" "${toString cfg.palmMinWidth}"''}
+ ${optionalString (cfg.palmMinZ != null) ''Option "PalmMinZ" "${toString cfg.palmMinZ}"''}
+ ${optionalString (cfg.scrollDelta != null) ''Option "VertScrollDelta" "${toString cfg.scrollDelta}"''}
+ ${if !cfg.horizontalScroll then ''Option "HorizScrollDelta" "0"''
+ else (optionalString (cfg.scrollDelta != null) ''Option "HorizScrollDelta" "${toString cfg.scrollDelta}"'')}
+ ${cfg.additionalOptions}
+ EndSection
+ '';
+
+ assertions = [
+ {
+ assertion = !config.services.xserver.libinput.enable;
+ message = "Synaptics and libinput are incompatible, you cannot enable both (in services.xserver).";
+ }
+ ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/wacom.nix b/nixpkgs/nixos/modules/services/x11/hardware/wacom.nix
new file mode 100644
index 00000000000..a27889c36a7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/hardware/wacom.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver.wacom;
+
+in
+
+{
+
+ options = {
+
+ services.xserver.wacom = {
+
+ enable = mkOption {
+ default = false;
+ description = ''
+ Whether to enable the Wacom touchscreen/digitizer/tablet.
+ If you ever have any issues such as, try switching to terminal (ctrl-alt-F1) and back
+ which will make Xorg reconfigure the device ?
+
+ If you're not satisfied by the default behaviour you can override
+ <option>environment.etc."X11/xorg.conf.d/70-wacom.conf"</option> in
+ configuration.nix easily.
+ '';
+ };
+
+ };
+
+ };
+
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.xf86_input_wacom ]; # provides xsetwacom
+
+ services.xserver.modules = [ pkgs.xf86_input_wacom ];
+
+ services.udev.packages = [ pkgs.xf86_input_wacom ];
+
+ environment.etc."X11/xorg.conf.d/70-wacom.conf".source = "${pkgs.xf86_input_wacom}/share/X11/xorg.conf.d/70-wacom.conf";
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/redshift.nix b/nixpkgs/nixos/modules/services/x11/redshift.nix
new file mode 100644
index 00000000000..21b0b33553a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/redshift.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.redshift;
+ lcfg = config.location;
+
+in {
+
+ imports = [
+ (mkChangedOptionModule [ "services" "redshift" "latitude" ] [ "location" "latitude" ]
+ (config:
+ let value = getAttrFromPath [ "services" "redshift" "latitude" ] config;
+ in if value == null then
+ throw "services.redshift.latitude is set to null, you can remove this"
+ else builtins.fromJSON value))
+ (mkChangedOptionModule [ "services" "redshift" "longitude" ] [ "location" "longitude" ]
+ (config:
+ let value = getAttrFromPath [ "services" "redshift" "longitude" ] config;
+ in if value == null then
+ throw "services.redshift.longitude is set to null, you can remove this"
+ else builtins.fromJSON value))
+ (mkRenamedOptionModule [ "services" "redshift" "provider" ] [ "location" "provider" ])
+ ];
+
+ options.services.redshift = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable Redshift to change your screen's colour temperature depending on
+ the time of day.
+ '';
+ };
+
+ temperature = {
+ day = mkOption {
+ type = types.int;
+ default = 5500;
+ description = ''
+ Colour temperature to use during the day, between
+ <literal>1000</literal> and <literal>25000</literal> K.
+ '';
+ };
+ night = mkOption {
+ type = types.int;
+ default = 3700;
+ description = ''
+ Colour temperature to use at night, between
+ <literal>1000</literal> and <literal>25000</literal> K.
+ '';
+ };
+ };
+
+ brightness = {
+ day = mkOption {
+ type = types.str;
+ default = "1";
+ description = ''
+ Screen brightness to apply during the day,
+ between <literal>0.1</literal> and <literal>1.0</literal>.
+ '';
+ };
+ night = mkOption {
+ type = types.str;
+ default = "1";
+ description = ''
+ Screen brightness to apply during the night,
+ between <literal>0.1</literal> and <literal>1.0</literal>.
+ '';
+ };
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.redshift;
+ defaultText = "pkgs.redshift";
+ description = ''
+ redshift derivation to use.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "-v" "-m randr" ];
+ description = ''
+ Additional command-line arguments to pass to
+ <command>redshift</command>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # needed so that .desktop files are installed, which geoclue cares about
+ environment.systemPackages = [ cfg.package ];
+
+ services.geoclue2.appConfig.redshift = {
+ isAllowed = true;
+ isSystem = true;
+ };
+
+ systemd.user.services.redshift =
+ let
+ providerString = if lcfg.provider == "manual"
+ then "${toString lcfg.latitude}:${toString lcfg.longitude}"
+ else lcfg.provider;
+ in
+ {
+ description = "Redshift colour temperature adjuster";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${cfg.package}/bin/redshift \
+ -l ${providerString} \
+ -t ${toString cfg.temperature.day}:${toString cfg.temperature.night} \
+ -b ${toString cfg.brightness.day}:${toString cfg.brightness.night} \
+ ${lib.strings.concatStringsSep " " cfg.extraOptions}
+ '';
+ RestartSec = 3;
+ Restart = "always";
+ };
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/terminal-server.nix b/nixpkgs/nixos/modules/services/x11/terminal-server.nix
new file mode 100644
index 00000000000..503c14c9b62
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/terminal-server.nix
@@ -0,0 +1,56 @@
+# This module implements a terminal service based on ‘x11vnc’. It
+# listens on port 5900 for VNC connections. It then presents a login
+# screen to the user. If the user successfully authenticates, x11vnc
+# checks to see if a X server is already running for that user. If
+# not, a X server (Xvfb) is started for that user. The Xvfb instances
+# persist across VNC sessions.
+
+{ lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ config = {
+
+ services.xserver.enable = true;
+ services.xserver.videoDrivers = [];
+
+ # Enable GDM. Any display manager will do as long as it supports XDMCP.
+ services.xserver.displayManager.gdm.enable = true;
+
+ systemd.sockets.terminal-server =
+ { description = "Terminal Server Socket";
+ wantedBy = [ "sockets.target" ];
+ before = [ "multi-user.target" ];
+ socketConfig.Accept = true;
+ socketConfig.ListenStream = 5900;
+ };
+
+ systemd.services."terminal-server@" =
+ { description = "Terminal Server";
+
+ path =
+ [ pkgs.xorg.xorgserver.out pkgs.gawk pkgs.which pkgs.openssl pkgs.xorg.xauth
+ pkgs.nettools pkgs.shadow pkgs.procps pkgs.utillinux pkgs.bash
+ ];
+
+ environment.FD_GEOM = "1024x786x24";
+ environment.FD_XDMCP_IF = "127.0.0.1";
+ #environment.FIND_DISPLAY_OUTPUT = "/tmp/foo"; # to debug the "find display" script
+
+ serviceConfig =
+ { StandardInput = "socket";
+ StandardOutput = "socket";
+ StandardError = "journal";
+ ExecStart = "@${pkgs.x11vnc}/bin/x11vnc x11vnc -inetd -display WAIT:1024x786:cmd=FINDCREATEDISPLAY-Xvfb.xdmcp -unixpw -ssl SAVE";
+ # Don't kill the X server when the user quits the VNC
+ # connection. FIXME: the X server should run in a
+ # separate systemd session.
+ KillMode = "process";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/unclutter-xfixes.nix b/nixpkgs/nixos/modules/services/x11/unclutter-xfixes.nix
new file mode 100644
index 00000000000..71262431b68
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/unclutter-xfixes.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.unclutter-xfixes;
+
+in {
+ options.services.unclutter-xfixes = {
+
+ enable = mkOption {
+ description = "Enable unclutter-xfixes to hide your mouse cursor when inactive.";
+ type = types.bool;
+ default = false;
+ };
+
+ package = mkOption {
+ description = "unclutter-xfixes derivation to use.";
+ type = types.package;
+ default = pkgs.unclutter-xfixes;
+ defaultText = "pkgs.unclutter-xfixes";
+ };
+
+ timeout = mkOption {
+ description = "Number of seconds before the cursor is marked inactive.";
+ type = types.int;
+ default = 1;
+ };
+
+ threshold = mkOption {
+ description = "Minimum number of pixels considered cursor movement.";
+ type = types.int;
+ default = 1;
+ };
+
+ extraOptions = mkOption {
+ description = "More arguments to pass to the unclutter-xfixes command.";
+ type = types.listOf types.str;
+ default = [];
+ example = [ "exclude-root" "ignore-scrolling" "fork" ];
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.unclutter-xfixes = {
+ description = "unclutter-xfixes";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+ serviceConfig.ExecStart = ''
+ ${cfg.package}/bin/unclutter \
+ --timeout ${toString cfg.timeout} \
+ --jitter ${toString (cfg.threshold - 1)} \
+ ${concatMapStrings (x: " --"+x) cfg.extraOptions} \
+ '';
+ serviceConfig.RestartSec = 3;
+ serviceConfig.Restart = "always";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/unclutter.nix b/nixpkgs/nixos/modules/services/x11/unclutter.nix
new file mode 100644
index 00000000000..5f16a680050
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/unclutter.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.unclutter;
+
+in {
+ options.services.unclutter = {
+
+ enable = mkOption {
+ description = "Enable unclutter to hide your mouse cursor when inactive";
+ type = types.bool;
+ default = false;
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.unclutter;
+ defaultText = "pkgs.unclutter";
+ description = "unclutter derivation to use.";
+ };
+
+ keystroke = mkOption {
+ description = "Wait for a keystroke before hiding the cursor";
+ type = types.bool;
+ default = false;
+ };
+
+ timeout = mkOption {
+ description = "Number of seconds before the cursor is marked inactive";
+ type = types.int;
+ default = 1;
+ };
+
+ threeshold = mkOption {
+ description = "Minimum number of pixels considered cursor movement";
+ type = types.int;
+ default = 1;
+ };
+
+ excluded = mkOption {
+ description = "Names of windows where unclutter should not apply";
+ type = types.listOf types.str;
+ default = [];
+ example = [ "" ];
+ };
+
+ extraOptions = mkOption {
+ description = "More arguments to pass to the unclutter command";
+ type = types.listOf types.str;
+ default = [];
+ example = [ "noevent" "grab" ];
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.unclutter = {
+ description = "unclutter";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+ serviceConfig.ExecStart = ''
+ ${cfg.package}/bin/unclutter \
+ -idle ${toString cfg.timeout} \
+ -jitter ${toString (cfg.threeshold - 1)} \
+ ${optionalString cfg.keystroke "-keystroke"} \
+ ${concatMapStrings (x: " -"+x) cfg.extraOptions} \
+ -not ${concatStringsSep " " cfg.excluded} \
+ '';
+ serviceConfig.PassEnvironment = "DISPLAY";
+ serviceConfig.RestartSec = 3;
+ serviceConfig.Restart = "always";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/urxvtd.nix b/nixpkgs/nixos/modules/services/x11/urxvtd.nix
new file mode 100644
index 00000000000..d916fa5bb39
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/urxvtd.nix
@@ -0,0 +1,48 @@
+{ config, lib, pkgs, ... }:
+
+# maintainer: siddharthist
+
+with lib;
+
+let
+ cfg = config.services.urxvtd;
+in {
+ options.services.urxvtd = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable urxvtd, the urxvt terminal daemon. To use urxvtd, run
+ "urxvtc".
+ '';
+ };
+
+ package = mkOption {
+ default = pkgs.rxvt_unicode-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.services.urxvtd = {
+ description = "urxvt terminal daemon";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+ path = [ pkgs.xsel ];
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/urxvtd -o";
+ Environment = "RXVT_SOCKET=%t/urxvtd-socket";
+ Restart = "on-failure";
+ RestartSec = "5s";
+ };
+ };
+
+ environment.systemPackages = [ cfg.package ];
+ environment.variables.RXVT_SOCKET = "/run/user/$(id -u)/urxvtd-socket";
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix
new file mode 100644
index 00000000000..fdbdf35b0f5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver.windowManager."2bwm";
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.xserver.windowManager."2bwm".enable = mkEnableOption "2bwm";
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.xserver.windowManager.session = singleton
+ { name = "2bwm";
+ start =
+ ''
+ ${pkgs._2bwm}/bin/2bwm &
+ waitPID=$!
+ '';
+ };
+
+ environment.systemPackages = [ pkgs._2bwm ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix b/nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix
new file mode 100644
index 00000000000..ba88a64c702
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.afterstep;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.afterstep.enable = mkEnableOption "afterstep";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "afterstep";
+ start = ''
+ ${pkgs.afterstep}/bin/afterstep &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.afterstep ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix b/nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix
new file mode 100644
index 00000000000..089e9f769f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver.windowManager.awesome;
+ awesome = cfg.package;
+ getLuaPath = lib : dir : "${lib}/${dir}/lua/${pkgs.luaPackages.lua.luaversion}";
+ makeSearchPath = lib.concatMapStrings (path:
+ " --search " + (getLuaPath path "share") +
+ " --search " + (getLuaPath path "lib")
+ );
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.xserver.windowManager.awesome = {
+
+ enable = mkEnableOption "Awesome window manager";
+
+ luaModules = mkOption {
+ default = [];
+ type = types.listOf types.package;
+ description = "List of lua packages available for being used in the Awesome configuration.";
+ example = literalExample "[ luaPackages.oocairo ]";
+ };
+
+ package = mkOption {
+ default = null;
+ type = types.nullOr types.package;
+ description = "Package to use for running the Awesome WM.";
+ apply = pkg: if pkg == null then pkgs.awesome else pkg;
+ };
+
+ noArgb = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Disable client transparency support, which can be greatly detrimental to performance in some setups";
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.xserver.windowManager.session = singleton
+ { name = "awesome";
+ start =
+ ''
+ ${awesome}/bin/awesome ${lib.optionalString cfg.noArgb "--no-argb"} ${makeSearchPath cfg.luaModules} &
+ waitPID=$!
+ '';
+ };
+
+ environment.systemPackages = [ awesome ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix
new file mode 100644
index 00000000000..23cd4f6529a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.bspwm;
+in
+
+{
+ options = {
+ services.xserver.windowManager.bspwm = {
+ enable = mkEnableOption "bspwm";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.bspwm;
+ defaultText = "pkgs.bspwm";
+ example = "pkgs.bspwm-unstable";
+ description = ''
+ bspwm package to use.
+ '';
+ };
+ configFile = mkOption {
+ type = with types; nullOr path;
+ example = "${pkgs.bspwm}/share/doc/bspwm/examples/bspwmrc";
+ default = null;
+ description = ''
+ Path to the bspwm configuration file.
+ If null, $HOME/.config/bspwm/bspwmrc will be used.
+ '';
+ };
+
+ sxhkd = {
+ package = mkOption {
+ type = types.package;
+ default = pkgs.sxhkd;
+ defaultText = "pkgs.sxhkd";
+ example = "pkgs.sxhkd-unstable";
+ description = ''
+ sxhkd package to use.
+ '';
+ };
+ configFile = mkOption {
+ type = with types; nullOr path;
+ example = "${pkgs.bspwm}/share/doc/bspwm/examples/sxhkdrc";
+ default = null;
+ description = ''
+ Path to the sxhkd configuration file.
+ If null, $HOME/.config/sxhkd/sxhkdrc will be used.
+ '';
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "bspwm";
+ start = ''
+ export _JAVA_AWT_WM_NONREPARENTING=1
+ SXHKD_SHELL=/bin/sh ${cfg.sxhkd.package}/bin/sxhkd ${optionalString (cfg.sxhkd.configFile != null) "-c \"${cfg.sxhkd.configFile}\""} &
+ ${cfg.package}/bin/bspwm ${optionalString (cfg.configFile != null) "-c \"${cfg.configFile}\""} &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ cfg.package ];
+ };
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "xserver" "windowManager" "bspwm-unstable" "enable" ]
+ "Use services.xserver.windowManager.bspwm.enable and set services.xserver.windowManager.bspwm.package to pkgs.bspwm-unstable to use the unstable version of bspwm.")
+ (mkRemovedOptionModule [ "services" "xserver" "windowManager" "bspwm" "startThroughSession" ]
+ "bspwm package does not provide bspwm-session anymore.")
+ (mkRemovedOptionModule [ "services" "xserver" "windowManager" "bspwm" "sessionScript" ]
+ "bspwm package does not provide bspwm-session anymore.")
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix
new file mode 100644
index 00000000000..176c1f46127
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.clfswm;
+in
+
+{
+ options = {
+ services.xserver.windowManager.clfswm.enable = mkEnableOption "clfswm";
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "clfswm";
+ start = ''
+ ${pkgs.clfswm}/bin/clfswm &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.clfswm ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/default.nix b/nixpkgs/nixos/modules/services/x11/window-managers/default.nix
new file mode 100644
index 00000000000..2a1f22fa9a4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/default.nix
@@ -0,0 +1,79 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager;
+in
+
+{
+ imports = [
+ ./2bwm.nix
+ ./afterstep.nix
+ ./bspwm.nix
+ ./dwm.nix
+ ./evilwm.nix
+ ./exwm.nix
+ ./fluxbox.nix
+ ./fvwm.nix
+ ./herbstluftwm.nix
+ ./i3.nix
+ ./jwm.nix
+ ./leftwm.nix
+ ./metacity.nix
+ ./mwm.nix
+ ./openbox.nix
+ ./pekwm.nix
+ ./notion.nix
+ ./ratpoison.nix
+ ./sawfish.nix
+ ./stumpwm.nix
+ ./spectrwm.nix
+ ./twm.nix
+ ./windowmaker.nix
+ ./wmii.nix
+ ./xmonad.nix
+ ./qtile.nix
+ ./none.nix ];
+
+ options = {
+
+ services.xserver.windowManager = {
+
+ session = mkOption {
+ internal = true;
+ default = [];
+ example = [{
+ name = "wmii";
+ start = "...";
+ }];
+ description = ''
+ Internal option used to add some common line to window manager
+ scripts before forwarding the value to the
+ <varname>displayManager</varname>.
+ '';
+ apply = map (d: d // {
+ manage = "window";
+ });
+ };
+
+ default = mkOption {
+ type = types.str;
+ default = "none";
+ example = "wmii";
+ description = "Default window manager loaded if none have been chosen.";
+ apply = defaultWM:
+ if any (w: w.name == defaultWM) cfg.session then
+ defaultWM
+ else
+ throw "Default window manager (${defaultWM}) not found.";
+ };
+
+ };
+
+ };
+
+ config = {
+ services.xserver.displayManager.session = cfg.session;
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix
new file mode 100644
index 00000000000..7777913ce1e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver.windowManager.dwm;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.xserver.windowManager.dwm.enable = mkEnableOption "dwm";
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.xserver.windowManager.session = singleton
+ { name = "dwm";
+ start =
+ ''
+ dwm &
+ waitPID=$!
+ '';
+ };
+
+ environment.systemPackages = [ pkgs.dwm ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix
new file mode 100644
index 00000000000..6e19e3572c7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.evilwm;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.evilwm.enable = mkEnableOption "evilwm";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "evilwm";
+ start = ''
+ ${pkgs.evilwm}/bin/evilwm &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.evilwm ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix
new file mode 100644
index 00000000000..dc1d957c170
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.exwm;
+ loadScript = pkgs.writeText "emacs-exwm-load" ''
+ (require 'exwm)
+ ${optionalString cfg.enableDefaultConfig ''
+ (require 'exwm-config)
+ (exwm-config-default)
+ ''}
+ '';
+ packages = epkgs: cfg.extraPackages epkgs ++ [ epkgs.exwm ];
+ exwm-emacs = pkgs.emacsWithPackages packages;
+in
+
+{
+ options = {
+ services.xserver.windowManager.exwm = {
+ enable = mkEnableOption "exwm";
+ enableDefaultConfig = mkOption {
+ default = true;
+ type = lib.types.bool;
+ description = "Enable an uncustomised exwm configuration.";
+ };
+ extraPackages = mkOption {
+ default = self: [];
+ example = literalExample ''
+ epkgs: [
+ epkgs.emms
+ epkgs.magit
+ epkgs.proofgeneral
+ ]
+ '';
+ description = ''
+ Extra packages available to Emacs. The value must be a
+ function which receives the attrset defined in
+ <varname>emacsPackages</varname> as the sole argument.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "exwm";
+ start = ''
+ ${exwm-emacs}/bin/emacs -l ${loadScript}
+ '';
+ };
+ environment.systemPackages = [ exwm-emacs ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix b/nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix
new file mode 100644
index 00000000000..b409335702a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.fluxbox;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.fluxbox.enable = mkEnableOption "fluxbox";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "fluxbox";
+ start = ''
+ ${pkgs.fluxbox}/bin/startfluxbox &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.fluxbox ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/fvwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/fvwm.nix
new file mode 100644
index 00000000000..9a51b9cd660
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/fvwm.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.fvwm;
+ fvwm = pkgs.fvwm.override { gestures = cfg.gestures; };
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.xserver.windowManager.fvwm = {
+ enable = mkEnableOption "Fvwm window manager";
+
+ gestures = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether or not to enable libstroke for gesture support";
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton
+ { name = "fvwm";
+ start =
+ ''
+ ${fvwm}/bin/fvwm &
+ waitPID=$!
+ '';
+ };
+
+ environment.systemPackages = [ fvwm ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix
new file mode 100644
index 00000000000..e3ea61cb9a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.herbstluftwm;
+in
+
+{
+ options = {
+ services.xserver.windowManager.herbstluftwm = {
+ enable = mkEnableOption "herbstluftwm";
+
+ configFile = mkOption {
+ default = null;
+ type = with types; nullOr path;
+ description = ''
+ Path to the herbstluftwm configuration file. If left at the
+ default value, $XDG_CONFIG_HOME/herbstluftwm/autostart will
+ be used.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "herbstluftwm";
+ start =
+ let configFileClause = optionalString
+ (cfg.configFile != null)
+ ''-c "${cfg.configFile}"''
+ ;
+ in "${pkgs.herbstluftwm}/bin/herbstluftwm ${configFileClause}";
+ };
+ environment.systemPackages = [ pkgs.herbstluftwm ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/i3.nix b/nixpkgs/nixos/modules/services/x11/window-managers/i3.nix
new file mode 100644
index 00000000000..0ef55d5f2c0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/i3.nix
@@ -0,0 +1,78 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.i3;
+in
+
+{
+ options.services.xserver.windowManager.i3 = {
+ enable = mkEnableOption "i3 window manager";
+
+ configFile = mkOption {
+ default = null;
+ type = with types; nullOr path;
+ description = ''
+ Path to the i3 configuration file.
+ If left at the default value, $HOME/.i3/config will be used.
+ '';
+ };
+
+ extraSessionCommands = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands executed just before i3 is started.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.i3;
+ defaultText = "pkgs.i3";
+ example = "pkgs.i3-gaps";
+ description = ''
+ i3 package to use.
+ '';
+ };
+
+ extraPackages = mkOption {
+ type = with types; listOf package;
+ default = with pkgs; [ dmenu i3status i3lock ];
+ example = literalExample ''
+ with pkgs; [
+ dmenu
+ i3status
+ i3lock
+ ]
+ '';
+ description = ''
+ Extra packages to be installed system wide.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = [{
+ name = "i3";
+ start = ''
+ ${cfg.extraSessionCommands}
+
+ ${cfg.package}/bin/i3 ${optionalString (cfg.configFile != null)
+ "-c /etc/i3/config"
+ } &
+ waitPID=$!
+ '';
+ }];
+ environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
+ environment.etc."i3/config" = mkIf (cfg.configFile != null) {
+ source = cfg.configFile;
+ };
+ };
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "xserver" "windowManager" "i3-gaps" "enable" ]
+ "Use services.xserver.windowManager.i3.enable and set services.xserver.windowManager.i3.package to pkgs.i3-gaps to use i3-gaps.")
+ ];
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix
new file mode 100644
index 00000000000..f4ae9222df6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.icewm;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.icewm.enable = mkEnableOption "icewm";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton
+ { name = "icewm";
+ start =
+ ''
+ ${pkgs.icewm}/bin/icewm &
+ waitPID=$!
+ '';
+ };
+
+ environment.systemPackages = [ pkgs.icewm ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix
new file mode 100644
index 00000000000..0e8dab2e922
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.jwm;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.jwm.enable = mkEnableOption "jwm";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "jwm";
+ start = ''
+ ${pkgs.jwm}/bin/jwm &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.jwm ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix
new file mode 100644
index 00000000000..3ef40df95df
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.leftwm;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.leftwm.enable = mkEnableOption "leftwm";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "leftwm";
+ start = ''
+ ${pkgs.leftwm}/bin/leftwm &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.leftwm ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix b/nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix
new file mode 100644
index 00000000000..5175fd7f3b1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver.windowManager.metacity;
+ inherit (pkgs) gnome3;
+in
+
+{
+ options = {
+ services.xserver.windowManager.metacity.enable = mkEnableOption "metacity";
+ };
+
+ config = mkIf cfg.enable {
+
+ services.xserver.windowManager.session = singleton
+ { name = "metacity";
+ start = ''
+ ${gnome3.metacity}/bin/metacity &
+ waitPID=$!
+ '';
+ };
+
+ environment.systemPackages = [ gnome3.metacity ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix
new file mode 100644
index 00000000000..31f7b725f74
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.mwm;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.mwm.enable = mkEnableOption "mwm";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "mwm";
+ start = ''
+ ${pkgs.motif}/bin/mwm &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.motif ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/none.nix b/nixpkgs/nixos/modules/services/x11/window-managers/none.nix
new file mode 100644
index 00000000000..84cf1d77077
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/none.nix
@@ -0,0 +1,12 @@
+{
+ services = {
+ xserver = {
+ windowManager = {
+ session = [{
+ name = "none";
+ start = "";
+ }];
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/notion.nix b/nixpkgs/nixos/modules/services/x11/window-managers/notion.nix
new file mode 100644
index 00000000000..4ece0d241c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/notion.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.notion;
+in
+
+{
+ options = {
+ services.xserver.windowManager.notion.enable = mkEnableOption "notion";
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager = {
+ session = [{
+ name = "notion";
+ start = ''
+ ${pkgs.notion}/bin/notion &
+ waitPID=$!
+ '';
+ }];
+ };
+ environment.systemPackages = [ pkgs.notion ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix b/nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix
new file mode 100644
index 00000000000..165772d1aa0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix
@@ -0,0 +1,24 @@
+{lib, pkgs, config, ...}:
+
+with lib;
+let
+ cfg = config.services.xserver.windowManager.openbox;
+in
+
+{
+ options = {
+ services.xserver.windowManager.openbox.enable = mkEnableOption "openbox";
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager = {
+ session = [{
+ name = "openbox";
+ start = "
+ ${pkgs.openbox}/bin/openbox-session
+ ";
+ }];
+ };
+ environment.systemPackages = [ pkgs.openbox ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix b/nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix
new file mode 100644
index 00000000000..bd7e3396864
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.oroborus;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.oroborus.enable = mkEnableOption "oroborus";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "oroborus";
+ start = ''
+ ${pkgs.oroborus}/bin/oroborus &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.oroborus ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix
new file mode 100644
index 00000000000..850335ce7dd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.pekwm;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.pekwm.enable = mkEnableOption "pekwm";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "pekwm";
+ start = ''
+ ${pkgs.pekwm}/bin/pekwm &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.pekwm ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix b/nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix
new file mode 100644
index 00000000000..ad3b65150b0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.qtile;
+in
+
+{
+ options = {
+ services.xserver.windowManager.qtile.enable = mkEnableOption "qtile";
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = [{
+ name = "qtile";
+ start = ''
+ ${pkgs.qtile}/bin/qtile &
+ waitPID=$!
+ '';
+ }];
+
+ environment.systemPackages = [ pkgs.qtile ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix b/nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix
new file mode 100644
index 00000000000..0d58481d457
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.ratpoison;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.ratpoison.enable = mkEnableOption "ratpoison";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "ratpoison";
+ start = ''
+ ${pkgs.ratpoison}/bin/ratpoison &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.ratpoison ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix b/nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix
new file mode 100644
index 00000000000..b988b5e1829
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.sawfish;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.sawfish.enable = mkEnableOption "sawfish";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "sawfish";
+ start = ''
+ ${pkgs.sawfish}/bin/sawfish &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.sawfish ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix
new file mode 100644
index 00000000000..a1dc298d242
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix
@@ -0,0 +1,27 @@
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.spectrwm;
+in
+
+{
+ options = {
+ services.xserver.windowManager.spectrwm.enable = mkEnableOption "spectrwm";
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager = {
+ session = [{
+ name = "spectrwm";
+ start = ''
+ ${pkgs.spectrwm}/bin/spectrwm &
+ waitPID=$!
+ '';
+ }];
+ };
+ environment.systemPackages = [ pkgs.spectrwm ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix
new file mode 100644
index 00000000000..27a17178476
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.stumpwm;
+in
+
+{
+ options = {
+ services.xserver.windowManager.stumpwm.enable = mkEnableOption "stumpwm";
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "stumpwm";
+ start = ''
+ ${pkgs.lispPackages.stumpwm}/bin/stumpwm &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.lispPackages.stumpwm ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/twm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/twm.nix
new file mode 100644
index 00000000000..fc09901aae3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/twm.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xserver.windowManager.twm;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ services.xserver.windowManager.twm.enable = mkEnableOption "twm";
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.xserver.windowManager.session = singleton
+ { name = "twm";
+ start =
+ ''
+ ${pkgs.xorg.twm}/bin/twm &
+ waitPID=$!
+ '';
+ };
+
+ environment.systemPackages = [ pkgs.xorg.twm ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix b/nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix
new file mode 100644
index 00000000000..fb891a39fa4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix
@@ -0,0 +1,22 @@
+{lib, pkgs, config, ...}:
+
+let
+ cfg = config.services.xserver.windowManager.windowlab;
+in
+
+{
+ options = {
+ services.xserver.windowManager.windowlab.enable =
+ lib.mkEnableOption "windowlab";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.xserver.windowManager = {
+ session =
+ [{ name = "windowlab";
+ start = "${pkgs.windowlab}/bin/windowlab";
+ }];
+ };
+ environment.systemPackages = [ pkgs.windowlab ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix b/nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix
new file mode 100644
index 00000000000..b6272375805
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.windowManager.windowmaker;
+in
+{
+ ###### interface
+ options = {
+ services.xserver.windowManager.windowmaker.enable = mkEnableOption "windowmaker";
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton {
+ name = "windowmaker";
+ start = ''
+ ${pkgs.windowmaker}/bin/wmaker &
+ waitPID=$!
+ '';
+ };
+ environment.systemPackages = [ pkgs.windowmaker ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix b/nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix
new file mode 100644
index 00000000000..9b50a99bf23
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.xserver.windowManager.wmii;
+ wmii = pkgs.wmii_hg;
+in
+{
+ options = {
+ services.xserver.windowManager.wmii.enable = mkEnableOption "wmii";
+ };
+
+ config = mkIf cfg.enable {
+ services.xserver.windowManager.session = singleton
+ # stop wmii by
+ # $wmiir xwrite /ctl quit
+ # this will cause wmii exiting with exit code 0
+ # (or "mod+a quit", which is bound to do the same thing in wmiirc
+ # by default)
+ #
+ # why this loop?
+ # wmii crashes once a month here. That doesn't matter that much
+ # wmii can recover very well. However without loop the X session
+ # terminates and then your workspace setup is lost and all
+ # applications running on X will terminate.
+ # Another use case is kill -9 wmii; after rotating screen.
+ # Note: we don't like kill for that purpose. But it works (->
+ # subject "wmii and xrandr" on mailinglist)
+ { name = "wmii";
+ start = ''
+ while :; do
+ ${wmii}/bin/wmii && break
+ done
+ '';
+ };
+
+ environment.systemPackages = [ wmii ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix b/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix
new file mode 100644
index 00000000000..0e131412276
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -0,0 +1,97 @@
+{pkgs, lib, config, ...}:
+
+with lib;
+let
+ inherit (lib) mkOption mkIf optionals literalExample;
+ cfg = config.services.xserver.windowManager.xmonad;
+ xmonad = pkgs.xmonad-with-packages.override {
+ ghcWithPackages = cfg.haskellPackages.ghcWithPackages;
+ packages = self: cfg.extraPackages self ++
+ optionals cfg.enableContribAndExtras
+ [ self.xmonad-contrib self.xmonad-extras ];
+ };
+ xmonadBin = pkgs.writers.writeHaskell "xmonad" {
+ ghc = cfg.haskellPackages.ghc;
+ libraries = [ cfg.haskellPackages.xmonad ] ++
+ cfg.extraPackages cfg.haskellPackages ++
+ optionals cfg.enableContribAndExtras
+ (with cfg.haskellPackages; [ xmonad-contrib xmonad-extras ]);
+ } cfg.config;
+
+in
+{
+ options = {
+ services.xserver.windowManager.xmonad = {
+ enable = mkEnableOption "xmonad";
+ haskellPackages = mkOption {
+ default = pkgs.haskellPackages;
+ defaultText = "pkgs.haskellPackages";
+ example = literalExample "pkgs.haskell.packages.ghc784";
+ description = ''
+ haskellPackages used to build Xmonad and other packages.
+ This can be used to change the GHC version used to build
+ Xmonad and the packages listed in
+ <varname>extraPackages</varname>.
+ '';
+ };
+
+ extraPackages = mkOption {
+ default = self: [];
+ defaultText = "self: []";
+ example = literalExample ''
+ haskellPackages: [
+ haskellPackages.xmonad-contrib
+ haskellPackages.monad-logger
+ ]
+ '';
+ description = ''
+ Extra packages available to ghc when rebuilding Xmonad. The
+ value must be a function which receives the attrset defined
+ in <varname>haskellPackages</varname> as the sole argument.
+ '';
+ };
+
+ enableContribAndExtras = mkOption {
+ default = false;
+ type = lib.types.bool;
+ description = "Enable xmonad-{contrib,extras} in Xmonad.";
+ };
+
+ config = mkOption {
+ default = null;
+ type = with lib.types; nullOr (either path str);
+ description = ''
+ Configuration from which XMonad gets compiled. If no value
+ is specified, the xmonad config from $HOME/.xmonad is taken.
+ If you use xmonad --recompile, $HOME/.xmonad will be taken as
+ the configuration, but on the next restart of display-manager
+ this config will be reapplied.
+ '';
+ example = ''
+ import XMonad
+
+ main = launch defaultConfig
+ { modMask = mod4Mask -- Use Super instead of Alt
+ , terminal = "urxvt"
+ }
+ '';
+ };
+ };
+ };
+ config = mkIf cfg.enable {
+ services.xserver.windowManager = {
+ session = [{
+ name = "xmonad";
+ start = if (cfg.config != null) then ''
+ ${xmonadBin}
+ waitPID=$!
+ '' else ''
+ ${xmonad}/bin/xmonad &
+ waitPID=$!
+ '';
+ }];
+ };
+
+ environment.systemPackages = [ xmonad ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/xautolock.nix b/nixpkgs/nixos/modules/services/x11/xautolock.nix
new file mode 100644
index 00000000000..3e03131ca11
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/xautolock.nix
@@ -0,0 +1,140 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.xserver.xautolock;
+in
+ {
+ options = {
+ services.xserver.xautolock = {
+ enable = mkEnableOption "xautolock";
+ enableNotifier = mkEnableOption "xautolock.notify" // {
+ description = ''
+ Whether to enable the notifier feature of xautolock.
+ This publishes a notification before the autolock.
+ '';
+ };
+
+ time = mkOption {
+ default = 15;
+ type = types.int;
+
+ description = ''
+ Idle time (in minutes) to wait until xautolock locks the computer.
+ '';
+ };
+
+ locker = mkOption {
+ default = "${pkgs.xlockmore}/bin/xlock"; # default according to `man xautolock`
+ example = "${pkgs.i3lock}/bin/i3lock -i /path/to/img";
+ type = types.str;
+
+ description = ''
+ The script to use when automatically locking the computer.
+ '';
+ };
+
+ nowlocker = mkOption {
+ default = null;
+ example = "${pkgs.i3lock}/bin/i3lock -i /path/to/img";
+ type = types.nullOr types.str;
+
+ description = ''
+ The script to use when manually locking the computer with <command>xautolock -locknow</command>.
+ '';
+ };
+
+ notify = mkOption {
+ default = 10;
+ type = types.int;
+
+ description = ''
+ Time (in seconds) before the actual lock when the notification about the pending lock should be published.
+ '';
+ };
+
+ notifier = mkOption {
+ default = null;
+ example = "${pkgs.libnotify}/bin/notify-send \"Locking in 10 seconds\"";
+ type = types.nullOr types.str;
+
+ description = ''
+ Notification script to be used to warn about the pending autolock.
+ '';
+ };
+
+ killer = mkOption {
+ default = null; # default according to `man xautolock` is none
+ example = "${pkgs.systemd}/bin/systemctl suspend";
+ type = types.nullOr types.str;
+
+ description = ''
+ The script to use when nothing has happend for as long as <option>killtime</option>
+ '';
+ };
+
+ killtime = mkOption {
+ default = 20; # default according to `man xautolock`
+ type = types.int;
+
+ description = ''
+ Minutes xautolock waits until it executes the script specified in <option>killer</option>
+ (Has to be at least 10 minutes)
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "-detectsleep" ];
+ description = ''
+ Additional command-line arguments to pass to
+ <command>xautolock</command>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [ xautolock ];
+ systemd.user.services.xautolock = {
+ description = "xautolock service";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+ serviceConfig = with lib; {
+ ExecStart = strings.concatStringsSep " " ([
+ "${pkgs.xautolock}/bin/xautolock"
+ "-noclose"
+ "-time ${toString cfg.time}"
+ "-locker '${cfg.locker}'"
+ ] ++ optionals cfg.enableNotifier [
+ "-notify ${toString cfg.notify}"
+ "-notifier '${cfg.notifier}'"
+ ] ++ optionals (cfg.nowlocker != null) [
+ "-nowlocker '${cfg.nowlocker}'"
+ ] ++ optionals (cfg.killer != null) [
+ "-killer '${cfg.killer}'"
+ "-killtime ${toString cfg.killtime}"
+ ] ++ cfg.extraOptions);
+ Restart = "always";
+ };
+ };
+ assertions = [
+ {
+ assertion = cfg.enableNotifier -> cfg.notifier != null;
+ message = "When enabling the notifier for xautolock, you also need to specify the notify script";
+ }
+ {
+ assertion = cfg.killer != null -> cfg.killtime >= 10;
+ message = "killtime has to be at least 10 minutes according to `man xautolock`";
+ }
+ ] ++ (lib.forEach [ "locker" "notifier" "nowlocker" "killer" ]
+ (option:
+ {
+ assertion = cfg.${option} != null -> builtins.substring 0 1 cfg.${option} == "/";
+ message = "Please specify a canonical path for `services.xserver.xautolock.${option}`";
+ })
+ );
+ };
+ }
diff --git a/nixpkgs/nixos/modules/services/x11/xbanish.nix b/nixpkgs/nixos/modules/services/x11/xbanish.nix
new file mode 100644
index 00000000000..b95fac68f16
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/xbanish.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.xbanish;
+
+in {
+ options.services.xbanish = {
+
+ enable = mkEnableOption "xbanish";
+
+ arguments = mkOption {
+ description = "Arguments to pass to xbanish command";
+ default = "";
+ example = "-d -i shift";
+ type = types.str;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.xbanish = {
+ description = "xbanish hides the mouse pointer";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+ serviceConfig.ExecStart = ''
+ ${pkgs.xbanish}/bin/xbanish ${cfg.arguments}
+ '';
+ serviceConfig.Restart = "always";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/xfs.conf b/nixpkgs/nixos/modules/services/x11/xfs.conf
new file mode 100644
index 00000000000..13dcf803db2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/xfs.conf
@@ -0,0 +1,15 @@
+# font server configuration file
+# $Xorg: config.cpp,v 1.3 2000/08/17 19:54:19 cpqbld Exp $
+
+clone-self = on
+use-syslog = off
+error-file = /var/log/xfs.log
+# in decipoints
+default-point-size = 120
+default-resolutions = 75,75,100,100
+
+# font cache control, specified in KB
+cache-hi-mark = 2048
+cache-low-mark = 1433
+cache-balance = 70
+catalogue = /run/current-system/sw/share/X11-fonts/
diff --git a/nixpkgs/nixos/modules/services/x11/xfs.nix b/nixpkgs/nixos/modules/services/x11/xfs.nix
new file mode 100644
index 00000000000..ea7cfa1aa43
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/xfs.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ configFile = ./xfs.conf;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.xfs = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the X Font Server.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.xfs.enable {
+ assertions = singleton
+ { assertion = config.fonts.enableFontDir;
+ message = "Please enable fonts.enableFontDir to use the X Font Server.";
+ };
+
+ systemd.services.xfs = {
+ description = "X Font Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.xorg.xfs ];
+ script = "xfs -config ${configFile}";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/xserver.nix b/nixpkgs/nixos/modules/services/x11/xserver.nix
new file mode 100644
index 00000000000..a8406544a72
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/xserver.nix
@@ -0,0 +1,822 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ # Abbreviations.
+ cfg = config.services.xserver;
+ xorg = pkgs.xorg;
+
+
+ # Map video driver names to driver packages. FIXME: move into card-specific modules.
+ knownVideoDrivers = {
+ # Alias so people can keep using "virtualbox" instead of "vboxvideo".
+ virtualbox = { modules = [ xorg.xf86videovboxvideo ]; driverName = "vboxvideo"; };
+
+ # Alias so that "radeon" uses the xf86-video-ati driver.
+ radeon = { modules = [ xorg.xf86videoati ]; driverName = "ati"; };
+
+ # modesetting does not have a xf86videomodesetting package as it is included in xorgserver
+ modesetting = {};
+ };
+
+ fontsForXServer =
+ config.fonts.fonts ++
+ # We don't want these fonts in fonts.conf, because then modern,
+ # fontconfig-based applications will get horrible bitmapped
+ # Helvetica fonts. It's better to get a substitution (like Nimbus
+ # Sans) than that horror. But we do need the Adobe fonts for some
+ # old non-fontconfig applications. (Possibly this could be done
+ # better using a fontconfig rule.)
+ [ pkgs.xorg.fontadobe100dpi
+ pkgs.xorg.fontadobe75dpi
+ ];
+
+ xrandrOptions = {
+ output = mkOption {
+ type = types.str;
+ example = "DVI-0";
+ description = ''
+ The output name of the monitor, as shown by <citerefentry>
+ <refentrytitle>xrandr</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry> invoked without arguments.
+ '';
+ };
+
+ primary = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether this head is treated as the primary monitor,
+ '';
+ };
+
+ monitorConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ DisplaySize 408 306
+ Option "DPMS" "false"
+ '';
+ description = ''
+ Extra lines to append to the <literal>Monitor</literal> section
+ verbatim. Available options are documented in the MONITOR section in
+ <citerefentry><refentrytitle>xorg.conf</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry>.
+ '';
+ };
+ };
+
+ # Just enumerate all heads without discarding XRandR output information.
+ xrandrHeads = let
+ mkHead = num: config: {
+ name = "multihead${toString num}";
+ inherit config;
+ };
+ in imap1 mkHead cfg.xrandrHeads;
+
+ xrandrDeviceSection = let
+ monitors = forEach xrandrHeads (h: ''
+ Option "monitor-${h.config.output}" "${h.name}"
+ '');
+ # First option is indented through the space in the config but any
+ # subsequent options aren't so we need to apply indentation to
+ # them here
+ monitorsIndented = if length monitors > 1
+ then singleton (head monitors) ++ map (m: " " + m) (tail monitors)
+ else monitors;
+ in concatStrings monitorsIndented;
+
+ # Here we chain every monitor from the left to right, so we have:
+ # m4 right of m3 right of m2 right of m1 .----.----.----.----.
+ # Which will end up in reverse ----------> | m1 | m2 | m3 | m4 |
+ # `----^----^----^----'
+ xrandrMonitorSections = let
+ mkMonitor = previous: current: singleton {
+ inherit (current) name;
+ value = ''
+ Section "Monitor"
+ Identifier "${current.name}"
+ ${optionalString (current.config.primary) ''
+ Option "Primary" "true"
+ ''}
+ ${optionalString (previous != []) ''
+ Option "RightOf" "${(head previous).name}"
+ ''}
+ ${current.config.monitorConfig}
+ EndSection
+ '';
+ } ++ previous;
+ monitors = reverseList (foldl mkMonitor [] xrandrHeads);
+ in concatMapStrings (getAttr "value") monitors;
+
+ configFile = pkgs.runCommand "xserver.conf"
+ { xfs = optionalString (cfg.useXFS != false)
+ ''FontPath "${toString cfg.useXFS}"'';
+ inherit (cfg) config;
+ preferLocalBuild = true;
+ }
+ ''
+ echo 'Section "Files"' >> $out
+ echo $xfs >> $out
+
+ for i in ${toString fontsForXServer}; do
+ if test "''${i:0:''${#NIX_STORE}}" == "$NIX_STORE"; then
+ for j in $(find $i -name fonts.dir); do
+ echo " FontPath \"$(dirname $j)\"" >> $out
+ done
+ fi
+ done
+
+ for i in $(find ${toString cfg.modules} -type d); do
+ if test $(echo $i/*.so* | wc -w) -ne 0; then
+ echo " ModulePath \"$i\"" >> $out
+ fi
+ done
+
+ echo 'EndSection' >> $out
+
+ echo "$config" >> $out
+ ''; # */
+
+in
+
+{
+
+ imports =
+ [ ./display-managers/default.nix
+ ./window-managers/default.nix
+ ./desktop-managers/default.nix
+ ];
+
+
+ ###### interface
+
+ options = {
+
+ services.xserver = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the X server.
+ '';
+ };
+
+ autorun = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to start the X server automatically.
+ '';
+ };
+
+ exportConfiguration = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to symlink the X server configuration under
+ <filename>/etc/X11/xorg.conf</filename>.
+ '';
+ };
+
+ enableTCP = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to allow the X server to accept TCP connections.
+ '';
+ };
+
+ autoRepeatDelay = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Sets the autorepeat delay (length of time in milliseconds that a key must be depressed before autorepeat starts).
+ '';
+ };
+
+ autoRepeatInterval = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Sets the autorepeat interval (length of time in milliseconds that should elapse between autorepeat-generated keystrokes).
+ '';
+ };
+
+ inputClassSections = mkOption {
+ type = types.listOf types.lines;
+ default = [];
+ example = literalExample ''
+ [ '''
+ Identifier "Trackpoint Wheel Emulation"
+ MatchProduct "ThinkPad USB Keyboard with TrackPoint"
+ Option "EmulateWheel" "true"
+ Option "EmulateWheelButton" "2"
+ Option "Emulate3Buttons" "false"
+ '''
+ ]
+ '';
+ description = "Content of additional InputClass sections of the X server configuration file.";
+ };
+
+ modules = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ example = literalExample "[ pkgs.xf86_input_wacom ]";
+ description = "Packages to be added to the module search path of the X server.";
+ };
+
+ resolutions = mkOption {
+ type = types.listOf types.attrs;
+ default = [];
+ example = [ { x = 1600; y = 1200; } { x = 1024; y = 786; } ];
+ description = ''
+ The screen resolutions for the X server. The first element
+ is the default resolution. If this list is empty, the X
+ server will automatically configure the resolution.
+ '';
+ };
+
+ videoDrivers = mkOption {
+ type = types.listOf types.str;
+ # !!! We'd like "nv" here, but it segfaults the X server.
+ default = [ "radeon" "cirrus" "vesa" "vmware" "modesetting" ];
+ example = [
+ "ati_unfree" "amdgpu" "amdgpu-pro"
+ "nv" "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304"
+ ];
+ # TODO(@oxij): think how to easily add the rest, like those nvidia things
+ relatedPackages = concatLists
+ (mapAttrsToList (n: v:
+ optional (hasPrefix "xf86video" n) {
+ path = [ "xorg" n ];
+ title = removePrefix "xf86video" n;
+ }) pkgs.xorg);
+ description = ''
+ The names of the video drivers the configuration
+ supports. They will be tried in order until one that
+ supports your card is found.
+ Don't combine those with "incompatible" OpenGL implementations,
+ e.g. free ones (mesa-based) with proprietary ones.
+
+ For unfree "nvidia*", the supported GPU lists are on
+ https://www.nvidia.com/object/unix.html
+ '';
+ };
+
+ videoDriver = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "i810";
+ description = ''
+ The name of the video driver for your graphics card. This
+ option is obsolete; please set the
+ <option>services.xserver.videoDrivers</option> instead.
+ '';
+ };
+
+ drivers = mkOption {
+ type = types.listOf types.attrs;
+ internal = true;
+ description = ''
+ A list of attribute sets specifying drivers to be loaded by
+ the X11 server.
+ '';
+ };
+
+ dpi = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "DPI resolution to use for X server.";
+ };
+
+ startDbusSession = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to start a new DBus session when you log in with dbus-launch.
+ '';
+ };
+
+ updateDbusEnvironment = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to update the DBus activation environment after launching the
+ desktop manager.
+ '';
+ };
+
+ layout = mkOption {
+ type = types.str;
+ default = "us";
+ description = ''
+ Keyboard layout, or multiple keyboard layouts separated by commas.
+ '';
+ };
+
+ xkbModel = mkOption {
+ type = types.str;
+ default = "pc104";
+ example = "presario";
+ description = ''
+ Keyboard model.
+ '';
+ };
+
+ xkbOptions = mkOption {
+ type = types.str;
+ default = "terminate:ctrl_alt_bksp";
+ example = "grp:caps_toggle, grp_led:scroll";
+ description = ''
+ X keyboard options; layout switching goes here.
+ '';
+ };
+
+ xkbVariant = mkOption {
+ type = types.str;
+ default = "";
+ example = "colemak";
+ description = ''
+ X keyboard variant.
+ '';
+ };
+
+ xkbDir = mkOption {
+ type = types.path;
+ default = "${pkgs.xkeyboard_config}/etc/X11/xkb";
+ description = ''
+ Path used for -xkbdir xserver parameter.
+ '';
+ };
+
+ config = mkOption {
+ type = types.lines;
+ description = ''
+ The contents of the configuration file of the X server
+ (<filename>xorg.conf</filename>).
+ '';
+ };
+
+ deviceSection = mkOption {
+ type = types.lines;
+ default = "";
+ example = "VideoRAM 131072";
+ description = "Contents of the first Device section of the X server configuration file.";
+ };
+
+ screenSection = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ Option "RandRRotation" "on"
+ '';
+ description = "Contents of the first Screen section of the X server configuration file.";
+ };
+
+ monitorSection = mkOption {
+ type = types.lines;
+ default = "";
+ example = "HorizSync 28-49";
+ description = "Contents of the first Monitor section of the X server configuration file.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional contents (sections) included in the X server configuration file";
+ };
+
+ xrandrHeads = mkOption {
+ default = [];
+ example = [
+ "HDMI-0"
+ { output = "DVI-0"; primary = true; }
+ { output = "DVI-1"; monitorConfig = "Option \"Rotate\" \"left\""; }
+ ];
+ type = with types; listOf (coercedTo str (output: {
+ inherit output;
+ }) (submodule { options = xrandrOptions; }));
+ # Set primary to true for the first head if no other has been set
+ # primary already.
+ apply = heads: let
+ hasPrimary = any (x: x.primary) heads;
+ firstPrimary = head heads // { primary = true; };
+ newHeads = singleton firstPrimary ++ tail heads;
+ in if heads != [] && !hasPrimary then newHeads else heads;
+ description = ''
+ Multiple monitor configuration, just specify a list of XRandR
+ outputs. The individual elements should be either simple strings or
+ an attribute set of output options.
+
+ If the element is a string, it is denoting the physical output for a
+ monitor, if it's an attribute set, you must at least provide the
+ <option>output</option> option.
+
+ The monitors will be mapped from left to right in the order of the
+ list.
+
+ By default, the first monitor will be set as the primary monitor if
+ none of the elements contain an option that has set
+ <option>primary</option> to <literal>true</literal>.
+
+ <note><para>Only one monitor is allowed to be primary.</para></note>
+
+ Be careful using this option with multiple graphic adapters or with
+ drivers that have poor support for XRandR, unexpected things might
+ happen with those.
+ '';
+ };
+
+ serverFlagsSection = mkOption {
+ default = "";
+ example =
+ ''
+ Option "BlankTime" "0"
+ Option "StandbyTime" "0"
+ Option "SuspendTime" "0"
+ Option "OffTime" "0"
+ '';
+ description = "Contents of the ServerFlags section of the X server configuration file.";
+ };
+
+ moduleSection = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ SubSection "extmod"
+ EndSubsection
+ '';
+ description = "Contents of the Module section of the X server configuration file.";
+ };
+
+ serverLayoutSection = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ Option "AIGLX" "true"
+ '';
+ description = "Contents of the ServerLayout section of the X server configuration file.";
+ };
+
+ extraDisplaySettings = mkOption {
+ type = types.lines;
+ default = "";
+ example = "Virtual 2048 2048";
+ description = "Lines to be added to every Display subsection of the Screen section.";
+ };
+
+ defaultDepth = mkOption {
+ type = types.int;
+ default = 0;
+ example = 8;
+ description = "Default colour depth.";
+ };
+
+ useXFS = mkOption {
+ # FIXME: what's the type of this option?
+ default = false;
+ example = "unix/:7100";
+ description = "Determines how to connect to the X Font Server.";
+ };
+
+ tty = mkOption {
+ type = types.nullOr types.int;
+ default = 7;
+ description = "Virtual console for the X server.";
+ };
+
+ display = mkOption {
+ type = types.nullOr types.int;
+ default = 0;
+ description = "Display number for the X server.";
+ };
+
+ virtualScreen = mkOption {
+ type = types.nullOr types.attrs;
+ default = null;
+ example = { x = 2048; y = 2048; };
+ description = ''
+ Virtual screen size for Xrandr.
+ '';
+ };
+
+ verbose = mkOption {
+ type = types.nullOr types.int;
+ default = 3;
+ example = 7;
+ description = ''
+ Controls verbosity of X logging.
+ '';
+ };
+
+ useGlamor = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use the Glamor module for 2D acceleration,
+ if possible.
+ '';
+ };
+
+ enableCtrlAltBackspace = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the DontZap option, which binds Ctrl+Alt+Backspace
+ to forcefully kill X. This can lead to data loss and is disabled
+ by default.
+ '';
+ };
+
+ terminateOnReset = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to terminate X upon server reset.
+ '';
+ };
+ };
+
+ };
+
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.xserver.displayManager.lightdm.enable =
+ let dmconf = cfg.displayManager;
+ default = !( dmconf.auto.enable
+ || dmconf.gdm.enable
+ || dmconf.sddm.enable
+ || dmconf.slim.enable
+ || dmconf.xpra.enable );
+ in mkIf (default) true;
+
+ hardware.opengl.enable = mkDefault true;
+
+ services.xserver.videoDrivers = mkIf (cfg.videoDriver != null) [ cfg.videoDriver ];
+
+ # FIXME: somehow check for unknown driver names.
+ services.xserver.drivers = flip concatMap cfg.videoDrivers (name:
+ let driver =
+ attrByPath [name]
+ (if xorg ? ${"xf86video" + name}
+ then { modules = [xorg.${"xf86video" + name}]; }
+ else null)
+ knownVideoDrivers;
+ in optional (driver != null) ({ inherit name; modules = []; driverName = name; } // driver));
+
+ assertions = [
+ { assertion = config.security.polkit.enable;
+ message = "X11 requires Polkit to be enabled (‘security.polkit.enable = true’).";
+ }
+ (let primaryHeads = filter (x: x.primary) cfg.xrandrHeads; in {
+ assertion = length primaryHeads < 2;
+ message = "Only one head is allowed to be primary in "
+ + "‘services.xserver.xrandrHeads’, but there are "
+ + "${toString (length primaryHeads)} heads set to primary: "
+ + concatMapStringsSep ", " (x: x.output) primaryHeads;
+ })
+ ];
+
+ environment.etc =
+ (optionals cfg.exportConfiguration
+ [ { source = "${configFile}";
+ target = "X11/xorg.conf";
+ }
+ # -xkbdir command line option does not seems to be passed to xkbcomp.
+ { source = "${cfg.xkbDir}";
+ target = "X11/xkb";
+ }
+ ])
+ # localectl looks into 00-keyboard.conf
+ ++ [
+ {
+ text = ''
+ Section "InputClass"
+ Identifier "Keyboard catchall"
+ MatchIsKeyboard "on"
+ Option "XkbModel" "${cfg.xkbModel}"
+ Option "XkbLayout" "${cfg.layout}"
+ Option "XkbOptions" "${cfg.xkbOptions}"
+ Option "XkbVariant" "${cfg.xkbVariant}"
+ EndSection
+ '';
+ target = "X11/xorg.conf.d/00-keyboard.conf";
+ }
+ ]
+ # Needed since 1.18; see https://bugs.freedesktop.org/show_bug.cgi?id=89023#c5
+ ++ (let cfgPath = "/X11/xorg.conf.d/10-evdev.conf"; in
+ [{
+ source = xorg.xf86inputevdev.out + "/share" + cfgPath;
+ target = cfgPath;
+ }]
+ );
+
+ environment.systemPackages =
+ [ xorg.xorgserver.out
+ xorg.xrandr
+ xorg.xrdb
+ xorg.setxkbmap
+ xorg.iceauth # required for KDE applications (it's called by dcopserver)
+ xorg.xlsclients
+ xorg.xset
+ xorg.xsetroot
+ xorg.xinput
+ xorg.xprop
+ xorg.xauth
+ pkgs.xterm
+ pkgs.xdg_utils
+ xorg.xf86inputevdev.out # get evdev.4 man page
+ ]
+ ++ optional (elem "virtualbox" cfg.videoDrivers) xorg.xrefresh;
+
+ environment.pathsToLink = [ "/share/X11" ];
+
+ xdg = {
+ autostart.enable = true;
+ menus.enable = true;
+ mime.enable = true;
+ icons.enable = true;
+ };
+
+ # The default max inotify watches is 8192.
+ # Nowadays most apps require a good number of inotify watches,
+ # the value below is used by default on several other distros.
+ boot.kernel.sysctl."fs.inotify.max_user_watches" = mkDefault 524288;
+
+ systemd.defaultUnit = mkIf cfg.autorun "graphical.target";
+
+ systemd.services.display-manager =
+ { description = "X11 Server";
+
+ after = [ "systemd-udev-settle.service" "acpid.service" "systemd-logind.service" ];
+ wants = [ "systemd-udev-settle.service" ];
+
+ restartIfChanged = false;
+
+ environment =
+ optionalAttrs config.hardware.opengl.setLdLibraryPath
+ { LD_LIBRARY_PATH = pkgs.addOpenGLRunpath.driverLink; }
+ // cfg.displayManager.job.environment;
+
+ preStart =
+ ''
+ ${cfg.displayManager.job.preStart}
+
+ rm -f /tmp/.X0-lock
+ '';
+
+ script = "${cfg.displayManager.job.execCmd}";
+
+ serviceConfig = {
+ Restart = "always";
+ RestartSec = "200ms";
+ SyslogIdentifier = "display-manager";
+ # Stop restarting if the display manager stops (crashes) 2 times
+ # in one minute. Starting X typically takes 3-4s.
+ StartLimitInterval = "30s";
+ StartLimitBurst = "3";
+ };
+ };
+
+ services.xserver.displayManager.xserverArgs =
+ [ "-config ${configFile}"
+ "-xkbdir" "${cfg.xkbDir}"
+ # Log at the default verbosity level to stderr rather than /var/log/X.*.log.
+ "-logfile" "/dev/null"
+ ] ++ optional (cfg.display != null) ":${toString cfg.display}"
+ ++ optional (cfg.tty != null) "vt${toString cfg.tty}"
+ ++ optional (cfg.dpi != null) "-dpi ${toString cfg.dpi}"
+ ++ optional (cfg.verbose != null) "-verbose ${toString cfg.verbose}"
+ ++ optional (!cfg.enableTCP) "-nolisten tcp"
+ ++ optional (cfg.autoRepeatDelay != null) "-ardelay ${toString cfg.autoRepeatDelay}"
+ ++ optional (cfg.autoRepeatInterval != null) "-arinterval ${toString cfg.autoRepeatInterval}"
+ ++ optional cfg.terminateOnReset "-terminate";
+
+ services.xserver.modules =
+ concatLists (catAttrs "modules" cfg.drivers) ++
+ [ xorg.xorgserver.out
+ xorg.xf86inputevdev.out
+ ];
+
+ system.extraDependencies = singleton (pkgs.runCommand "xkb-validated" {
+ inherit (cfg) xkbModel layout xkbVariant xkbOptions;
+ nativeBuildInputs = [ pkgs.xkbvalidate ];
+ preferLocalBuild = true;
+ } ''
+ xkbvalidate "$xkbModel" "$layout" "$xkbVariant" "$xkbOptions"
+ touch "$out"
+ '');
+
+ services.xserver.config =
+ ''
+ Section "ServerFlags"
+ Option "AllowMouseOpenFail" "on"
+ Option "DontZap" "${if cfg.enableCtrlAltBackspace then "off" else "on"}"
+ ${cfg.serverFlagsSection}
+ EndSection
+
+ Section "Module"
+ ${cfg.moduleSection}
+ EndSection
+
+ Section "Monitor"
+ Identifier "Monitor[0]"
+ ${cfg.monitorSection}
+ EndSection
+
+ # Additional "InputClass" sections
+ ${flip concatMapStrings cfg.inputClassSections (inputClassSection: ''
+ Section "InputClass"
+ ${inputClassSection}
+ EndSection
+ '')}
+
+
+ Section "ServerLayout"
+ Identifier "Layout[all]"
+ ${cfg.serverLayoutSection}
+ # Reference the Screen sections for each driver. This will
+ # cause the X server to try each in turn.
+ ${flip concatMapStrings cfg.drivers (d: ''
+ Screen "Screen-${d.name}[0]"
+ '')}
+ EndSection
+
+ ${if cfg.useGlamor then ''
+ Section "Module"
+ Load "dri2"
+ Load "glamoregl"
+ EndSection
+ '' else ""}
+
+ # For each supported driver, add a "Device" and "Screen"
+ # section.
+ ${flip concatMapStrings cfg.drivers (driver: ''
+
+ Section "Device"
+ Identifier "Device-${driver.name}[0]"
+ Driver "${driver.driverName or driver.name}"
+ ${if cfg.useGlamor then ''Option "AccelMethod" "glamor"'' else ""}
+ ${cfg.deviceSection}
+ ${driver.deviceSection or ""}
+ ${xrandrDeviceSection}
+ EndSection
+
+ Section "Screen"
+ Identifier "Screen-${driver.name}[0]"
+ Device "Device-${driver.name}[0]"
+ ${optionalString (cfg.monitorSection != "") ''
+ Monitor "Monitor[0]"
+ ''}
+
+ ${cfg.screenSection}
+ ${driver.screenSection or ""}
+
+ ${optionalString (cfg.defaultDepth != 0) ''
+ DefaultDepth ${toString cfg.defaultDepth}
+ ''}
+
+ ${optionalString
+ (driver.name != "virtualbox" &&
+ (cfg.resolutions != [] ||
+ cfg.extraDisplaySettings != "" ||
+ cfg.virtualScreen != null))
+ (let
+ f = depth:
+ ''
+ SubSection "Display"
+ Depth ${toString depth}
+ ${optionalString (cfg.resolutions != [])
+ "Modes ${concatMapStrings (res: ''"${toString res.x}x${toString res.y}"'') cfg.resolutions}"}
+ ${cfg.extraDisplaySettings}
+ ${optionalString (cfg.virtualScreen != null)
+ "Virtual ${toString cfg.virtualScreen.x} ${toString cfg.virtualScreen.y}"}
+ EndSubSection
+ '';
+ in concatMapStrings f [8 16 24]
+ )}
+
+ EndSection
+ '')}
+
+ ${xrandrMonitorSections}
+
+ ${cfg.extraConfig}
+ '';
+
+ fonts.enableDefaultFonts = mkDefault true;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/activation/activation-script.nix b/nixpkgs/nixos/modules/system/activation/activation-script.nix
new file mode 100644
index 00000000000..ddfd1af4a31
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/activation/activation-script.nix
@@ -0,0 +1,229 @@
+# generate the script used to activate the configuration.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ addAttributeName = mapAttrs (a: v: v // {
+ text = ''
+ #### Activation script snippet ${a}:
+ _localstatus=0
+ ${v.text}
+
+ if (( _localstatus > 0 )); then
+ printf "Activation script snippet '%s' failed (%s)\n" "${a}" "$_localstatus"
+ fi
+ '';
+ });
+
+ path = with pkgs; map getBin
+ [ coreutils
+ gnugrep
+ findutils
+ getent
+ stdenv.cc.libc # nscd in update-users-groups.pl
+ shadow
+ nettools # needed for hostname
+ utillinux # needed for mount and mountpoint
+ ];
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ system.activationScripts = mkOption {
+ default = {};
+
+ example = literalExample ''
+ { stdio = {
+ text = '''
+ # Needed by some programs.
+ ln -sfn /proc/self/fd /dev/fd
+ ln -sfn /proc/self/fd/0 /dev/stdin
+ ln -sfn /proc/self/fd/1 /dev/stdout
+ ln -sfn /proc/self/fd/2 /dev/stderr
+ ''';
+ deps = [];
+ };
+ }
+ '';
+
+ description = ''
+ A set of shell script fragments that are executed when a NixOS
+ system configuration is activated. Examples are updating
+ /etc, creating accounts, and so on. Since these are executed
+ every time you boot the system or run
+ <command>nixos-rebuild</command>, it's important that they are
+ idempotent and fast.
+ '';
+
+ type = types.attrsOf types.unspecified; # FIXME
+
+ apply = set: {
+ script =
+ ''
+ #! ${pkgs.runtimeShell}
+
+ systemConfig=@out@
+
+ export PATH=/empty
+ for i in ${toString path}; do
+ PATH=$PATH:$i/bin:$i/sbin
+ done
+
+ _status=0
+ trap "_status=1 _localstatus=\$?" ERR
+
+ # Ensure a consistent umask.
+ umask 0022
+
+ ${
+ let
+ set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set;
+ withHeadlines = addAttributeName set';
+ in textClosureMap id (withHeadlines) (attrNames withHeadlines)
+ }
+
+ # Make this configuration the current configuration.
+ # The readlink is there to ensure that when $systemConfig = /system
+ # (which is a symlink to the store), /run/current-system is still
+ # used as a garbage collection root.
+ ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
+
+ # Prevent the current configuration from being garbage-collected.
+ ln -sfn /run/current-system /nix/var/nix/gcroots/current-system
+
+ 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
+ '';
+ };
+
+ };
+
+ environment.usrbinenv = mkOption {
+ default = "${pkgs.coreutils}/bin/env";
+ example = literalExample ''
+ "''${pkgs.busybox}/bin/env"
+ '';
+ type = types.nullOr types.path;
+ visible = false;
+ description = ''
+ The env(1) executable that is linked system-wide to
+ <literal>/usr/bin/env</literal>.
+ '';
+ };
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ system.activationScripts.stdio = ""; # obsolete
+
+ system.activationScripts.var =
+ ''
+ # Various log/runtime directories.
+
+ mkdir -m 1777 -p /var/tmp
+
+ # Empty, immutable home directory of many system accounts.
+ mkdir -p /var/empty
+ # Make sure it's really empty
+ ${pkgs.e2fsprogs}/bin/chattr -f -i /var/empty || true
+ find /var/empty -mindepth 1 -delete
+ chmod 0555 /var/empty
+ chown root:root /var/empty
+ ${pkgs.e2fsprogs}/bin/chattr -f +i /var/empty || true
+ '';
+
+ system.activationScripts.usrbinenv = if config.environment.usrbinenv != null
+ then ''
+ mkdir -m 0755 -p /usr/bin
+ ln -sfn ${config.environment.usrbinenv} /usr/bin/.env.tmp
+ mv /usr/bin/.env.tmp /usr/bin/env # atomically replace /usr/bin/env
+ ''
+ else ''
+ rm -f /usr/bin/env
+ rmdir --ignore-fail-on-non-empty /usr/bin /usr
+ '';
+
+ system.activationScripts.specialfs =
+ ''
+ specialMount() {
+ local device="$1"
+ local mountPoint="$2"
+ local options="$3"
+ local fsType="$4"
+
+ if mountpoint -q "$mountPoint"; then
+ local options="remount,$options"
+ else
+ mkdir -m 0755 -p "$mountPoint"
+ fi
+ mount -t "$fsType" -o "$options" "$device" "$mountPoint"
+ }
+ 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/nixpkgs/nixos/modules/system/activation/no-clone.nix b/nixpkgs/nixos/modules/system/activation/no-clone.nix
new file mode 100644
index 00000000000..7f458443526
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/activation/no-clone.nix
@@ -0,0 +1,9 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ boot.loader.grub.device = mkOverride 0 "nodev";
+ nesting.children = mkOverride 0 [];
+ nesting.clone = mkOverride 0 [];
+}
diff --git a/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl b/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl
new file mode 100644
index 00000000000..641cf9faadc
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl
@@ -0,0 +1,489 @@
+#! @perl@
+
+use strict;
+use warnings;
+use File::Basename;
+use File::Slurp;
+use Net::DBus;
+use Sys::Syslog qw(:standard :macros);
+use Cwd 'abs_path';
+
+my $out = "@out@";
+
+# FIXME: maybe we should use /proc/1/exe to get the current systemd.
+my $curSystemd = abs_path("/run/current-system/sw/bin");
+
+# To be robust against interruption, record what units need to be started etc.
+my $startListFile = "/run/systemd/start-list";
+my $restartListFile = "/run/systemd/restart-list";
+my $reloadListFile = "/run/systemd/reload-list";
+
+my $action = shift @ARGV;
+
+if ("@localeArchive@" ne "") {
+ $ENV{LOCALE_ARCHIVE} = "@localeArchive@";
+}
+
+if (!defined $action || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) {
+ print STDERR <<EOF;
+Usage: $0 [switch|boot|test]
+
+switch: make the configuration the boot default and activate now
+boot: make the configuration the boot default
+test: activate the configuration, but don\'t make it the boot default
+dry-activate: show what would be done if this configuration were activated
+EOF
+ exit 1;
+}
+
+# This is a NixOS installation if it has /etc/NIXOS or a proper
+# /etc/os-release.
+die "This is not a NixOS installation!\n" unless
+ -f "/etc/NIXOS" || (read_file("/etc/os-release", err_mode => 'quiet') // "") =~ /ID=nixos/s;
+
+openlog("nixos", "", LOG_USER);
+
+# Install or update the bootloader.
+if ($action eq "switch" || $action eq "boot") {
+ system("@installBootLoader@ $out") == 0 or exit 1;
+}
+
+# Just in case the new configuration hangs the system, do a sync now.
+system("@coreutils@/bin/sync", "-f", "/nix/store") unless ($ENV{"NIXOS_NO_SYNC"} // "") eq "1";
+
+exit 0 if $action eq "boot";
+
+# Check if we can activate the new configuration.
+my $oldVersion = read_file("/run/current-system/init-interface-version", err_mode => 'quiet') // "";
+my $newVersion = read_file("$out/init-interface-version");
+
+if ($newVersion ne $oldVersion) {
+ print STDERR <<EOF;
+Warning: the new NixOS configuration has an ‘init’ that is
+incompatible with the current configuration. The new configuration
+won\'t take effect until you reboot the system.
+EOF
+ exit 100;
+}
+
+# Ignore SIGHUP so that we're not killed if we're running on (say)
+# virtual console 1 and we restart the "tty1" unit.
+$SIG{PIPE} = "IGNORE";
+
+sub getActiveUnits {
+ my $mgr = Net::DBus->system->get_service("org.freedesktop.systemd1")->get_object("/org/freedesktop/systemd1");
+ my $units = $mgr->ListUnitsByPatterns([], []);
+ my $res = {};
+ for my $item (@$units) {
+ my ($id, $description, $load_state, $active_state, $sub_state,
+ $following, $unit_path, $job_id, $job_type, $job_path) = @$item;
+ next unless $following eq '';
+ next if $job_id == 0 and $active_state eq 'inactive';
+ $res->{$id} = { load => $load_state, state => $active_state, substate => $sub_state };
+ }
+ return $res;
+}
+
+sub parseFstab {
+ my ($filename) = @_;
+ my ($fss, $swaps);
+ foreach my $line (read_file($filename, err_mode => 'quiet')) {
+ chomp $line;
+ $line =~ s/^\s*#.*//;
+ next if $line =~ /^\s*$/;
+ my @xs = split / /, $line;
+ if ($xs[2] eq "swap") {
+ $swaps->{$xs[0]} = { options => $xs[3] // "" };
+ } else {
+ $fss->{$xs[1]} = { device => $xs[0], fsType => $xs[2], options => $xs[3] // "" };
+ }
+ }
+ return ($fss, $swaps);
+}
+
+sub parseUnit {
+ my ($filename) = @_;
+ my $info = {};
+ parseKeyValues($info, read_file($filename)) if -f $filename;
+ parseKeyValues($info, read_file("${filename}.d/overrides.conf")) if -f "${filename}.d/overrides.conf";
+ return $info;
+}
+
+sub parseKeyValues {
+ my $info = shift;
+ foreach my $line (@_) {
+ # FIXME: not quite correct.
+ $line =~ /^([^=]+)=(.*)$/ or next;
+ $info->{$1} = $2;
+ }
+}
+
+sub boolIsTrue {
+ my ($s) = @_;
+ return $s eq "yes" || $s eq "true";
+}
+
+sub recordUnit {
+ my ($fn, $unit) = @_;
+ write_file($fn, { append => 1 }, "$unit\n") if $action ne "dry-activate";
+}
+
+# As a fingerprint for determining whether a unit has changed, we use
+# its absolute path. If it has an override file, we append *its*
+# absolute path as well.
+sub fingerprintUnit {
+ my ($s) = @_;
+ return abs_path($s) . (-f "${s}.d/overrides.conf" ? " " . abs_path "${s}.d/overrides.conf" : "");
+}
+
+# Figure out what units need to be stopped, started, restarted or reloaded.
+my (%unitsToStop, %unitsToSkip, %unitsToStart, %unitsToRestart, %unitsToReload);
+
+my %unitsToFilter; # units not shown
+
+$unitsToStart{$_} = 1 foreach
+ split('\n', read_file($startListFile, err_mode => 'quiet') // "");
+
+$unitsToRestart{$_} = 1 foreach
+ split('\n', read_file($restartListFile, err_mode => 'quiet') // "");
+
+$unitsToReload{$_} = 1 foreach
+ split '\n', read_file($reloadListFile, err_mode => 'quiet') // "";
+
+my $activePrev = getActiveUnits;
+while (my ($unit, $state) = each %{$activePrev}) {
+ my $baseUnit = $unit;
+
+ my $prevUnitFile = "/etc/systemd/system/$baseUnit";
+ my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+
+ # Detect template instances.
+ if (!-e $prevUnitFile && !-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
+ $baseUnit = "$1\@.$2";
+ $prevUnitFile = "/etc/systemd/system/$baseUnit";
+ $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+ }
+
+ my $baseName = $baseUnit;
+ $baseName =~ s/\.[a-z]*$//;
+
+ if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) {
+ if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") {
+ my $unitInfo = parseUnit($prevUnitFile);
+ $unitsToStop{$unit} = 1 if boolIsTrue($unitInfo->{'X-StopOnRemoval'} // "yes");
+ }
+
+ elsif ($unit =~ /\.target$/) {
+ my $unitInfo = parseUnit($newUnitFile);
+
+ # Cause all active target units to be restarted below.
+ # This should start most changed units we stop here as
+ # well as any new dependencies (including new mounts and
+ # swap devices). FIXME: the suspend target is sometimes
+ # active after the system has resumed, which probably
+ # should not be the case. Just ignore it.
+ if ($unit ne "suspend.target" && $unit ne "hibernate.target" && $unit ne "hybrid-sleep.target") {
+ unless (boolIsTrue($unitInfo->{'RefuseManualStart'} // "no")) {
+ $unitsToStart{$unit} = 1;
+ recordUnit($startListFile, $unit);
+ # Don't spam the user with target units that always get started.
+ $unitsToFilter{$unit} = 1;
+ }
+ }
+
+ # Stop targets that have X-StopOnReconfiguration set.
+ # This is necessary to respect dependency orderings
+ # involving targets: if unit X starts after target Y and
+ # target Y starts after unit Z, then if X and Z have both
+ # changed, then X should be restarted after Z. However,
+ # if target Y is in the "active" state, X and Z will be
+ # restarted at the same time because X's dependency on Y
+ # is already satisfied. Thus, we need to stop Y first.
+ # Stopping a target generally has no effect on other units
+ # (unless there is a PartOf dependency), so this is just a
+ # bookkeeping thing to get systemd to do the right thing.
+ if (boolIsTrue($unitInfo->{'X-StopOnReconfiguration'} // "no")) {
+ $unitsToStop{$unit} = 1;
+ }
+ }
+
+ elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) {
+ if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target") {
+ # Do nothing. These cannot be restarted directly.
+ } elsif ($unit =~ /\.mount$/) {
+ # Reload the changed mount unit to force a remount.
+ $unitsToReload{$unit} = 1;
+ recordUnit($reloadListFile, $unit);
+ } elsif ($unit =~ /\.socket$/ || $unit =~ /\.path$/ || $unit =~ /\.slice$/) {
+ # FIXME: do something?
+ } else {
+ my $unitInfo = parseUnit($newUnitFile);
+ if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) {
+ $unitsToReload{$unit} = 1;
+ recordUnit($reloadListFile, $unit);
+ }
+ elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes") || boolIsTrue($unitInfo->{'RefuseManualStop'} // "no") ) {
+ $unitsToSkip{$unit} = 1;
+ } else {
+ if (!boolIsTrue($unitInfo->{'X-StopIfChanged'} // "yes")) {
+ # This unit should be restarted instead of
+ # stopped and started.
+ $unitsToRestart{$unit} = 1;
+ recordUnit($restartListFile, $unit);
+ } else {
+ # If this unit is socket-activated, then stop the
+ # socket unit(s) as well, and restart the
+ # socket(s) instead of the service.
+ my $socketActivated = 0;
+ if ($unit =~ /\.service$/) {
+ my @sockets = split / /, ($unitInfo->{Sockets} // "");
+ if (scalar @sockets == 0) {
+ @sockets = ("$baseName.socket");
+ }
+ foreach my $socket (@sockets) {
+ if (defined $activePrev->{$socket}) {
+ $unitsToStop{$socket} = 1;
+ $unitsToStart{$socket} = 1;
+ recordUnit($startListFile, $socket);
+ $socketActivated = 1;
+ }
+ }
+ }
+
+ # If the unit is not socket-activated, record
+ # that this unit needs to be started below.
+ # We write this to a file to ensure that the
+ # service gets restarted if we're interrupted.
+ if (!$socketActivated) {
+ $unitsToStart{$unit} = 1;
+ recordUnit($startListFile, $unit);
+ }
+
+ $unitsToStop{$unit} = 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+sub pathToUnitName {
+ my ($path) = @_;
+ # Use current version of systemctl binary before daemon is reexeced.
+ open my $cmd, "-|", "$curSystemd/systemd-escape", "--suffix=mount", "-p", $path
+ or die "Unable to escape $path!\n";
+ my $escaped = join "", <$cmd>;
+ chomp $escaped;
+ close $cmd or die;
+ return $escaped;
+}
+
+sub unique {
+ my %seen;
+ my @res;
+ foreach my $name (@_) {
+ next if $seen{$name};
+ $seen{$name} = 1;
+ push @res, $name;
+ }
+ return @res;
+}
+
+# Compare the previous and new fstab to figure out which filesystems
+# need a remount or need to be unmounted. New filesystems are mounted
+# automatically by starting local-fs.target. FIXME: might be nicer if
+# we generated units for all mounts; then we could unify this with the
+# unit checking code above.
+my ($prevFss, $prevSwaps) = parseFstab "/etc/fstab";
+my ($newFss, $newSwaps) = parseFstab "$out/etc/fstab";
+foreach my $mountPoint (keys %$prevFss) {
+ my $prev = $prevFss->{$mountPoint};
+ my $new = $newFss->{$mountPoint};
+ my $unit = pathToUnitName($mountPoint);
+ if (!defined $new) {
+ # Filesystem entry disappeared, so unmount it.
+ $unitsToStop{$unit} = 1;
+ } elsif ($prev->{fsType} ne $new->{fsType} || $prev->{device} ne $new->{device}) {
+ # Filesystem type or device changed, so unmount and mount it.
+ $unitsToStop{$unit} = 1;
+ $unitsToStart{$unit} = 1;
+ recordUnit($startListFile, $unit);
+ } elsif ($prev->{options} ne $new->{options}) {
+ # Mount options changes, so remount it.
+ $unitsToReload{$unit} = 1;
+ recordUnit($reloadListFile, $unit);
+ }
+}
+
+# Also handles swap devices.
+foreach my $device (keys %$prevSwaps) {
+ my $prev = $prevSwaps->{$device};
+ my $new = $newSwaps->{$device};
+ if (!defined $new) {
+ # Swap entry disappeared, so turn it off. Can't use
+ # "systemctl stop" here because systemd has lots of alias
+ # units that prevent a stop from actually calling
+ # "swapoff".
+ print STDERR "stopping swap device: $device\n";
+ system("@utillinux@/sbin/swapoff", $device);
+ }
+ # FIXME: update swap options (i.e. its priority).
+}
+
+
+# Should we have systemd re-exec itself?
+my $prevSystemd = abs_path("/proc/1/exe") // "/unknown";
+my $newSystemd = abs_path("@systemd@/lib/systemd/systemd") or die;
+my $restartSystemd = $prevSystemd ne $newSystemd;
+
+
+sub filterUnits {
+ my ($units) = @_;
+ my @res;
+ foreach my $unit (sort(keys %{$units})) {
+ push @res, $unit if !defined $unitsToFilter{$unit};
+ }
+ return @res;
+}
+
+my @unitsToStopFiltered = filterUnits(\%unitsToStop);
+my @unitsToStartFiltered = filterUnits(\%unitsToStart);
+
+
+# Show dry-run actions.
+if ($action eq "dry-activate") {
+ print STDERR "would stop the following units: ", join(", ", @unitsToStopFiltered), "\n"
+ if scalar @unitsToStopFiltered > 0;
+ print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n"
+ if scalar(keys %unitsToSkip) > 0;
+ print STDERR "would restart systemd\n" if $restartSystemd;
+ print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"
+ if scalar(keys %unitsToRestart) > 0;
+ print STDERR "would start the following units: ", join(", ", @unitsToStartFiltered), "\n"
+ if scalar @unitsToStartFiltered;
+ print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n"
+ if scalar(keys %unitsToReload) > 0;
+ exit 0;
+}
+
+
+syslog(LOG_NOTICE, "switching to system configuration $out");
+
+if (scalar (keys %unitsToStop) > 0) {
+ print STDERR "stopping the following units: ", join(", ", @unitsToStopFiltered), "\n"
+ if scalar @unitsToStopFiltered;
+ # Use current version of systemctl binary before daemon is reexeced.
+ system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToStop)); # FIXME: ignore errors?
+}
+
+print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n"
+ if scalar(keys %unitsToSkip) > 0;
+
+# Activate the new configuration (i.e., update /etc, make accounts,
+# and so on).
+my $res = 0;
+print STDERR "activating the configuration...\n";
+system("$out/activate", "$out") == 0 or $res = 2;
+
+# Restart systemd if necessary. Note that this is done using the
+# current version of systemd, just in case the new one has trouble
+# communicating with the running pid 1.
+if ($restartSystemd) {
+ print STDERR "restarting systemd...\n";
+ system("$curSystemd/systemctl", "daemon-reexec") == 0 or $res = 2;
+}
+
+# Forget about previously failed services.
+system("@systemd@/bin/systemctl", "reset-failed");
+
+# Make systemd reload its units.
+system("@systemd@/bin/systemctl", "daemon-reload") == 0 or $res = 3;
+
+# Reload user units
+open my $listActiveUsers, '-|', '@systemd@/bin/loginctl', 'list-users', '--no-legend';
+while (my $f = <$listActiveUsers>) {
+ next unless $f =~ /^\s*(?<uid>\d+)\s+(?<user>\S+)/;
+ my ($uid, $name) = ($+{uid}, $+{user});
+ print STDERR "reloading user units for $name...\n";
+
+ system("@su@", "-s", "@shell@", "-l", $name, "-c",
+ "export XDG_RUNTIME_DIR=/run/user/$uid; " .
+ "$curSystemd/systemctl --user daemon-reexec; " .
+ "@systemd@/bin/systemctl --user start nixos-activation.service");
+}
+
+close $listActiveUsers;
+
+# Set the new tmpfiles
+print STDERR "setting up tmpfiles\n";
+system("@systemd@/bin/systemd-tmpfiles", "--create", "--remove", "--exclude-prefix=/dev") == 0 or $res = 3;
+
+# Reload units that need it. This includes remounting changed mount
+# units.
+if (scalar(keys %unitsToReload) > 0) {
+ print STDERR "reloading the following units: ", join(", ", sort(keys %unitsToReload)), "\n";
+ system("@systemd@/bin/systemctl", "reload", "--", sort(keys %unitsToReload)) == 0 or $res = 4;
+ unlink($reloadListFile);
+}
+
+# Restart changed services (those that have to be restarted rather
+# than stopped and started).
+if (scalar(keys %unitsToRestart) > 0) {
+ print STDERR "restarting the following units: ", join(", ", sort(keys %unitsToRestart)), "\n";
+ system("@systemd@/bin/systemctl", "restart", "--", sort(keys %unitsToRestart)) == 0 or $res = 4;
+ unlink($restartListFile);
+}
+
+# Start all active targets, as well as changed units we stopped above.
+# The latter is necessary because some may not be dependencies of the
+# targets (i.e., they were manually started). FIXME: detect units
+# that are symlinks to other units. We shouldn't start both at the
+# same time because we'll get a "Failed to add path to set" error from
+# systemd.
+print STDERR "starting the following units: ", join(", ", @unitsToStartFiltered), "\n"
+ if scalar @unitsToStartFiltered;
+system("@systemd@/bin/systemctl", "start", "--", sort(keys %unitsToStart)) == 0 or $res = 4;
+unlink($startListFile);
+
+
+# Print failed and new units.
+my (@failed, @new, @restarting);
+my $activeNew = getActiveUnits;
+while (my ($unit, $state) = each %{$activeNew}) {
+ if ($state->{state} eq "failed") {
+ push @failed, $unit;
+ }
+ elsif ($state->{state} eq "auto-restart") {
+ # A unit in auto-restart state is a failure *if* it previously failed to start
+ my $lines = `@systemd@/bin/systemctl show '$unit'`;
+ my $info = {};
+ parseKeyValues($info, split("\n", $lines));
+
+ if ($info->{ExecMainStatus} ne '0') {
+ push @failed, $unit;
+ }
+ }
+ elsif ($state->{state} ne "failed" && !defined $activePrev->{$unit}) {
+ push @new, $unit;
+ }
+}
+
+print STDERR "the following new units were started: ", join(", ", sort(@new)), "\n"
+ if scalar @new > 0;
+
+if (scalar @failed > 0) {
+ print STDERR "warning: the following units failed: ", join(", ", sort(@failed)), "\n";
+ foreach my $unit (@failed) {
+ print STDERR "\n";
+ system("COLUMNS=1000 @systemd@/bin/systemctl status --no-pager '$unit' >&2");
+ }
+ $res = 4;
+}
+
+if ($res == 0) {
+ syslog(LOG_NOTICE, "finished switching to system configuration $out");
+} else {
+ syslog(LOG_ERR, "switching to system configuration $out failed (status $res)");
+}
+
+exit $res;
diff --git a/nixpkgs/nixos/modules/system/activation/top-level.nix b/nixpkgs/nixos/modules/system/activation/top-level.nix
new file mode 100644
index 00000000000..f67d2900561
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/activation/top-level.nix
@@ -0,0 +1,278 @@
+{ config, lib, pkgs, modules, baseModules, ... }:
+
+with lib;
+
+let
+
+
+ # This attribute is responsible for creating boot entries for
+ # child configuration. They are only (directly) accessible
+ # when the parent configuration is boot default. For example,
+ # you can provide an easy way to boot the same configuration
+ # as you use, but with another kernel
+ # !!! fix this
+ cloner = inheritParent: list:
+ map (childConfig:
+ (import ../../../lib/eval-config.nix {
+ inherit baseModules;
+ modules =
+ (optionals inheritParent modules)
+ ++ [ ./no-clone.nix ]
+ ++ [ childConfig ];
+ }).config.system.build.toplevel
+ ) list;
+
+ children =
+ cloner false config.nesting.children
+ ++ cloner true config.nesting.clone;
+
+ systemBuilder =
+ let
+ kernelPath = "${config.boot.kernelPackages.kernel}/" +
+ "${config.system.boot.loader.kernelFile}";
+ initrdPath = "${config.system.build.initialRamdisk}/" +
+ "${config.system.boot.loader.initrdFile}";
+ in ''
+ mkdir $out
+
+ # Containers don't have their own kernel or initrd. They boot
+ # directly into stage 2.
+ ${optionalString (!config.boot.isContainer) ''
+ if [ ! -f ${kernelPath} ]; then
+ echo "The bootloader cannot find the proper kernel image."
+ echo "(Expecting ${kernelPath})"
+ false
+ fi
+
+ ln -s ${kernelPath} $out/kernel
+ ln -s ${config.system.modulesTree} $out/kernel-modules
+ ${optionalString (config.hardware.deviceTree.package != null) ''
+ ln -s ${config.hardware.deviceTree.package} $out/dtbs
+ ''}
+
+ echo -n "$kernelParams" > $out/kernel-params
+
+ ln -s ${initrdPath} $out/initrd
+
+ ln -s ${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets $out
+
+ ln -s ${config.hardware.firmware}/lib/firmware $out/firmware
+ ''}
+
+ echo "$activationScript" > $out/activate
+ substituteInPlace $out/activate --subst-var out
+ chmod u+x $out/activate
+ unset activationScript
+
+ cp ${config.system.build.bootStage2} $out/init
+ substituteInPlace $out/init --subst-var-by systemConfig $out
+
+ ln -s ${config.system.build.etc}/etc $out/etc
+ ln -s ${config.system.path} $out/sw
+ ln -s "$systemd" $out/systemd
+
+ echo -n "$configurationName" > $out/configuration-name
+ echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version
+ echo -n "$nixosLabel" > $out/nixos-version
+ echo -n "${pkgs.stdenv.hostPlatform.system}" > $out/system
+
+ mkdir $out/fine-tune
+ childCount=0
+ for i in $children; do
+ childCount=$(( childCount + 1 ))
+ ln -s $i $out/fine-tune/child-$childCount
+ done
+
+ mkdir $out/bin
+ export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive"
+ substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration
+ chmod +x $out/bin/switch-to-configuration
+
+ echo -n "${toString config.system.extraDependencies}" > $out/extra-dependencies
+
+ ${config.system.extraSystemBuilderCmds}
+ '';
+
+ # 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 = 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);
+
+ baseSystemAssertWarn = if failedAssertions != []
+ then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
+ else showWarnings config.warnings baseSystem;
+
+ # Replace runtime dependencies
+ system = fold ({ oldDependency, newDependency }: drv:
+ pkgs.replaceDependency { inherit oldDependency newDependency drv; }
+ ) baseSystemAssertWarn config.system.replaceRuntimeDependencies;
+
+in
+
+{
+ options = {
+
+ system.build = mkOption {
+ internal = true;
+ default = {};
+ type = types.attrs;
+ description = ''
+ Attribute set of derivations used to setup the system.
+ '';
+ };
+
+ nesting.children = mkOption {
+ default = [];
+ description = ''
+ Additional configurations to build.
+ '';
+ };
+
+ nesting.clone = mkOption {
+ default = [];
+ description = ''
+ Additional configurations to build based on the current
+ configuration which then has a lower priority.
+
+ To switch to a cloned configuration (e.g. <literal>child-1</literal>)
+ at runtime, run
+
+ <programlisting>
+ # sudo /run/current-system/fine-tune/child-1/bin/switch-to-configuration test
+ </programlisting>
+ '';
+ };
+
+ system.boot.loader.id = mkOption {
+ internal = true;
+ default = "";
+ description = ''
+ Id string of the used bootloader.
+ '';
+ };
+
+ system.boot.loader.kernelFile = mkOption {
+ internal = true;
+ default = pkgs.stdenv.hostPlatform.platform.kernelTarget;
+ type = types.str;
+ description = ''
+ Name of the kernel file to be passed to the bootloader.
+ '';
+ };
+
+ system.boot.loader.initrdFile = mkOption {
+ internal = true;
+ default = "initrd";
+ type = types.str;
+ description = ''
+ Name of the initrd file to be passed to the bootloader.
+ '';
+ };
+
+ system.copySystemConfiguration = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, copies the NixOS configuration file
+ (usually <filename>/etc/nixos/configuration.nix</filename>)
+ and links it from the resulting system
+ (getting to <filename>/run/current-system/configuration.nix</filename>).
+ Note that only this single file is copied, even if it imports others.
+ '';
+ };
+
+ system.extraSystemBuilderCmds = mkOption {
+ type = types.lines;
+ internal = true;
+ default = "";
+ description = ''
+ This code will be added to the builder creating the system store path.
+ '';
+ };
+
+ system.extraDependencies = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = ''
+ A list of packages that should be included in the system
+ closure but not otherwise made available to users. This is
+ primarily used by the installation tests.
+ '';
+ };
+
+ system.replaceRuntimeDependencies = mkOption {
+ default = [];
+ example = lib.literalExample "[ ({ original = pkgs.openssl; replacement = pkgs.callPackage /path/to/openssl { }; }) ]";
+ type = types.listOf (types.submodule (
+ { ... }: {
+ options.original = mkOption {
+ type = types.package;
+ description = "The original package to override.";
+ };
+
+ options.replacement = mkOption {
+ type = types.package;
+ description = "The replacement package.";
+ };
+ })
+ );
+ apply = map ({ original, replacement, ... }: {
+ oldDependency = original;
+ newDependency = replacement;
+ });
+ description = ''
+ List of packages to override without doing a full rebuild.
+ The original derivation and replacement derivation must have the same
+ name length, and ideally should have close-to-identical directory layout.
+ '';
+ };
+
+ };
+
+
+ config = {
+
+ system.extraSystemBuilderCmds =
+ optionalString
+ config.system.copySystemConfiguration
+ ''ln -s '${import ../../../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>}' \
+ "$out/configuration.nix"
+ '';
+
+ system.build.toplevel = system;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/binfmt.nix b/nixpkgs/nixos/modules/system/boot/binfmt.nix
new file mode 100644
index 00000000000..a32c9dc1f2b
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/binfmt.nix
@@ -0,0 +1,272 @@
+{ config, lib, pkgs, ... }:
+let
+ inherit (lib) mkOption types optionalString;
+
+ cfg = config.boot.binfmt;
+
+ makeBinfmtLine = name: { recognitionType, offset, magicOrExtension
+ , mask, preserveArgvZero, openBinary
+ , matchCredentials, fixBinary, ...
+ }: let
+ type = if recognitionType == "magic" then "M" else "E";
+ offset' = toString offset;
+ mask' = toString mask;
+ interpreter = "/run/binfmt/${name}";
+ flags = if !(matchCredentials -> openBinary)
+ then throw "boot.binfmt.registrations.${name}: you can't specify openBinary = false when matchCredentials = true."
+ else optionalString preserveArgvZero "P" +
+ optionalString (openBinary && !matchCredentials) "O" +
+ optionalString matchCredentials "C" +
+ optionalString fixBinary "F";
+ in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
+
+ activationSnippet = name: { interpreter, ... }:
+ "ln -sf ${interpreter} /run/binfmt/${name}";
+
+ getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs;
+
+ # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
+ # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
+ # and
+ # - https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh
+ # TODO: maybe put these in a JSON file?
+ magics = {
+ armv6l-linux = {
+ magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+ };
+ armv7l-linux = {
+ magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+ };
+ aarch64-linux = {
+ magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+ };
+ aarch64_be-linux = {
+ magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+ };
+ i386-linux = {
+ magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+ };
+ i486-linux = {
+ magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+ };
+ i586-linux = {
+ magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+ };
+ i686-linux = {
+ magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+ };
+ x86_64-linux = {
+ magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+ };
+ alpha-linux = {
+ magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90'';
+ mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+ };
+ sparc64-linux = {
+ magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+ };
+ sparc-linux = {
+ magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+ };
+ powerpc-linux = {
+ magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+ };
+ powerpc64-linux = {
+ magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+ };
+ powerpc64le-linux = {
+ magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00'';
+ };
+ mips-linux = {
+ magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+ };
+ mipsel-linux = {
+ magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+ };
+ mips64-linux = {
+ magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+ };
+ mips64el-linux = {
+ magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+ };
+ riscv32-linux = {
+ magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+ };
+ riscv64-linux = {
+ magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
+ mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+ };
+ wasm32-wasi = {
+ magicOrExtension = ''\x00asm'';
+ mask = ''\xff\xff\xff\xff'';
+ };
+ wasm64-wasi = {
+ magicOrExtension = ''\x00asm'';
+ mask = ''\xff\xff\xff\xff'';
+ };
+ x86_64-windows = {
+ magicOrExtension = ".exe";
+ recognitionType = "extension";
+ };
+ i686-windows = {
+ magicOrExtension = ".exe";
+ recognitionType = "extension";
+ };
+ };
+
+in {
+ options = {
+ boot.binfmt = {
+ registrations = mkOption {
+ default = {};
+
+ description = ''
+ Extra binary formats to register with the kernel.
+ See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
+ '';
+
+ type = types.attrsOf (types.submodule ({ config, ... }: {
+ options = {
+ recognitionType = mkOption {
+ default = "magic";
+ description = "Whether to recognize executables by magic number or extension.";
+ type = types.enum [ "magic" "extension" ];
+ };
+
+ offset = mkOption {
+ default = null;
+ description = "The byte offset of the magic number used for recognition.";
+ type = types.nullOr types.int;
+ };
+
+ magicOrExtension = mkOption {
+ description = "The magic number or extension to match on.";
+ type = types.str;
+ };
+
+ mask = mkOption {
+ default = null;
+ description =
+ "A mask to be ANDed with the byte sequence of the file before matching";
+ type = types.nullOr types.str;
+ };
+
+ interpreter = mkOption {
+ description = ''
+ The interpreter to invoke to run the program.
+
+ Note that the actual registration will point to
+ /run/binfmt/''${name}, so the kernel interpreter length
+ limit doesn't apply.
+ '';
+ type = types.path;
+ };
+
+ preserveArgvZero = mkOption {
+ default = false;
+ description = ''
+ Whether to pass the original argv[0] to the interpreter.
+
+ See the description of the 'P' flag in the kernel docs
+ for more details;
+ '';
+ type = types.bool;
+ };
+
+ openBinary = mkOption {
+ default = config.matchCredentials;
+ description = ''
+ Whether to pass the binary to the interpreter as an open
+ file descriptor, instead of a path.
+ '';
+ type = types.bool;
+ };
+
+ matchCredentials = mkOption {
+ default = false;
+ description = ''
+ Whether to launch with the credentials and security
+ token of the binary, not the interpreter (e.g. setuid
+ bit).
+
+ See the description of the 'C' flag in the kernel docs
+ for more details.
+
+ Implies/requires openBinary = true.
+ '';
+ type = types.bool;
+ };
+
+ fixBinary = mkOption {
+ default = false;
+ description = ''
+ Whether to open the interpreter file as soon as the
+ registration is loaded, rather than waiting for a
+ relevant file to be invoked.
+
+ See the description of the 'F' flag in the kernel docs
+ for more details.
+ '';
+ type = types.bool;
+ };
+ };
+ }));
+ };
+
+ emulatedSystems = mkOption {
+ default = [];
+ example = [ "wasm32-wasi" "x86_64-windows" "aarch64-linux" ];
+ description = ''
+ List of systems to emulate. Will also configure Nix to
+ support your new systems.
+ '';
+ type = types.listOf types.str;
+ };
+ };
+ };
+
+ config = {
+ boot.binfmt.registrations = builtins.listToAttrs (map (system: {
+ name = system;
+ value = {
+ interpreter = getEmulator system;
+ } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"));
+ }) cfg.emulatedSystems);
+ # TODO: add a nix.extraPlatforms option to NixOS!
+ nix.extraOptions = lib.mkIf (cfg.emulatedSystems != []) ''
+ extra-platforms = ${toString (cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux")}
+ '';
+ nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != [])
+ ([ "/run/binfmt" ] ++ (map (system: dirOf (dirOf (getEmulator system))) cfg.emulatedSystems));
+
+ environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
+ (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
+ system.activationScripts.binfmt = ''
+ mkdir -p -m 0755 /run/binfmt
+ ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)}
+ '';
+ systemd.additionalUpstreamSystemUnits = lib.mkIf (config.boot.binfmt.registrations != {})
+ [ "proc-sys-fs-binfmt_misc.automount"
+ "proc-sys-fs-binfmt_misc.mount"
+ ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/emergency-mode.nix b/nixpkgs/nixos/modules/system/boot/emergency-mode.nix
new file mode 100644
index 00000000000..9cdab841619
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/emergency-mode.nix
@@ -0,0 +1,37 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ systemd.enableEmergencyMode = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable emergency mode, which is an
+ <command>sulogin</command> shell started on the console if
+ mounting a filesystem fails. Since some machines (like EC2
+ instances) have no console of any kind, emergency mode doesn't
+ make sense, and it's better to continue with the boot insofar
+ as possible.
+ '';
+ };
+
+ };
+
+ ###### implementation
+
+ config = {
+
+ systemd.additionalUpstreamSystemUnits = optionals
+ config.systemd.enableEmergencyMode [
+ "emergency.target" "emergency.service"
+ ];
+
+ };
+
+} \ No newline at end of file
diff --git a/nixpkgs/nixos/modules/system/boot/grow-partition.nix b/nixpkgs/nixos/modules/system/boot/grow-partition.nix
new file mode 100644
index 00000000000..8c9b1502558
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/grow-partition.nix
@@ -0,0 +1,50 @@
+# This module automatically grows the root partition.
+# This allows an instance to be created with a bigger root filesystem
+# than provided by the machine image.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ options = {
+ boot.growPartition = mkEnableOption "grow the root partition on boot";
+ };
+
+ config = mkIf config.boot.growPartition {
+
+ boot.initrd.extraUtilsCommands = ''
+ copy_bin_and_libs ${pkgs.gawk}/bin/gawk
+ copy_bin_and_libs ${pkgs.gnused}/bin/sed
+ copy_bin_and_libs ${pkgs.utillinux}/sbin/sfdisk
+ copy_bin_and_libs ${pkgs.utillinux}/sbin/lsblk
+
+ substitute "${pkgs.cloud-utils}/bin/.growpart-wrapped" "$out/bin/growpart" \
+ --replace "${pkgs.bash}/bin/sh" "/bin/sh" \
+ --replace "awk" "gawk" \
+ --replace "sed" "gnused"
+
+ ln -s sed $out/bin/gnused
+ '';
+
+ boot.initrd.postDeviceCommands = ''
+ rootDevice="${config.fileSystems."/".device}"
+ if waitDevice "$rootDevice"; then
+ rootDevice="$(readlink -f "$rootDevice")"
+ parentDevice="$rootDevice"
+ while [ "''${parentDevice%[0-9]}" != "''${parentDevice}" ]; do
+ parentDevice="''${parentDevice%[0-9]}";
+ done
+ partNum="''${rootDevice#''${parentDevice}}"
+ if [ "''${parentDevice%[0-9]p}" != "''${parentDevice}" ] && [ -b "''${parentDevice%p}" ]; then
+ parentDevice="''${parentDevice%p}"
+ fi
+ TMPDIR=/run sh $(type -P growpart) "$parentDevice" "$partNum"
+ udevadm settle
+ fi
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/initrd-network.nix b/nixpkgs/nixos/modules/system/boot/initrd-network.nix
new file mode 100644
index 00000000000..cb8fc957a99
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/initrd-network.nix
@@ -0,0 +1,138 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+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"
+ fi
+ if [ -n "$dns" ]; then
+ rm -f /etc/resolv.conf
+ for i in $dns; do
+ echo "nameserver $dns" >> /etc/resolv.conf
+ done
+ fi
+ fi
+ '';
+
+ udhcpcArgs = toString cfg.udhcpc.extraArgs;
+
+in
+
+{
+
+ options = {
+
+ boot.initrd.network.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Add network connectivity support to initrd. The network may be
+ configured using the <literal>ip</literal> kernel parameter,
+ as described in <link
+ xlink:href="https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt">the
+ kernel documentation</link>. Otherwise, if
+ <option>networking.useDHCP</option> is enabled, an IP address
+ is acquired using DHCP.
+
+ You should add the module(s) required for your network card to
+ boot.initrd.availableKernelModules.
+ <literal>lspci -v | grep -iA8 'network\|ethernet'</literal>
+ will tell you which.
+ '';
+ };
+
+ boot.initrd.network.udhcpc.extraArgs = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ Additional command-line arguments passed verbatim to udhcpc if
+ <option>boot.initrd.network.enable</option> and <option>networking.useDHCP</option>
+ are enabled.
+ '';
+ };
+
+ boot.initrd.network.postCommands = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands to be executed after stage 1 of the
+ boot has initialised the network.
+ '';
+ };
+
+
+ };
+
+ config = mkIf cfg.enable {
+
+ boot.initrd.kernelModules = [ "af_packet" ];
+
+ boot.initrd.extraUtilsCommands = ''
+ copy_bin_and_libs ${pkgs.mkinitcpio-nfs-utils}/bin/ipconfig
+ '';
+
+ boot.initrd.preLVMCommands = mkBefore (
+ # Search for interface definitions in command line.
+ ''
+ for o in $(cat /proc/cmdline); do
+ case $o in
+ ip=*)
+ ipconfig $o && hasNetwork=1
+ ;;
+ esac
+ done
+ ''
+
+ # Otherwise, use DHCP.
+ + optionalString (config.networking.useDHCP || dhcpinterfaces != []) ''
+ if [ -z "$hasNetwork" ]; then
+
+ # Bring up all interfaces.
+ for iface in $(ls /sys/class/net/); do
+ echo "bringing up network interface $iface..."
+ ip link set "$iface" up
+ done
+
+ # 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
+ ''
+
+ + ''
+ if [ -n "$hasNetwork" ]; then
+ echo "networking is up!"
+ ${cfg.postCommands}
+ fi
+ '');
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/initrd-ssh.nix b/nixpkgs/nixos/modules/system/boot/initrd-ssh.nix
new file mode 100644
index 00000000000..2d3e3b05c98
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/initrd-ssh.nix
@@ -0,0 +1,132 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.boot.initrd.network.ssh;
+
+in
+
+{
+
+ options = {
+
+ boot.initrd.network.ssh.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Start SSH service during initrd boot. It can be used to debug failing
+ boot on a remote server, enter pasphrase for an encrypted partition etc.
+ Service is killed when stage-1 boot is finished.
+ '';
+ };
+
+ boot.initrd.network.ssh.port = mkOption {
+ type = types.int;
+ default = 22;
+ description = ''
+ Port on which SSH initrd service should listen.
+ '';
+ };
+
+ boot.initrd.network.ssh.shell = mkOption {
+ type = types.str;
+ default = "/bin/ash";
+ description = ''
+ Login shell of the remote user. Can be used to limit actions user can do.
+ '';
+ };
+
+ boot.initrd.network.ssh.hostRSAKey = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ RSA SSH private key file in the Dropbear format.
+
+ WARNING: Unless your bootloader supports initrd secrets, this key is
+ contained insecurely in the global Nix store. Do NOT use your regular
+ SSH host private keys for this purpose or you'll expose them to
+ regular users!
+ '';
+ };
+
+ boot.initrd.network.ssh.hostDSSKey = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ DSS SSH private key file in the Dropbear format.
+
+ WARNING: Unless your bootloader supports initrd secrets, this key is
+ contained insecurely in the global Nix store. Do NOT use your regular
+ SSH host private keys for this purpose or you'll expose them to
+ regular users!
+ '';
+ };
+
+ boot.initrd.network.ssh.hostECDSAKey = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ ECDSA SSH private key file in the Dropbear format.
+
+ WARNING: Unless your bootloader supports initrd secrets, this key is
+ contained insecurely in the global Nix store. Do NOT use your regular
+ SSH host private keys for this purpose or you'll expose them to
+ regular users!
+ '';
+ };
+
+ boot.initrd.network.ssh.authorizedKeys = mkOption {
+ type = types.listOf types.str;
+ 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.
+ '';
+ };
+
+ };
+
+ config = mkIf (config.boot.initrd.network.enable && cfg.enable) {
+ assertions = [
+ { assertion = cfg.authorizedKeys != [];
+ message = "You should specify at least one authorized key for initrd SSH";
+ }
+ ];
+
+ boot.initrd.extraUtilsCommands = ''
+ copy_bin_and_libs ${pkgs.dropbear}/bin/dropbear
+ cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
+ '';
+
+ boot.initrd.extraUtilsCommandsTest = ''
+ $out/bin/dropbear -V
+ '';
+
+ boot.initrd.network.postCommands = ''
+ echo '${cfg.shell}' > /etc/shells
+ echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd
+ echo 'passwd: files' > /etc/nsswitch.conf
+
+ mkdir -p /var/log
+ touch /var/log/lastlog
+
+ mkdir -p /etc/dropbear
+
+ mkdir -p /root/.ssh
+ ${concatStrings (map (key: ''
+ echo ${escapeShellArg key} >> /root/.ssh/authorized_keys
+ '') cfg.authorizedKeys)}
+
+ dropbear -s -j -k -E -p ${toString cfg.port} ${optionalString (cfg.hostRSAKey == null && cfg.hostDSSKey == null && cfg.hostECDSAKey == null) "-R"}
+ '';
+
+ boot.initrd.secrets =
+ (optionalAttrs (cfg.hostRSAKey != null) { "/etc/dropbear/dropbear_rsa_host_key" = cfg.hostRSAKey; }) //
+ (optionalAttrs (cfg.hostDSSKey != null) { "/etc/dropbear/dropbear_dss_host_key" = cfg.hostDSSKey; }) //
+ (optionalAttrs (cfg.hostECDSAKey != null) { "/etc/dropbear/dropbear_ecdsa_host_key" = cfg.hostECDSAKey; });
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/kernel.nix b/nixpkgs/nixos/modules/system/boot/kernel.nix
new file mode 100644
index 00000000000..8a309f3bc5f
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/kernel.nix
@@ -0,0 +1,326 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (config.boot) kernelPatches;
+ inherit (config.boot.kernel) features randstructSeed;
+ inherit (config.boot.kernelPackages) kernel;
+
+ kernelModulesConf = pkgs.writeText "nixos.conf"
+ ''
+ ${concatStringsSep "\n" config.boot.kernelModules}
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ boot.kernel.features = mkOption {
+ default = {};
+ example = literalExample "{ debug = true; }";
+ internal = true;
+ description = ''
+ This option allows to enable or disable certain kernel features.
+ It's not API, because it's about kernel feature sets, that
+ make sense for specific use cases. Mostly along with programs,
+ which would have separate nixos options.
+ `grep features pkgs/os-specific/linux/kernel/common-config.nix`
+ '';
+ };
+
+ boot.kernelPackages = mkOption {
+ default = pkgs.linuxPackages;
+ type = types.unspecified // { merge = mergeEqualOption; };
+ apply = kernelPackages: kernelPackages.extend (self: super: {
+ kernel = super.kernel.override {
+ inherit randstructSeed;
+ kernelPatches = super.kernel.kernelPatches ++ kernelPatches;
+ features = lib.recursiveUpdate super.kernel.features features;
+ };
+ });
+ # We don't want to evaluate all of linuxPackages for the manual
+ # - some of it might not even evaluate correctly.
+ defaultText = "pkgs.linuxPackages";
+ example = literalExample "pkgs.linuxPackages_2_6_25";
+ description = ''
+ This option allows you to override the Linux kernel used by
+ NixOS. Since things like external kernel module packages are
+ tied to the kernel you're using, it also overrides those.
+ This option is a function that takes Nixpkgs as an argument
+ (as a convenience), and returns an attribute set containing at
+ the very least an attribute <varname>kernel</varname>.
+ Additional attributes may be needed depending on your
+ configuration. For instance, if you use the NVIDIA X driver,
+ then it also needs to contain an attribute
+ <varname>nvidia_x11</varname>.
+ '';
+ };
+
+ boot.kernelPatches = mkOption {
+ type = types.listOf types.attrs;
+ default = [];
+ example = literalExample "[ pkgs.kernelPatches.ubuntu_fan_4_4 ]";
+ description = "A list of additional patches to apply to the kernel.";
+ };
+
+ boot.kernel.randstructSeed = mkOption {
+ type = types.str;
+ default = "";
+ example = "my secret seed";
+ description = ''
+ Provides a custom seed for the <varname>RANDSTRUCT</varname> security
+ option of the Linux kernel. Note that <varname>RANDSTRUCT</varname> is
+ only enabled in NixOS hardened kernels. Using a custom seed requires
+ building the kernel and dependent packages locally, since this
+ customization happens at build time.
+ '';
+ };
+
+ boot.kernelParams = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = "Parameters added to the kernel command line.";
+ };
+
+ boot.consoleLogLevel = mkOption {
+ type = types.int;
+ default = 4;
+ description = ''
+ The kernel console <literal>loglevel</literal>. All Kernel Messages with a log level smaller
+ than this setting will be printed to the console.
+ '';
+ };
+
+ boot.vesa = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to activate VESA video mode on boot.
+ '';
+ };
+
+ boot.extraModulePackages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample "[ config.boot.kernelPackages.nvidia_x11 ]";
+ description = "A list of additional packages supplying kernel modules.";
+ };
+
+ boot.kernelModules = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ The set of kernel modules to be loaded in the second stage of
+ the boot process. Note that modules that are needed to
+ mount the root file system should be added to
+ <option>boot.initrd.availableKernelModules</option> or
+ <option>boot.initrd.kernelModules</option>.
+ '';
+ };
+
+ boot.initrd.availableKernelModules = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "sata_nv" "ext3" ];
+ description = ''
+ The set of kernel modules in the initial ramdisk used during the
+ boot process. This set must include all modules necessary for
+ mounting the root device. That is, it should include modules
+ for the physical device (e.g., SCSI drivers) and for the file
+ system (e.g., ext3). The set specified here is automatically
+ closed under the module dependency relation, i.e., all
+ dependencies of the modules list here are included
+ automatically. The modules listed here are available in the
+ initrd, but are only loaded on demand (e.g., the ext3 module is
+ loaded automatically when an ext3 filesystem is mounted, and
+ modules for PCI devices are loaded when they match the PCI ID
+ of a device in your system). To force a module to be loaded,
+ include it in <option>boot.initrd.kernelModules</option>.
+ '';
+ };
+
+ boot.initrd.kernelModules = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "List of modules that are always loaded by the initrd.";
+ };
+
+ system.modulesTree = mkOption {
+ type = types.listOf types.path;
+ internal = true;
+ default = [];
+ description = ''
+ Tree of kernel modules. This includes the kernel, plus modules
+ built outside of the kernel. Combine these into a single tree of
+ symlinks because modprobe only supports one directory.
+ '';
+ # Convert the list of path to only one path.
+ apply = pkgs.aggregateModules;
+ };
+
+ system.requiredKernelConfig = mkOption {
+ default = [];
+ example = literalExample ''
+ with config.lib.kernelConfig; [
+ (isYes "MODULES")
+ (isEnabled "FB_CON_DECOR")
+ (isEnabled "BLK_DEV_INITRD")
+ ]
+ '';
+ internal = true;
+ type = types.listOf types.attrs;
+ description = ''
+ This option allows modules to specify the kernel config options that
+ must be set (or unset) for the module to work. Please use the
+ lib.kernelConfig functions to build list elements.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf (!config.boot.isContainer) {
+
+ system.build = { inherit kernel; };
+
+ system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages;
+
+ # Implement consoleLogLevel both in early boot and using sysctl
+ # (so you don't need to reboot to have changes take effect).
+ boot.kernelParams =
+ [ "loglevel=${toString config.boot.consoleLogLevel}" ] ++
+ optionals config.boot.vesa [ "vga=0x317" "nomodeset" ];
+
+ boot.kernel.sysctl."kernel.printk" = mkDefault config.boot.consoleLogLevel;
+
+ boot.kernelModules = [ "loop" "atkbd" ];
+
+ boot.initrd.availableKernelModules =
+ [ # Note: most of these (especially the SATA/PATA modules)
+ # shouldn't be included by default since nixos-generate-config
+ # detects them, but I'm keeping them for now for backwards
+ # compatibility.
+
+ # Some SATA/PATA stuff.
+ "ahci"
+ "sata_nv"
+ "sata_via"
+ "sata_sis"
+ "sata_uli"
+ "ata_piix"
+ "pata_marvell"
+
+ # Standard SCSI stuff.
+ "sd_mod"
+ "sr_mod"
+
+ # SD cards and internal eMMC drives.
+ "mmc_block"
+
+ # Support USB keyboards, in case the boot fails and we only have
+ # a USB keyboard, or for LUKS passphrase prompt.
+ "uhci_hcd"
+ "ehci_hcd"
+ "ehci_pci"
+ "ohci_hcd"
+ "ohci_pci"
+ "xhci_hcd"
+ "xhci_pci"
+ "usbhid"
+ "hid_generic" "hid_lenovo" "hid_apple" "hid_roccat"
+ "hid_logitech_hidpp" "hid_logitech_dj"
+
+ ] ++ optionals (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [
+ # Misc. x86 keyboard stuff.
+ "pcips2" "atkbd" "i8042"
+
+ # x86 RTC needed by the stage 2 init script.
+ "rtc_cmos"
+ ];
+
+ boot.initrd.kernelModules =
+ [ # For LVM.
+ "dm_mod"
+ ];
+
+ # The Linux kernel >= 2.6.27 provides firmware.
+ hardware.firmware = [ kernel ];
+
+ # Create /etc/modules-load.d/nixos.conf, which is read by
+ # systemd-modules-load.service to load required kernel modules.
+ environment.etc = singleton
+ { target = "modules-load.d/nixos.conf";
+ source = kernelModulesConf;
+ };
+
+ systemd.services.systemd-modules-load =
+ { wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ kernelModulesConf ];
+ serviceConfig =
+ { # Ignore failed module loads. Typically some of the
+ # modules in ‘boot.kernelModules’ are "nice to have but
+ # not required" (e.g. acpi-cpufreq), so we don't want to
+ # barf on those.
+ SuccessExitStatus = "0 1";
+ };
+ };
+
+ lib.kernelConfig = {
+ isYes = option: {
+ assertion = config: config.isYes option;
+ message = "CONFIG_${option} is not yes!";
+ configLine = "CONFIG_${option}=y";
+ };
+
+ isNo = option: {
+ assertion = config: config.isNo option;
+ message = "CONFIG_${option} is not no!";
+ configLine = "CONFIG_${option}=n";
+ };
+
+ isModule = option: {
+ assertion = config: config.isModule option;
+ message = "CONFIG_${option} is not built as a module!";
+ configLine = "CONFIG_${option}=m";
+ };
+
+ ### Usually you will just want to use these two
+ # True if yes or module
+ isEnabled = option: {
+ assertion = config: config.isEnabled option;
+ message = "CONFIG_${option} is not enabled!";
+ configLine = "CONFIG_${option}=y";
+ };
+
+ # True if no or omitted
+ isDisabled = option: {
+ assertion = config: config.isDisabled option;
+ message = "CONFIG_${option} is not disabled!";
+ configLine = "CONFIG_${option}=n";
+ };
+ };
+
+ # The config options that all modules can depend upon
+ system.requiredKernelConfig = with config.lib.kernelConfig; [
+ # !!! Should this really be needed?
+ (isYes "MODULES")
+ (isYes "BINFMT_ELF")
+ ] ++ (optional (randstructSeed != "") (isYes "GCC_PLUGIN_RANDSTRUCT"));
+
+ # nixpkgs kernels are assumed to have all required features
+ assertions = if config.boot.kernelPackages.kernel ? features then [] else
+ let cfg = config.boot.kernelPackages.kernel.config; in map (attrs:
+ { assertion = attrs.assertion cfg; inherit (attrs) message; }
+ ) config.system.requiredKernelConfig;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/kernel_config.nix b/nixpkgs/nixos/modules/system/boot/kernel_config.nix
new file mode 100644
index 00000000000..a316782dfc5
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/kernel_config.nix
@@ -0,0 +1,136 @@
+{ 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;
+ 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/nixpkgs/nixos/modules/system/boot/kexec.nix b/nixpkgs/nixos/modules/system/boot/kexec.nix
new file mode 100644
index 00000000000..27a8e0217c5
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/kexec.nix
@@ -0,0 +1,32 @@
+{ pkgs, lib, ... }:
+
+{
+ config = lib.mkIf (lib.any (lib.meta.platformMatch pkgs.stdenv.hostPlatform) pkgs.kexectools.meta.platforms) {
+ environment.systemPackages = [ pkgs.kexectools ];
+
+ systemd.services.prepare-kexec =
+ { description = "Preparation for kexec";
+ wantedBy = [ "kexec.target" ];
+ before = [ "systemd-kexec.service" ];
+ unitConfig.DefaultDependencies = false;
+ serviceConfig.Type = "oneshot";
+ path = [ pkgs.kexectools ];
+ script =
+ ''
+ # Don't load the current system profile if we already have a kernel loaded
+ if [[ 1 = "$(</sys/kernel/kexec_loaded)" ]] ; then
+ echo "kexec kernel has already been loaded, prepare-kexec skipped"
+ exit 0
+ fi
+
+ p=$(readlink -f /nix/var/nix/profiles/system)
+ if ! [[ -d $p ]]; then
+ echo "Could not find system profile for prepare-kexec"
+ exit 1
+ fi
+ echo "Loading NixOS system via kexec."
+ exec kexec --load $p/kernel --initrd=$p/initrd --append="$(cat $p/kernel-params) init=$p/init"
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/efi.nix b/nixpkgs/nixos/modules/system/boot/loader/efi.nix
new file mode 100644
index 00000000000..6043c904c45
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/efi.nix
@@ -0,0 +1,20 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ options.boot.loader.efi = {
+
+ canTouchEfiVariables = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether the installation process is allowed to modify EFI boot variables.";
+ };
+
+ efiSysMountPoint = mkOption {
+ default = "/boot";
+ type = types.str;
+ description = "Where the EFI System Partition is mounted.";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh
new file mode 100644
index 00000000000..e723b9eb7cb
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh
@@ -0,0 +1,106 @@
+#! @bash@/bin/sh -e
+
+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: generations-dir-builder.sh <DEFAULT-CONFIG>"
+ exit 1
+fi
+
+echo "updating the boot generations directory..."
+
+mkdir -p /boot
+
+rm -Rf /boot/system* || true
+
+target=/boot/grub/menu.lst
+tmp=$target.tmp
+
+# Convert a path to a file in the Nix store such as
+# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
+cleanName() {
+ local path="$1"
+ echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
+}
+
+# Copy a file from the Nix store to /boot/kernels.
+declare -A filesCopied
+
+copyToKernelsDir() {
+ local src="$1"
+ local dst="/boot/kernels/$(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.
+ if ! test -e $dst; then
+ local dstTmp=$dst.tmp.$$
+ cp $src $dstTmp
+ mv $dstTmp $dst
+ fi
+ filesCopied[$dst]=1
+ result=$dst
+}
+
+
+# Copy its kernel and initrd to /boot/kernels.
+addEntry() {
+ local path="$1"
+ local generation="$2"
+ local outdir=/boot/system-$generation
+
+ if ! test -e $path/kernel -a -e $path/initrd; then
+ return
+ fi
+
+ local kernel=$(readlink -f $path/kernel)
+ local initrd=$(readlink -f $path/initrd)
+
+ if test -n "@copyKernels@"; then
+ copyToKernelsDir $kernel; kernel=$result
+ copyToKernelsDir $initrd; initrd=$result
+ fi
+
+ mkdir -p $outdir
+ ln -sf $(readlink -f $path) $outdir/system
+ ln -sf $(readlink -f $path/init) $outdir/init
+ ln -sf $initrd $outdir/initrd
+ ln -sf $kernel $outdir/kernel
+
+ if test $(readlink -f "$path") = "$default"; then
+ cp "$kernel" /boot/nixos-kernel
+ cp "$initrd" /boot/nixos-initrd
+ cp "$(readlink -f "$path/init")" /boot/nixos-init
+
+ mkdir -p /boot/default
+ # ln -sfT: overrides target even if it exists.
+ ln -sfT $(readlink -f $path) /boot/default/system
+ ln -sfT $(readlink -f $path/init) /boot/default/init
+ ln -sfT $initrd /boot/default/initrd
+ ln -sfT $kernel /boot/default/kernel
+ fi
+}
+
+if test -n "@copyKernels@"; then
+ mkdir -p /boot/kernels
+fi
+
+# Add all generations of the system profile to the menu, in reverse
+# (most recent to least recent) order.
+for generation in $(
+ (cd /nix/var/nix/profiles && ls -d system-*-link) \
+ | sed 's/system-\([0-9]\+\)-link/\1/' \
+ | sort -n -r); do
+ link=/nix/var/nix/profiles/system-$generation-link
+ addEntry $link $generation
+done
+
+# Remove obsolete files from /boot/kernels.
+for fn in /boot/kernels/*; do
+ if ! test "${filesCopied[$fn]}" = 1; then
+ rm -vf -- "$fn"
+ fi
+done
diff --git a/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
new file mode 100644
index 00000000000..2d27611946e
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ generationsDirBuilder = pkgs.substituteAll {
+ src = ./generations-dir-builder.sh;
+ isExecutable = true;
+ inherit (pkgs) bash;
+ path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+ inherit (config.boot.loader.generationsDir) copyKernels;
+ };
+
+ # Temporary check, for nixos to cope both with nixpkgs stdenv-updates and trunk
+ inherit (pkgs.stdenv.hostPlatform) platform;
+
+in
+
+{
+ options = {
+
+ boot.loader.generationsDir = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to create symlinks to the system generations under
+ <literal>/boot</literal>. When enabled,
+ <literal>/boot/default/kernel</literal>,
+ <literal>/boot/default/initrd</literal>, etc., are updated to
+ point to the current generation's kernel image, initial RAM
+ disk, and other bootstrap files.
+
+ This optional is not necessary with boot loaders such as GNU GRUB
+ for which the menu is updated to point to the latest bootstrap
+ files. However, it is needed for U-Boot on platforms where the
+ boot command line is stored in flash memory rather than in a
+ menu file.
+ '';
+ };
+
+ copyKernels = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether copy the necessary boot files into /boot, so
+ /nix/store is not needed by the boot loader.
+ '';
+ };
+
+ };
+
+ };
+
+
+ config = mkIf config.boot.loader.generationsDir.enable {
+
+ system.build.installBootLoader = generationsDirBuilder;
+ system.boot.loader.id = "generationsDir";
+ system.boot.loader.kernelFile = platform.kernelTarget;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
new file mode 100644
index 00000000000..af39c7bb684
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ blCfg = config.boot.loader;
+ cfg = blCfg.generic-extlinux-compatible;
+
+ timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
+
+ builder = import ./extlinux-conf-builder.nix { inherit pkgs; };
+in
+{
+ options = {
+ boot.loader.generic-extlinux-compatible = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to generate an extlinux-compatible configuration file
+ under <literal>/boot/extlinux.conf</literal>. For instance,
+ U-Boot's generic distro boot support uses this file format.
+
+ See <link xlink:href="http://git.denx.de/?p=u-boot.git;a=blob;f=doc/README.distro;hb=refs/heads/master">U-boot's documentation</link>
+ for more information.
+ '';
+ };
+
+ configurationLimit = mkOption {
+ default = 20;
+ example = 10;
+ type = types.int;
+ description = ''
+ Maximum number of configurations in the boot menu.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ system.build.installBootLoader = "${builder} -g ${toString cfg.configurationLimit} -t ${timeoutStr} -c";
+ system.boot.loader.id = "generic-extlinux-compatible";
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
new file mode 100644
index 00000000000..576a07c1d27
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
@@ -0,0 +1,8 @@
+{ pkgs }:
+
+pkgs.substituteAll {
+ src = ./extlinux-conf-builder.sh;
+ isExecutable = true;
+ path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+ inherit (pkgs) bash;
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
new file mode 100644
index 00000000000..0092ee92b62
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
@@ -0,0 +1,140 @@
+#! @bash@/bin/sh -e
+
+shopt -s nullglob
+
+export PATH=/empty
+for i in @path@; do PATH=$PATH:$i/bin; done
+
+usage() {
+ echo "usage: $0 -t <timeout> -c <path-to-default-configuration> [-d <boot-dir>] [-g <num-generations>]" >&2
+ exit 1
+}
+
+timeout= # Timeout in centiseconds
+default= # Default configuration
+target=/boot # Target directory
+numGenerations=0 # Number of other generations to include in the menu
+
+while getopts "t:c:d:g:" opt; do
+ case "$opt" in
+ t) # U-Boot interprets '0' as infinite and negative as instant boot
+ if [ "$OPTARG" -lt 0 ]; then
+ timeout=0
+ elif [ "$OPTARG" = 0 ]; then
+ timeout=-10
+ else
+ timeout=$((OPTARG * 10))
+ fi
+ ;;
+ c) default="$OPTARG" ;;
+ d) target="$OPTARG" ;;
+ g) numGenerations="$OPTARG" ;;
+ \?) usage ;;
+ esac
+done
+
+[ "$timeout" = "" -o "$default" = "" ] && usage
+
+mkdir -p $target/nixos
+mkdir -p $target/extlinux
+
+# Convert a path to a file in the Nix store such as
+# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
+cleanName() {
+ local path="$1"
+ echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
+}
+
+# Copy a file from the Nix store to $target/nixos.
+declare -A filesCopied
+
+copyToKernelsDir() {
+ local src=$(readlink -f "$1")
+ local dst="$target/nixos/$(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.
+ if ! test -e $dst; then
+ local dstTmp=$dst.tmp.$$
+ cp -r $src $dstTmp
+ mv $dstTmp $dst
+ fi
+ filesCopied[$dst]=1
+ result=$dst
+}
+
+# Copy its kernel, initrd and dtbs to $target/nixos, and echo out an
+# extlinux menu entry
+addEntry() {
+ local path=$(readlink -f "$1")
+ local tag="$2" # Generation number or 'default'
+
+ if ! test -e $path/kernel -a -e $path/initrd; then
+ return
+ fi
+
+ copyToKernelsDir "$path/kernel"; kernel=$result
+ copyToKernelsDir "$path/initrd"; initrd=$result
+ dtbDir=$(readlink -m "$path/dtbs")
+ if [ -e "$dtbDir" ]; then
+ copyToKernelsDir "$dtbDir"; dtbs=$result
+ fi
+
+ timestampEpoch=$(stat -L -c '%Z' $path)
+
+ timestamp=$(date "+%Y-%m-%d %H:%M" -d @$timestampEpoch)
+ nixosLabel="$(cat $path/nixos-version)"
+ extraParams="$(cat $path/kernel-params)"
+
+ echo
+ echo "LABEL nixos-$tag"
+ if [ "$tag" = "default" ]; then
+ echo " MENU LABEL NixOS - Default"
+ else
+ echo " MENU LABEL NixOS - Configuration $tag ($timestamp - $nixosLabel)"
+ fi
+ echo " LINUX ../nixos/$(basename $kernel)"
+ echo " INITRD ../nixos/$(basename $initrd)"
+ if [ -d "$dtbDir" ]; then
+ echo " FDTDIR ../nixos/$(basename $dtbs)"
+ fi
+ echo " APPEND systemConfig=$path init=$path/init $extraParams"
+}
+
+tmpFile="$target/extlinux/extlinux.conf.tmp.$$"
+
+cat > $tmpFile <<EOF
+# Generated file, all changes will be lost on nixos-rebuild!
+
+# Change this to e.g. nixos-42 to temporarily boot to an older configuration.
+DEFAULT nixos-default
+
+MENU TITLE ------------------------------------------------------------
+TIMEOUT $timeout
+EOF
+
+addEntry $default default >> $tmpFile
+
+if [ "$numGenerations" -gt 0 ]; then
+ # Add up to $numGenerations generations of the system profile to the menu,
+ # in reverse (most recent to least recent) order.
+ for generation in $(
+ (cd /nix/var/nix/profiles && ls -d system-*-link) \
+ | sed 's/system-\([0-9]\+\)-link/\1/' \
+ | sort -n -r \
+ | head -n $numGenerations); do
+ link=/nix/var/nix/profiles/system-$generation-link
+ addEntry $link $generation
+ done >> $tmpFile
+fi
+
+mv -f $tmpFile $target/extlinux/extlinux.conf
+
+# Remove obsolete files from $target/nixos.
+for fn in $target/nixos/*; do
+ if ! test "${filesCopied[$fn]}" = 1; then
+ echo "Removing no longer needed boot file: $fn"
+ chmod +w -- "$fn"
+ rm -rf -- "$fn"
+ fi
+done
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
new file mode 100644
index 00000000000..e13f0421d38
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
@@ -0,0 +1,706 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.boot.loader.grub;
+
+ efi = config.boot.loader.efi;
+
+ grubPkgs =
+ # Package set of targeted architecture
+ if cfg.forcei686 then pkgs.pkgsi686Linux else pkgs;
+
+ realGrub = if cfg.version == 1 then grubPkgs.grub
+ else if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; }
+ else if cfg.trustedBoot.enable
+ then if cfg.trustedBoot.isHPLaptop
+ then grubPkgs.trustedGrub-for-HP
+ else grubPkgs.trustedGrub
+ else grubPkgs.grub2;
+
+ grub =
+ # Don't include GRUB if we're only generating a GRUB menu (e.g.,
+ # in EC2 instances).
+ if cfg.devices == ["nodev"]
+ then null
+ else realGrub;
+
+ grubEfi =
+ # EFI version of Grub v2
+ if cfg.efiSupport && (cfg.version == 2)
+ then realGrub.override { efiSupport = cfg.efiSupport; }
+ else null;
+
+ f = x: if x == null then "" else "" + x;
+
+ grubConfig = args:
+ let
+ efiSysMountPoint = if args.efiSysMountPoint == null then args.path else args.efiSysMountPoint;
+ efiSysMountPoint' = replaceChars [ "/" ] [ "-" ] efiSysMountPoint;
+ in
+ pkgs.writeText "grub-config.xml" (builtins.toXML
+ { splashImage = f cfg.splashImage;
+ splashMode = f cfg.splashMode;
+ backgroundColor = f cfg.backgroundColor;
+ grub = f grub;
+ grubTarget = f (grub.grubTarget or "");
+ shell = "${pkgs.runtimeShell}";
+ fullName = (builtins.parseDrvName realGrub.name).name;
+ fullVersion = (builtins.parseDrvName realGrub.name).version;
+ grubEfi = f grubEfi;
+ grubTargetEfi = if cfg.efiSupport && (cfg.version == 2) then f (grubEfi.grubTarget or "") else "";
+ bootPath = args.path;
+ storePath = config.boot.loader.grub.storePath;
+ bootloaderId = if args.efiBootloaderId == null then "NixOS${efiSysMountPoint'}" else args.efiBootloaderId;
+ timeout = if config.boot.loader.timeout == null then -1 else config.boot.loader.timeout;
+ inherit efiSysMountPoint;
+ inherit (args) devices;
+ inherit (efi) canTouchEfiVariables;
+ inherit (cfg)
+ version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
+ extraEntriesBeforeNixOS extraPrepareConfig extraInitrd configurationLimit copyKernels
+ default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios;
+ path = with pkgs; makeBinPath (
+ [ coreutils gnused gnugrep findutils diffutils btrfs-progs utillinux mdadm ]
+ ++ optional (cfg.efiSupport && (cfg.version == 2)) efibootmgr
+ ++ optionals cfg.useOSProber [ busybox os-prober ]);
+ font = if cfg.font == null then ""
+ else (if lib.last (lib.splitString "." cfg.font) == "pf2"
+ then cfg.font
+ else "${convertedFont}");
+ });
+
+ bootDeviceCounters = fold (device: attr: attr // { ${device} = (attr.${device} or 0) + 1; }) {}
+ (concatMap (args: args.devices) cfg.mirroredBoots);
+
+ convertedFont = (pkgs.runCommand "grub-font-converted.pf2" {}
+ (builtins.concatStringsSep " "
+ ([ "${realGrub}/bin/grub-mkfont"
+ cfg.font
+ "--output" "$out"
+ ] ++ (optional (cfg.fontSize!=null) "--size ${toString cfg.fontSize}")))
+ );
+
+ defaultSplash = "${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bootloader.png";
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ boot.loader.grub = {
+
+ enable = mkOption {
+ default = !config.boot.isContainer;
+ type = types.bool;
+ description = ''
+ Whether to enable the GNU GRUB boot loader.
+ '';
+ };
+
+ version = mkOption {
+ default = 2;
+ example = 1;
+ type = types.int;
+ description = ''
+ The version of GRUB to use: <literal>1</literal> for GRUB
+ Legacy (versions 0.9x), or <literal>2</literal> (the
+ default) for GRUB 2.
+ '';
+ };
+
+ device = mkOption {
+ default = "";
+ example = "/dev/disk/by-id/wwn-0x500001234567890a";
+ type = types.str;
+ description = ''
+ The device on which the GRUB boot loader will be installed.
+ The special value <literal>nodev</literal> means that a GRUB
+ boot menu will be generated, but GRUB itself will not
+ actually be installed. To install GRUB on multiple devices,
+ use <literal>boot.loader.grub.devices</literal>.
+ '';
+ };
+
+ devices = mkOption {
+ default = [];
+ example = [ "/dev/disk/by-id/wwn-0x500001234567890a" ];
+ type = types.listOf types.str;
+ description = ''
+ The devices on which the boot loader, GRUB, will be
+ installed. Can be used instead of <literal>device</literal> to
+ install GRUB onto multiple devices.
+ '';
+ };
+
+ mirroredBoots = mkOption {
+ default = [ ];
+ example = [
+ { path = "/boot1"; devices = [ "/dev/disk/by-id/wwn-0x500001234567890a" ]; }
+ { path = "/boot2"; devices = [ "/dev/disk/by-id/wwn-0x500009876543210a" ]; }
+ ];
+ description = ''
+ Mirror the boot configuration to multiple partitions and install grub
+ to the respective devices corresponding to those partitions.
+ '';
+
+ type = with types; listOf (submodule {
+ options = {
+
+ path = mkOption {
+ example = "/boot1";
+ type = types.str;
+ description = ''
+ The path to the boot directory where GRUB will be written. Generally
+ this boot path should double as an EFI path.
+ '';
+ };
+
+ efiSysMountPoint = mkOption {
+ default = null;
+ example = "/boot1/efi";
+ type = types.nullOr types.str;
+ description = ''
+ The path to the efi system mount point. Usually this is the same
+ partition as the above path and can be left as null.
+ '';
+ };
+
+ efiBootloaderId = mkOption {
+ default = null;
+ example = "NixOS-fsid";
+ type = types.nullOr types.str;
+ description = ''
+ The id of the bootloader to store in efi nvram.
+ The default is to name it NixOS and append the path or efiSysMountPoint.
+ This is only used if <literal>boot.loader.efi.canTouchEfiVariables</literal> is true.
+ '';
+ };
+
+ devices = mkOption {
+ default = [ ];
+ example = [ "/dev/disk/by-id/wwn-0x500001234567890a" "/dev/disk/by-id/wwn-0x500009876543210a" ];
+ type = types.listOf types.str;
+ description = ''
+ The path to the devices which will have the GRUB MBR written.
+ Note these are typically device paths and not paths to partitions.
+ '';
+ };
+
+ };
+ });
+ };
+
+ configurationName = mkOption {
+ default = "";
+ example = "Stable 2.6.21";
+ type = types.str;
+ description = ''
+ GRUB entry name instead of default.
+ '';
+ };
+
+ storePath = mkOption {
+ default = "/nix/store";
+ type = types.str;
+ description = ''
+ Path to the Nix store when looking for kernels at boot.
+ Only makes sense when copyKernels is false.
+ '';
+ };
+
+ extraPrepareConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Additional bash commands to be run at the script that
+ prepares the GRUB menu entries.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ example = "serial; terminal_output.serial";
+ type = types.lines;
+ description = ''
+ Additional GRUB commands inserted in the configuration file
+ just before the menu entries.
+ '';
+ };
+
+ extraPerEntryConfig = mkOption {
+ default = "";
+ example = "root (hd0)";
+ type = types.lines;
+ description = ''
+ Additional GRUB commands inserted in the configuration file
+ at the start of each NixOS menu entry.
+ '';
+ };
+
+ extraEntries = mkOption {
+ default = "";
+ type = types.lines;
+ example = ''
+ # GRUB 1 example (not GRUB 2 compatible)
+ title Windows
+ chainloader (hd0,1)+1
+
+ # GRUB 2 example
+ menuentry "Windows 7" {
+ chainloader (hd0,4)+1
+ }
+
+ # GRUB 2 with UEFI example, chainloading another distro
+ menuentry "Fedora" {
+ set root=(hd1,1)
+ chainloader /efi/fedora/grubx64.efi
+ }
+ '';
+ description = ''
+ Any additional entries you want added to the GRUB boot menu.
+ '';
+ };
+
+ extraEntriesBeforeNixOS = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether extraEntries are included before the default option.
+ '';
+ };
+
+ extraFiles = mkOption {
+ type = types.attrsOf types.path;
+ default = {};
+ example = literalExample ''
+ { "memtest.bin" = "''${pkgs.memtest86plus}/memtest.bin"; }
+ '';
+ description = ''
+ A set of files to be copied to <filename>/boot</filename>.
+ Each attribute name denotes the destination file name in
+ <filename>/boot</filename>, while the corresponding
+ attribute value specifies the source file.
+ '';
+ };
+
+ extraInitrd = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/boot/extra_initramfs.gz";
+ description = ''
+ The path to a second initramfs to be supplied to the kernel.
+ This ramfs will not be copied to the store, so that it can
+ contain secrets such as LUKS keyfiles or ssh keys.
+ This implies that rolling back to a previous configuration
+ won't rollback the state of this file.
+ '';
+ };
+
+ useOSProber = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If set to true, append entries for other OSs detected by os-prober.
+ '';
+ };
+
+ splashImage = mkOption {
+ type = types.nullOr types.path;
+ example = literalExample "./my-background.png";
+ description = ''
+ Background image used for GRUB.
+ Set to <literal>null</literal> to run GRUB in text mode.
+
+ <note><para>
+ For grub 1:
+ It must be a 640x480,
+ 14-colour image in XPM format, optionally compressed with
+ <command>gzip</command> or <command>bzip2</command>.
+ </para></note>
+
+ <note><para>
+ For grub 2:
+ File must be one of .png, .tga, .jpg, or .jpeg. JPEG images must
+ not be progressive.
+ The image will be scaled if necessary to fit the screen.
+ </para></note>
+ '';
+ };
+
+ backgroundColor = mkOption {
+ type = types.nullOr types.str;
+ example = "#7EBAE4";
+ default = null;
+ description = ''
+ Background color to be used for GRUB to fill the areas the image isn't filling.
+
+ <note><para>
+ This options has no effect for GRUB 1.
+ </para></note>
+ '';
+ };
+
+ splashMode = mkOption {
+ type = types.enum [ "normal" "stretch" ];
+ default = "stretch";
+ description = ''
+ Whether to stretch the image or show the image in the top-left corner unstretched.
+
+ <note><para>
+ This options has no effect for GRUB 1.
+ </para></note>
+ '';
+ };
+
+ font = mkOption {
+ type = types.nullOr types.path;
+ default = "${realGrub}/share/grub/unicode.pf2";
+ defaultText = ''"''${pkgs.grub2}/share/grub/unicode.pf2"'';
+ description = ''
+ Path to a TrueType, OpenType, or pf2 font to be used by Grub.
+ '';
+ };
+
+ fontSize = mkOption {
+ type = types.nullOr types.int;
+ example = literalExample 16;
+ default = null;
+ description = ''
+ Font size for the grub menu. Ignored unless <literal>font</literal>
+ is set to a ttf or otf font.
+ '';
+ };
+
+ gfxmodeEfi = mkOption {
+ default = "auto";
+ example = "1024x768";
+ type = types.str;
+ description = ''
+ The gfxmode to pass to GRUB when loading a graphical boot interface under EFI.
+ '';
+ };
+
+ gfxmodeBios = mkOption {
+ default = "1024x768";
+ example = "auto";
+ type = types.str;
+ description = ''
+ The gfxmode to pass to GRUB when loading a graphical boot interface under BIOS.
+ '';
+ };
+
+ gfxpayloadEfi = mkOption {
+ default = "keep";
+ example = "text";
+ type = types.str;
+ description = ''
+ The gfxpayload to pass to GRUB when loading a graphical boot interface under EFI.
+ '';
+ };
+
+ gfxpayloadBios = mkOption {
+ default = "text";
+ example = "keep";
+ type = types.str;
+ description = ''
+ The gfxpayload to pass to GRUB when loading a graphical boot interface under BIOS.
+ '';
+ };
+
+ configurationLimit = mkOption {
+ default = 100;
+ example = 120;
+ type = types.int;
+ description = ''
+ Maximum of configurations in boot menu. GRUB has problems when
+ there are too many entries.
+ '';
+ };
+
+ copyKernels = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether the GRUB menu builder should copy kernels and initial
+ ramdisks to /boot. This is done automatically if /boot is
+ on a different partition than /.
+ '';
+ };
+
+ default = mkOption {
+ default = "0";
+ type = types.either types.int types.str;
+ apply = toString;
+ description = ''
+ Index of the default menu item to be booted.
+ '';
+ };
+
+ fsIdentifier = mkOption {
+ default = "uuid";
+ type = types.enum [ "uuid" "label" "provided" ];
+ description = ''
+ Determines how GRUB will identify devices when generating the
+ configuration file. A value of uuid / label signifies that grub
+ will always resolve the uuid or label of the device before using
+ it in the configuration. A value of provided means that GRUB will
+ use the device name as show in <command>df</command> or
+ <command>mount</command>. Note, zfs zpools / datasets are ignored
+ and will always be mounted using their labels.
+ '';
+ };
+
+ zfsSupport = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether GRUB should be built against libzfs.
+ ZFS support is only available for GRUB v2.
+ This option is ignored for GRUB v1.
+ '';
+ };
+
+ efiSupport = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether GRUB should be built with EFI support.
+ EFI support is only available for GRUB v2.
+ This option is ignored for GRUB v1.
+ '';
+ };
+
+ efiInstallAsRemovable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to invoke <literal>grub-install</literal> with
+ <literal>--removable</literal>.</para>
+
+ <para>Unless you turn this on, GRUB will install itself somewhere in
+ <literal>boot.loader.efi.efiSysMountPoint</literal> (exactly where
+ depends on other config variables). If you've set
+ <literal>boot.loader.efi.canTouchEfiVariables</literal> *AND* you
+ are currently booted in UEFI mode, then GRUB will use
+ <literal>efibootmgr</literal> to modify the boot order in the
+ EFI variables of your firmware to include this location. If you are
+ *not* booted in UEFI mode at the time GRUB is being installed, the
+ NVRAM will not be modified, and your system will not find GRUB at
+ boot time. However, GRUB will still return success so you may miss
+ the warning that gets printed ("<literal>efibootmgr: EFI variables
+ are not supported on this system.</literal>").</para>
+
+ <para>If you turn this feature on, GRUB will install itself in a
+ special location within <literal>efiSysMountPoint</literal> (namely
+ <literal>EFI/boot/boot$arch.efi</literal>) which the firmwares
+ are hardcoded to try first, regardless of NVRAM EFI variables.</para>
+
+ <para>To summarize, turn this on if:
+ <itemizedlist>
+ <listitem><para>You are installing NixOS and want it to boot in UEFI mode,
+ but you are currently booted in legacy mode</para></listitem>
+ <listitem><para>You want to make a drive that will boot regardless of
+ the NVRAM state of the computer (like a USB "removable" drive)</para></listitem>
+ <listitem><para>You simply dislike the idea of depending on NVRAM
+ state to make your drive bootable</para></listitem>
+ </itemizedlist>
+ '';
+ };
+
+ enableCryptodisk = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable support for encrypted partitions. GRUB should automatically
+ unlock the correct encrypted partition and look for filesystems.
+ '';
+ };
+
+ forceInstall = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to try and forcibly install GRUB even if problems are
+ detected. It is not recommended to enable this unless you know what
+ you are doing.
+ '';
+ };
+
+ forcei686 = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to force the use of a ia32 boot loader on x64 systems. Required
+ to install and run NixOS on 64bit x86 systems with 32bit (U)EFI.
+ '';
+ };
+
+ trustedBoot = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable trusted boot. GRUB will measure all critical components during
+ the boot process to offer TCG (TPM) support.
+ '';
+ };
+
+ systemHasTPM = mkOption {
+ default = "";
+ example = "YES_TPM_is_activated";
+ type = types.str;
+ description = ''
+ Assertion that the target system has an activated TPM. It is a safety
+ check before allowing the activation of 'trustedBoot.enable'. TrustedBoot
+ WILL FAIL TO BOOT YOUR SYSTEM if no TPM is available.
+ '';
+ };
+
+ isHPLaptop = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Use a special version of TrustedGRUB that is needed by some HP laptops
+ and works only for the HP laptops.
+ '';
+ };
+
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkMerge [
+
+ { boot.loader.grub.splashImage = mkDefault (
+ if cfg.version == 1 then pkgs.fetchurl {
+ url = http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz;
+ sha256 = "14kqdx2lfqvh40h6fjjzqgff1mwk74dmbjvmqphi6azzra7z8d59";
+ }
+ # GRUB 1.97 doesn't support gzipped XPMs.
+ else defaultSplash);
+ }
+
+ (mkIf (cfg.splashImage == defaultSplash) {
+ boot.loader.grub.backgroundColor = mkDefault "#2F302F";
+ boot.loader.grub.splashMode = mkDefault "normal";
+ })
+
+ (mkIf cfg.enable {
+
+ boot.loader.grub.devices = optional (cfg.device != "") cfg.device;
+
+ boot.loader.grub.mirroredBoots = optionals (cfg.devices != [ ]) [
+ { path = "/boot"; inherit (cfg) devices; inherit (efi) efiSysMountPoint; }
+ ];
+
+ system.build.installBootLoader =
+ let
+ install-grub-pl = pkgs.substituteAll {
+ src = ./install-grub.pl;
+ inherit (pkgs) utillinux;
+ btrfsprogs = pkgs.btrfs-progs;
+ };
+ in pkgs.writeScript "install-grub.sh" (''
+ #!${pkgs.runtimeShell}
+ set -e
+ 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} $@
+ ''));
+
+ system.build.grub = grub;
+
+ # Common attribute for boot loaders so only one of them can be
+ # set at once.
+ system.boot.loader.id = "grub";
+
+ environment.systemPackages = optional (grub != null) grub;
+
+ boot.loader.grub.extraPrepareConfig =
+ concatStrings (mapAttrsToList (n: v: ''
+ ${pkgs.coreutils}/bin/cp -pf "${v}" "/boot/${n}"
+ '') config.boot.loader.grub.extraFiles);
+
+ assertions = [
+ {
+ assertion = !cfg.zfsSupport || cfg.version == 2;
+ message = "Only GRUB version 2 provides ZFS support";
+ }
+ {
+ assertion = cfg.mirroredBoots != [ ];
+ message = "You must set the option ‘boot.loader.grub.devices’ or "
+ + "'boot.loader.grub.mirroredBoots' to make the system bootable.";
+ }
+ {
+ assertion = cfg.efiSupport || all (c: c < 2) (mapAttrsToList (_: c: c) bootDeviceCounters);
+ message = "You cannot have duplicated devices in mirroredBoots";
+ }
+ {
+ assertion = !cfg.trustedBoot.enable || cfg.version == 2;
+ message = "Trusted GRUB is only available for GRUB 2";
+ }
+ {
+ assertion = !cfg.efiSupport || !cfg.trustedBoot.enable;
+ message = "Trusted GRUB does not have EFI support";
+ }
+ {
+ assertion = !cfg.zfsSupport || !cfg.trustedBoot.enable;
+ message = "Trusted GRUB does not have ZFS support";
+ }
+ {
+ assertion = !cfg.trustedBoot.enable || cfg.trustedBoot.systemHasTPM == "YES_TPM_is_activated";
+ message = "Trusted GRUB can break the system! Confirm that the system has an activated TPM by setting 'systemHasTPM'.";
+ }
+ {
+ assertion = cfg.efiInstallAsRemovable -> cfg.efiSupport;
+ message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn on boot.loader.grub.efiSupport";
+ }
+ {
+ assertion = cfg.efiInstallAsRemovable -> !config.boot.loader.efi.canTouchEfiVariables;
+ message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn off boot.loader.efi.canTouchEfiVariables";
+ }
+ ] ++ flip concatMap cfg.mirroredBoots (args: [
+ {
+ assertion = args.devices != [ ];
+ message = "A boot path cannot have an empty devices string in ${args.path}";
+ }
+ {
+ assertion = hasPrefix "/" args.path;
+ message = "Boot paths must be absolute, not ${args.path}";
+ }
+ {
+ assertion = if args.efiSysMountPoint == null then true else hasPrefix "/" args.efiSysMountPoint;
+ message = "EFI paths must be absolute, not ${args.efiSysMountPoint}";
+ }
+ ] ++ forEach args.devices (device: {
+ assertion = device == "nodev" || hasPrefix "/" device;
+ message = "GRUB devices must be absolute paths, not ${device} in ${args.path}";
+ }));
+ })
+
+ ];
+
+
+ imports =
+ [ (mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "")
+ (mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ])
+ (mkRenamedOptionModule [ "boot" "extraGrubEntries" ] [ "boot" "loader" "grub" "extraEntries" ])
+ (mkRenamedOptionModule [ "boot" "extraGrubEntriesBeforeNixos" ] [ "boot" "loader" "grub" "extraEntriesBeforeNixOS" ])
+ (mkRenamedOptionModule [ "boot" "grubDevice" ] [ "boot" "loader" "grub" "device" ])
+ (mkRenamedOptionModule [ "boot" "bootMount" ] [ "boot" "loader" "grub" "bootDevice" ])
+ (mkRenamedOptionModule [ "boot" "grubSplashImage" ] [ "boot" "loader" "grub" "splashImage" ])
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl
new file mode 100644
index 00000000000..a09c5dc4761
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -0,0 +1,637 @@
+use strict;
+use warnings;
+use Class::Struct;
+use XML::LibXML;
+use File::Basename;
+use File::Path;
+use File::stat;
+use File::Copy;
+use File::Slurp;
+use File::Temp;
+require List::Compare;
+use POSIX;
+use Cwd;
+
+# system.build.toplevel path
+my $defaultConfig = $ARGV[1] or die;
+
+# Grub config XML generated by grubConfig function in grub.nix
+my $dom = XML::LibXML->load_xml(location => $ARGV[0]);
+
+sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); }
+
+sub readFile {
+ my ($fn) = @_; local $/ = undef;
+ open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE;
+ local $/ = "\n"; chomp $s; return $s;
+}
+
+sub writeFile {
+ my ($fn, $s) = @_;
+ open FILE, ">$fn" or die "cannot create $fn: $!\n";
+ print FILE $s or die;
+ close FILE or die;
+}
+
+sub runCommand {
+ my ($cmd) = @_;
+ open FILE, "$cmd 2>/dev/null |" or die "Failed to execute: $cmd\n";
+ my @ret = <FILE>;
+ close FILE;
+ return ($?, @ret);
+}
+
+my $grub = get("grub");
+my $grubVersion = int(get("version"));
+my $grubTarget = get("grubTarget");
+my $extraConfig = get("extraConfig");
+my $extraPrepareConfig = get("extraPrepareConfig");
+my $extraPerEntryConfig = get("extraPerEntryConfig");
+my $extraEntries = get("extraEntries");
+my $extraEntriesBeforeNixOS = get("extraEntriesBeforeNixOS") eq "true";
+my $extraInitrd = get("extraInitrd");
+my $splashImage = get("splashImage");
+my $splashMode = get("splashMode");
+my $backgroundColor = get("backgroundColor");
+my $configurationLimit = int(get("configurationLimit"));
+my $copyKernels = get("copyKernels") eq "true";
+my $timeout = int(get("timeout"));
+my $defaultEntry = get("default");
+my $fsIdentifier = get("fsIdentifier");
+my $grubEfi = get("grubEfi");
+my $grubTargetEfi = get("grubTargetEfi");
+my $bootPath = get("bootPath");
+my $storePath = get("storePath");
+my $canTouchEfiVariables = get("canTouchEfiVariables");
+my $efiInstallAsRemovable = get("efiInstallAsRemovable");
+my $efiSysMountPoint = get("efiSysMountPoint");
+my $gfxmodeEfi = get("gfxmodeEfi");
+my $gfxmodeBios = get("gfxmodeBios");
+my $gfxpayloadEfi = get("gfxpayloadEfi");
+my $gfxpayloadBios = get("gfxpayloadBios");
+my $bootloaderId = get("bootloaderId");
+my $forceInstall = get("forceInstall");
+my $font = get("font");
+$ENV{'PATH'} = get("path");
+
+die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2;
+
+print STDERR "updating GRUB $grubVersion menu...\n";
+
+mkpath("$bootPath/grub", 0, 0700);
+
+# Discover whether the bootPath is on the same filesystem as / and
+# /nix/store. If not, then all kernels and initrds must be copied to
+# the bootPath.
+if (stat($bootPath)->dev != stat("/nix/store")->dev) {
+ $copyKernels = 1;
+}
+
+# Discover information about the location of the bootPath
+struct(Fs => {
+ device => '$',
+ type => '$',
+ mount => '$',
+});
+sub PathInMount {
+ my ($path, $mount) = @_;
+ my @splitMount = split /\//, $mount;
+ my @splitPath = split /\//, $path;
+ if ($#splitPath < $#splitMount) {
+ return 0;
+ }
+ for (my $i = 0; $i <= $#splitMount; $i++) {
+ if ($splitMount[$i] ne $splitPath[$i]) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+# Figure out what filesystem is used for the directory with init/initrd/kernel files
+sub GetFs {
+ my ($dir) = @_;
+ my $bestFs = Fs->new(device => "", type => "", mount => "");
+ foreach my $fs (read_file("/proc/self/mountinfo")) {
+ chomp $fs;
+ my @fields = split / /, $fs;
+ my $mountPoint = $fields[4];
+ next unless -d $mountPoint;
+ my @mountOptions = split /,/, $fields[5];
+
+ # Skip the optional fields.
+ my $n = 6; $n++ while $fields[$n] ne "-"; $n++;
+ my $fsType = $fields[$n];
+ my $device = $fields[$n + 1];
+ my @superOptions = split /,/, $fields[$n + 2];
+
+ # Skip the bind-mount on /nix/store.
+ next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions);
+ # Skip mount point generated by systemd-efi-boot-generator?
+ next if $fsType eq "autofs";
+
+ # Ensure this matches the intended directory
+ next unless PathInMount($dir, $mountPoint);
+
+ # Is it better than our current match?
+ if (length($mountPoint) > length($bestFs->mount)) {
+ $bestFs = Fs->new(device => $device, type => $fsType, mount => $mountPoint);
+ }
+ }
+ return $bestFs;
+}
+struct (Grub => {
+ path => '$',
+ search => '$',
+});
+my $driveid = 1;
+sub GrubFs {
+ my ($dir) = @_;
+ my $fs = GetFs($dir);
+ my $path = substr($dir, length($fs->mount));
+ if (substr($path, 0, 1) ne "/") {
+ $path = "/$path";
+ }
+ my $search = "";
+
+ if ($grubVersion > 1) {
+ # ZFS is completely separate logic as zpools are always identified by a label
+ # or custom UUID
+ if ($fs->type eq 'zfs') {
+ my $sid = index($fs->device, '/');
+
+ if ($sid < 0) {
+ $search = '--label ' . $fs->device;
+ $path = '/@' . $path;
+ } else {
+ $search = '--label ' . substr($fs->device, 0, $sid);
+ $path = '/' . substr($fs->device, $sid) . '/@' . $path;
+ }
+ } else {
+ my %types = ('uuid' => '--fs-uuid', 'label' => '--label');
+
+ if ($fsIdentifier eq 'provided') {
+ # If the provided dev is identifying the partition using a label or uuid,
+ # we should get the label / uuid and do a proper search
+ my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/;
+ if ($#matches > 1) {
+ die "Too many matched devices"
+ } elsif ($#matches == 1) {
+ $search = "$types{$matches[0]} $matches[1]"
+ }
+ } else {
+ # Determine the identifying type
+ $search = $types{$fsIdentifier} . ' ';
+
+ # Based on the type pull in the identifier from the system
+ my ($status, @devInfo) = runCommand("@utillinux@/bin/blkid -o export @{[$fs->device]}");
+ if ($status != 0) {
+ die "Failed to get blkid info (returned $status) for @{[$fs->mount]} on @{[$fs->device]}";
+ }
+ my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/;
+ if ($#matches != 0) {
+ die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n"
+ }
+ $search .= $matches[0];
+ }
+
+ # BTRFS is a special case in that we need to fix the referrenced path based on subvolumes
+ if ($fs->type eq 'btrfs') {
+ my ($status, @id_info) = runCommand("@btrfsprogs@/bin/btrfs subvol show @{[$fs->mount]}");
+ if ($status != 0) {
+ die "Failed to retrieve subvolume info for @{[$fs->mount]}\n";
+ }
+ my @ids = join("\n", @id_info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
+ if ($#ids > 0) {
+ die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n"
+ } elsif ($#ids == 0) {
+ my ($status, @path_info) = runCommand("@btrfsprogs@/bin/btrfs subvol list @{[$fs->mount]}");
+ if ($status != 0) {
+ die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n";
+ }
+ my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/;
+ if ($#paths > 0) {
+ die "Btrfs returned multiple paths for a single subvolume id, mountpoint @{[$fs->mount]}\n";
+ } elsif ($#paths != 0) {
+ die "Btrfs did not return a path for the subvolume at @{[$fs->mount]}\n";
+ }
+ $path = "/$paths[0]$path";
+ }
+ }
+ }
+ if (not $search eq "") {
+ $search = "search --set=drive$driveid " . $search;
+ $path = "(\$drive$driveid)$path";
+ $driveid += 1;
+ }
+ }
+ return Grub->new(path => $path, search => $search);
+}
+my $grubBoot = GrubFs($bootPath);
+my $grubStore;
+if ($copyKernels == 0) {
+ $grubStore = GrubFs($storePath);
+}
+my $extraInitrdPath;
+if ($extraInitrd) {
+ if (! -f $extraInitrd) {
+ print STDERR "Warning: the specified extraInitrd " . $extraInitrd . " doesn't exist. Your system won't boot without it.\n";
+ }
+ $extraInitrdPath = GrubFs($extraInitrd);
+}
+
+# Generate the header.
+my $conf .= "# Automatically generated. DO NOT EDIT THIS FILE!\n";
+
+if ($grubVersion == 1) {
+ $conf .= "
+ default $defaultEntry
+ timeout $timeout
+ ";
+ if ($splashImage) {
+ copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath\n";
+ $conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n";
+ }
+}
+
+else {
+ if ($copyKernels == 0) {
+ $conf .= "
+ " . $grubStore->search;
+ }
+ # FIXME: should use grub-mkconfig.
+ $conf .= "
+ " . $grubBoot->search . "
+ if [ -s \$prefix/grubenv ]; then
+ load_env
+ fi
+
+ # ‘grub-reboot’ sets a one-time saved entry, which we process here and
+ # then delete.
+ if [ \"\${next_entry}\" ]; then
+ set default=\"\${next_entry}\"
+ set next_entry=
+ save_env next_entry
+ set timeout=1
+ else
+ set default=$defaultEntry
+ set timeout=$timeout
+ fi
+
+ # Setup the graphics stack for bios and efi systems
+ if [ \"\${grub_platform}\" = \"efi\" ]; then
+ insmod efi_gop
+ insmod efi_uga
+ else
+ insmod vbe
+ fi
+ ";
+
+ if ($font) {
+ copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath\n";
+ $conf .= "
+ insmod font
+ if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
+ insmod gfxterm
+ if [ \"\${grub_platform}\" = \"efi\" ]; then
+ set gfxmode=$gfxmodeEfi
+ set gfxpayload=$gfxpayloadEfi
+ else
+ set gfxmode=$gfxmodeBios
+ set gfxpayload=$gfxpayloadBios
+ fi
+ terminal_output gfxterm
+ fi
+ ";
+ }
+ if ($splashImage) {
+ # Keeps the image's extension.
+ my ($filename, $dirs, $suffix) = fileparse($splashImage, qr"\..[^.]*$");
+ # The module for jpg is jpeg.
+ if ($suffix eq ".jpg") {
+ $suffix = ".jpeg";
+ }
+ if ($backgroundColor) {
+ $conf .= "
+ background_color '$backgroundColor'
+ ";
+ }
+ copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath\n";
+ $conf .= "
+ insmod " . substr($suffix, 1) . "
+ if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
+ set color_normal=white/black
+ set color_highlight=black/white
+ else
+ set menu_color_normal=cyan/blue
+ set menu_color_highlight=white/blue
+ fi
+ ";
+ }
+}
+
+$conf .= "$extraConfig\n";
+
+
+# Generate the menu entries.
+$conf .= "\n";
+
+my %copied;
+mkpath("$bootPath/kernels", 0, 0755) if $copyKernels;
+
+sub copyToKernelsDir {
+ my ($path) = @_;
+ return $grubStore->path . substr($path, length("/nix/store")) unless $copyKernels;
+ $path =~ /\/nix\/store\/(.*)/ or die;
+ my $name = $1; $name =~ s/\//-/g;
+ my $dst = "$bootPath/kernels/$name";
+ # 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.
+ if (! -e $dst) {
+ my $tmp = "$dst.tmp";
+ copy $path, $tmp or die "cannot copy $path to $tmp\n";
+ rename $tmp, $dst or die "cannot rename $tmp to $dst\n";
+ }
+ $copied{$dst} = 1;
+ return ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$name";
+}
+
+sub addEntry {
+ my ($name, $path) = @_;
+ return unless -e "$path/kernel" && -e "$path/initrd";
+
+ my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel"));
+ my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd"));
+ if ($extraInitrd) {
+ $initrd .= " " .$extraInitrdPath->path;
+ }
+ my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef;
+
+ # FIXME: $confName
+
+ my $kernelParams =
+ "systemConfig=" . Cwd::abs_path($path) . " " .
+ "init=" . Cwd::abs_path("$path/init") . " " .
+ readFile("$path/kernel-params");
+ my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : "";
+
+ if ($grubVersion == 1) {
+ $conf .= "title $name\n";
+ $conf .= " $extraPerEntryConfig\n" if $extraPerEntryConfig;
+ $conf .= " kernel $xen $xenParams\n" if $xen;
+ $conf .= " " . ($xen ? "module" : "kernel") . " $kernel $kernelParams\n";
+ $conf .= " " . ($xen ? "module" : "initrd") . " $initrd\n\n";
+ } else {
+ $conf .= "menuentry \"$name\" {\n";
+ $conf .= $grubBoot->search . "\n";
+ if ($copyKernels == 0) {
+ $conf .= $grubStore->search . "\n";
+ }
+ if ($extraInitrd) {
+ $conf .= $extraInitrdPath->search . "\n";
+ }
+ $conf .= " $extraPerEntryConfig\n" if $extraPerEntryConfig;
+ $conf .= " multiboot $xen $xenParams\n" if $xen;
+ $conf .= " " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n";
+ $conf .= " " . ($xen ? "module" : "initrd") . " $initrd\n";
+ $conf .= "}\n\n";
+ }
+}
+
+
+# Add default entries.
+$conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS;
+
+addEntry("NixOS - Default", $defaultConfig);
+
+$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
+
+# Find all the children of the current default configuration
+# Do not search for grand children
+my @links = sort (glob "$defaultConfig/fine-tune/*");
+foreach my $link (@links) {
+
+ my $entryName = "";
+
+ my $cfgName = readFile("$link/configuration-name");
+
+ my $date = strftime("%F", localtime(lstat($link)->mtime));
+ my $version =
+ -e "$link/nixos-version"
+ ? readFile("$link/nixos-version")
+ : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
+
+ if ($cfgName) {
+ $entryName = $cfgName;
+ } else {
+ $entryName = "($date - $version)";
+ }
+ addEntry("NixOS - $entryName", $link);
+}
+
+my $grubBootPath = $grubBoot->path;
+# extraEntries could refer to @bootRoot@, which we have to substitute
+$conf =~ s/\@bootRoot\@/$grubBootPath/g;
+
+# Emit submenus for all system profiles.
+sub addProfile {
+ my ($profile, $description) = @_;
+
+ # Add entries for all generations of this profile.
+ $conf .= "submenu \"$description\" {\n" if $grubVersion == 2;
+
+ sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; }
+
+ my @links = sort
+ { nrFromGen($b) <=> nrFromGen($a) }
+ (glob "$profile-*-link");
+
+ my $curEntry = 0;
+ foreach my $link (@links) {
+ last if $curEntry++ >= $configurationLimit;
+ if (! -e "$link/nixos-version") {
+ warn "skipping corrupt system profile entry ‘$link’\n";
+ next;
+ }
+ my $date = strftime("%F", localtime(lstat($link)->mtime));
+ my $version =
+ -e "$link/nixos-version"
+ ? readFile("$link/nixos-version")
+ : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
+ addEntry("NixOS - Configuration " . nrFromGen($link) . " ($date - $version)", $link);
+ }
+
+ $conf .= "}\n" if $grubVersion == 2;
+}
+
+addProfile "/nix/var/nix/profiles/system", "NixOS - All configurations";
+
+if ($grubVersion == 2) {
+ for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") {
+ my $name = basename($profile);
+ next unless $name =~ /^\w+$/;
+ addProfile $profile, "NixOS - Profile '$name'";
+ }
+}
+
+# Run extraPrepareConfig in sh
+if ($extraPrepareConfig ne "") {
+ system((get("shell"), "-c", $extraPrepareConfig));
+}
+
+# write the GRUB config.
+my $confFile = $grubVersion == 1 ? "$bootPath/grub/menu.lst" : "$bootPath/grub/grub.cfg";
+my $tmpFile = $confFile . ".tmp";
+writeFile($tmpFile, $conf);
+
+
+# check whether to install GRUB EFI or not
+sub getEfiTarget {
+ if ($grubVersion == 1) {
+ return "no"
+ } elsif (($grub ne "") && ($grubEfi ne "")) {
+ # EFI can only be installed when target is set;
+ # A target is also required then for non-EFI grub
+ if (($grubTarget eq "") || ($grubTargetEfi eq "")) { die }
+ else { return "both" }
+ } elsif (($grub ne "") && ($grubEfi eq "")) {
+ # TODO: It would be safer to disallow non-EFI grub installation if no taget is given.
+ # If no target is given, then grub auto-detects the target which can lead to errors.
+ # E.g. it seems as if grub would auto-detect a EFI target based on the availability
+ # of a EFI partition.
+ # However, it seems as auto-detection is currently relied on for non-x86_64 and non-i386
+ # architectures in NixOS. That would have to be fixed in the nixos modules first.
+ return "no"
+ } elsif (($grub eq "") && ($grubEfi ne "")) {
+ # EFI can only be installed when target is set;
+ if ($grubTargetEfi eq "") { die }
+ else {return "only" }
+ } else {
+ # prevent an installation if neither grub nor grubEfi is given
+ return "neither"
+ }
+}
+
+my $efiTarget = getEfiTarget();
+
+# Append entries detected by os-prober
+if (get("useOSProber") eq "true") {
+ my $targetpackage = ($efiTarget eq "no") ? $grub : $grubEfi;
+ system(get("shell"), "-c", "pkgdatadir=$targetpackage/share/grub $targetpackage/etc/grub.d/30_os-prober >> $tmpFile");
+}
+
+# Atomically switch to the new config
+rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile\n";
+
+
+# Remove obsolete files from $bootPath/kernels.
+foreach my $fn (glob "$bootPath/kernels/*") {
+ next if defined $copied{$fn};
+ print STDERR "removing obsolete file $fn\n";
+ unlink $fn;
+}
+
+
+#
+# Install GRUB if the parameters changed from the last time we installed it.
+#
+
+struct(GrubState => {
+ name => '$',
+ version => '$',
+ efi => '$',
+ devices => '$',
+ efiMountPoint => '$',
+});
+sub readGrubState {
+ my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "" );
+ open FILE, "<$bootPath/grub/state" or return $defaultGrubState;
+ local $/ = "\n";
+ my $name = <FILE>;
+ chomp($name);
+ my $version = <FILE>;
+ chomp($version);
+ my $efi = <FILE>;
+ chomp($efi);
+ my $devices = <FILE>;
+ chomp($devices);
+ my $efiMountPoint = <FILE>;
+ chomp($efiMountPoint);
+ close FILE;
+ my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint );
+ return $grubState
+}
+
+sub getDeviceTargets {
+ my @devices = ();
+ foreach my $dev ($dom->findnodes('/expr/attrs/attr[@name = "devices"]/list/string/@value')) {
+ $dev = $dev->findvalue(".") or die;
+ push(@devices, $dev);
+ }
+ return @devices;
+}
+my @deviceTargets = getDeviceTargets();
+my $prevGrubState = readGrubState();
+my @prevDeviceTargets = split/,/, $prevGrubState->devices;
+
+my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference());
+my $nameDiffer = get("fullName") ne $prevGrubState->name;
+my $versionDiffer = get("fullVersion") ne $prevGrubState->version;
+my $efiDiffer = $efiTarget ne $prevGrubState->efi;
+my $efiMountPointDiffer = $efiSysMountPoint ne $prevGrubState->efiMountPoint;
+if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1") {
+ warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER";
+ $ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1";
+}
+my $requireNewInstall = $devicesDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1");
+
+# install a symlink so that grub can detect the boot drive
+my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space";
+symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot";
+
+# install non-EFI GRUB
+if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
+ foreach my $dev (@deviceTargets) {
+ next if $dev eq "nodev";
+ print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
+ my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev));
+ if ($forceInstall eq "true") {
+ push @command, "--force";
+ }
+ if ($grubTarget ne "") {
+ push @command, "--target=$grubTarget";
+ }
+ (system @command) == 0 or die "$0: installation of GRUB on $dev failed\n";
+ }
+}
+
+
+# install EFI GRUB
+if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
+ print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n";
+ my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint");
+ if ($forceInstall eq "true") {
+ push @command, "--force";
+ }
+ if ($canTouchEfiVariables eq "true") {
+ push @command, "--bootloader-id=$bootloaderId";
+ } else {
+ push @command, "--no-nvram";
+ push @command, "--removable" if $efiInstallAsRemovable eq "true";
+ }
+
+ (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed\n";
+}
+
+
+# update GRUB state file
+if ($requireNewInstall != 0) {
+ open FILE, ">$bootPath/grub/state" or die "cannot create $bootPath/grub/state: $!\n";
+ print FILE get("fullName"), "\n" or die;
+ print FILE get("fullVersion"), "\n" or die;
+ print FILE $efiTarget, "\n" or die;
+ print FILE join( ",", @deviceTargets ), "\n" or die;
+ print FILE $efiSysMountPoint, "\n" or die;
+ close FILE or die;
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix
new file mode 100644
index 00000000000..249c2761934
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix
@@ -0,0 +1,64 @@
+# This module adds a scripted iPXE entry to the GRUB boot menu.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ scripts = builtins.attrNames config.boot.loader.grub.ipxe;
+
+ grubEntry = name:
+ ''
+ menuentry "iPXE - ${name}" {
+ linux16 @bootRoot@/ipxe.lkrn
+ initrd16 @bootRoot@/${name}.ipxe
+ }
+
+ '';
+
+ scriptFile = name:
+ let
+ value = builtins.getAttr name config.boot.loader.grub.ipxe;
+ in
+ if builtins.typeOf value == "path" then value
+ else builtins.toFile "${name}.ipxe" value;
+in
+{
+ options =
+ { boot.loader.grub.ipxe = mkOption {
+ type = types.attrsOf (types.either types.path types.str);
+ description =
+ ''
+ Set of iPXE scripts available for
+ booting from the GRUB boot menu.
+ '';
+ default = { };
+ example = literalExample ''
+ { demo = '''
+ #!ipxe
+ dhcp
+ chain http://boot.ipxe.org/demo/boot.php
+ ''';
+ }
+ '';
+ };
+ };
+
+ config = mkIf (builtins.length scripts != 0) {
+
+ boot.loader.grub.extraEntries =
+ if config.boot.loader.grub.version == 2 then
+ toString (map grubEntry scripts)
+ else
+ throw "iPXE is not supported with GRUB 1.";
+
+ boot.loader.grub.extraFiles =
+ { "ipxe.lkrn" = "${pkgs.ipxe}/ipxe.lkrn"; }
+ //
+ builtins.listToAttrs ( map
+ (name: { name = name+".ipxe"; value = scriptFile name; })
+ scripts
+ );
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix
new file mode 100644
index 00000000000..94e5a14174b
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix
@@ -0,0 +1,93 @@
+# This module adds Memtest86+ to the GRUB boot menu.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ memtest86 = pkgs.memtest86plus;
+ cfg = config.boot.loader.grub.memtest86;
+in
+
+{
+ options = {
+
+ boot.loader.grub.memtest86 = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Make Memtest86+, a memory testing program, available from the
+ GRUB boot menu.
+ '';
+ };
+
+ params = mkOption {
+ default = [];
+ example = [ "console=ttyS0,115200" ];
+ type = types.listOf types.str;
+ description = ''
+ Parameters added to the Memtest86+ command line. As of memtest86+ 5.01
+ the following list of (apparently undocumented) parameters are
+ accepted:
+
+ <itemizedlist>
+
+ <listitem>
+ <para><literal>console=...</literal>, set up a serial console.
+ Examples:
+ <literal>console=ttyS0</literal>,
+ <literal>console=ttyS0,9600</literal> or
+ <literal>console=ttyS0,115200n8</literal>.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>btrace</literal>, enable boot trace.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>maxcpus=N</literal>, limit number of CPUs.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>onepass</literal>, run one pass and exit if there
+ are no errors.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>tstlist=...</literal>, list of tests to run.
+ Example: <literal>0,1,2</literal>.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>cpumask=...</literal>, set a CPU mask, to select CPUs
+ to use for testing.</para>
+ </listitem>
+
+ </itemizedlist>
+
+ This list of command line options was obtained by reading the
+ Memtest86+ source code.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ boot.loader.grub.extraEntries =
+ if config.boot.loader.grub.version == 2 then
+ ''
+ menuentry "Memtest86+" {
+ linux16 @bootRoot@/memtest.bin ${toString cfg.params}
+ }
+ ''
+ else
+ throw "Memtest86+ is not supported with GRUB 1.";
+
+ boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin";
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
new file mode 100644
index 00000000000..08d4ab14c9c
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
@@ -0,0 +1,93 @@
+#! @bash@/bin/sh -e
+
+shopt -s nullglob
+
+export PATH=/empty
+for i in @path@; do PATH=$PATH:$i/bin; done
+
+if test $# -ne 1; then
+ echo "Usage: init-script-builder.sh DEFAULT-CONFIG"
+ exit 1
+fi
+
+defaultConfig="$1"
+
+
+[ "$(stat -f -c '%i' /)" = "$(stat -f -c '%i' /boot)" ] || {
+ # see grub-menu-builder.sh
+ echo "WARNING: /boot being on a different filesystem not supported by init-script-builder.sh"
+}
+
+
+
+target="/sbin/init"
+targetOther="/boot/init-other-configurations-contents.txt"
+
+tmp="$target.tmp"
+tmpOther="$targetOther.tmp"
+
+
+configurationCounter=0
+numAlienEntries=`cat <<EOF | egrep '^[[:space:]]*title' | wc -l
+@extraEntries@
+EOF`
+
+
+
+
+# Add an entry to $targetOther
+addEntry() {
+ local name="$1"
+ local path="$2"
+ local shortSuffix="$3"
+
+ configurationCounter=$((configurationCounter + 1))
+
+ local stage2=$path/init
+
+ content="$(
+ echo "#!/bin/sh"
+ echo "# $name"
+ echo "# created by init-script-builder.sh"
+ echo "export systemConfig=$(readlink -f $path)"
+ echo "exec $stage2"
+ )"
+
+ [ "$path" != "$defaultConfig" ] || {
+ echo "$content" > $tmp
+ echo "# older configurations: $targetOther" >> $tmp
+ chmod +x $tmp
+ }
+
+ echo -e "$content\n\n" >> $tmpOther
+}
+
+
+mkdir -p /boot /sbin
+
+addEntry "NixOS - Default" $defaultConfig ""
+
+# Add all generations of the system profile to the menu, in reverse
+# (most recent to least recent) order.
+for link in $((ls -d $defaultConfig/fine-tune/* ) | sort -n); do
+ date=$(stat --printf="%y\n" $link | sed 's/\..*//')
+ addEntry "NixOS - variation" $link ""
+done
+
+for generation in $(
+ (cd /nix/var/nix/profiles && ls -d system-*-link) \
+ | sed 's/system-\([0-9]\+\)-link/\1/' \
+ | sort -n -r); do
+ link=/nix/var/nix/profiles/system-$generation-link
+ date=$(stat --printf="%y\n" $link | sed 's/\..*//')
+ if [ -d $link/kernel ]; then
+ kernelVersion=$(cd $(dirname $(readlink -f $link/kernel))/lib/modules && echo *)
+ suffix="($date - $kernelVersion)"
+ else
+ suffix="($date)"
+ fi
+ addEntry "NixOS - Configuration $generation $suffix" $link "$generation ($date)"
+done
+
+mv $tmpOther $targetOther
+mv $tmp $target
diff --git a/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix
new file mode 100644
index 00000000000..374d9524ff1
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ initScriptBuilder = pkgs.substituteAll {
+ src = ./init-script-builder.sh;
+ isExecutable = true;
+ inherit (pkgs) bash;
+ path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ boot.loader.initScript = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Some systems require a /sbin/init script which is started.
+ Or having it makes starting NixOS easier.
+ This applies to some kind of hosting services and user mode linux.
+
+ Additionally this script will create
+ /boot/init-other-configurations-contents.txt containing
+ contents of remaining configurations. You can copy paste them into
+ /sbin/init manually running a rescue system or such.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.boot.loader.initScript.enable {
+
+ system.build.installBootLoader = initScriptBuilder;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/loader.nix b/nixpkgs/nixos/modules/system/boot/loader/loader.nix
new file mode 100644
index 00000000000..7fbda9ef0f5
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/loader.nix
@@ -0,0 +1,15 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ options = {
+ boot.loader.timeout = mkOption {
+ default = 5;
+ type = types.nullOr types.int;
+ description = ''
+ Timeout (in seconds) until loader boots the default menu item. Use null if the loader menu should be displayed indefinitely.
+ '';
+ };
+ };
+} \ No newline at end of file
diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix
new file mode 100644
index 00000000000..7eb52e3d021
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
new file mode 100644
index 00000000000..0fb07de10c0
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
@@ -0,0 +1,132 @@
+#! @bash@/bin/sh -e
+
+shopt -s nullglob
+
+export PATH=/empty
+for i in @path@; do PATH=$PATH:$i/bin; done
+
+usage() {
+ echo "usage: $0 -c <path-to-default-configuration> [-d <boot-dir>]" >&2
+ exit 1
+}
+
+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 $target/old
+
+# Convert a path to a file in the Nix store such as
+# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
+cleanName() {
+ local path="$1"
+ echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
+}
+
+# Copy a file from the Nix store to $target/kernels.
+declare -A filesCopied
+
+copyToKernelsDir() {
+ local src="$1"
+ 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.
+ if ! test -e $dst; then
+ local dstTmp=$dst.tmp.$$
+ cp $src $dstTmp
+ mv $dstTmp $dst
+ fi
+ filesCopied[$dst]=1
+ result=$dst
+}
+
+copyForced() {
+ local src="$1"
+ local dst="$2"
+ cp $src $dst.tmp
+ mv $dst.tmp $dst
+}
+
+outdir=$target/old
+mkdir -p $outdir || true
+
+# Copy its kernel and initrd to $target/old.
+addEntry() {
+ local path="$1"
+ local generation="$2"
+
+ if ! test -e $path/kernel -a -e $path/initrd; then
+ return
+ fi
+
+ local kernel=$(readlink -f $path/kernel)
+ local initrd=$(readlink -f $path/initrd)
+ local dtb_path=$(readlink -f $path/kernel-modules/dtbs)
+
+ if test -n "@copyKernels@"; then
+ copyToKernelsDir $kernel; kernel=$result
+ copyToKernelsDir $initrd; initrd=$result
+ fi
+
+ echo $(readlink -f $path) > $outdir/$generation-system
+ echo $(readlink -f $path/init) > $outdir/$generation-init
+ cp $path/kernel-params $outdir/$generation-cmdline.txt
+ echo $initrd > $outdir/$generation-initrd
+ echo $kernel > $outdir/$generation-kernel
+
+ 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")" $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 $(
+ (cd /nix/var/nix/profiles && ls -d system-*-link) \
+ | sed 's/system-\([0-9]\+\)-link/\1/' \
+ | sort -n -r); do
+ link=/nix/var/nix/profiles/system-$generation-link
+ addEntry $link $generation
+done
+
+# 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 config.txt
+copyForced @configTxt@ $target/config.txt
+
+# 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
+done
diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
new file mode 100644
index 00000000000..1c8354e5269
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.boot.loader.raspberryPi;
+
+ inherit (pkgs.stdenv.hostPlatform) platform;
+
+ 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} -c";
+
+ blCfg = config.boot.loader;
+ timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
+
+ isAarch64 = pkgs.stdenv.hostPlatform.isAarch64;
+ optional = pkgs.stdenv.lib.optionalString;
+
+ configTxt =
+ pkgs.writeText "config.txt" (''
+ # U-Boot used to need this to work, regardless of whether UART is actually used or not.
+ # TODO: check when/if this can be removed.
+ enable_uart=1
+
+ # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
+ # when attempting to show low-voltage or overtemperature warnings.
+ avoid_warnings=1
+ '' + optional isAarch64 ''
+ # Boot in 64-bit mode.
+ arm_64bit=1
+ '' + (if cfg.uboot.enable then ''
+ kernel=u-boot-rpi.bin
+ '' else ''
+ kernel=kernel.img
+ initramfs initrd followkernel
+ '') + optional (cfg.firmwareConfig != null) cfg.firmwareConfig);
+
+in
+
+{
+ options = {
+
+ boot.loader.raspberryPi = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to create files with the system generations in
+ <literal>/boot</literal>.
+ <literal>/boot/old</literal> will hold files from old generations.
+ '';
+ };
+
+ version = mkOption {
+ default = 2;
+ type = types.enum [ 0 1 2 3 ];
+ description = ''
+ '';
+ };
+
+ uboot = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable using uboot as bootmanager for the raspberry pi.
+ '';
+ };
+
+ configurationLimit = mkOption {
+ default = 20;
+ example = 10;
+ type = types.int;
+ description = ''
+ Maximum number of configurations in the boot menu.
+ '';
+ };
+
+ };
+
+ firmwareConfig = mkOption {
+ default = null;
+ type = types.nullOr types.lines;
+ description = ''
+ Extra options that will be appended to <literal>/boot/config.txt</literal> file.
+ For possible values, see: https://www.raspberrypi.org/documentation/configuration/config-txt/
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = singleton {
+ assertion = !pkgs.stdenv.hostPlatform.isAarch64 || cfg.version == 3;
+ message = "Only Raspberry Pi 3 supports aarch64.";
+ };
+
+ system.build.installBootLoader = builder;
+ system.boot.loader.id = "raspberrypi";
+ system.boot.loader.kernelFile = platform.kernelTarget;
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
new file mode 100644
index 00000000000..9d4f8a93d28
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
@@ -0,0 +1,35 @@
+{ pkgs, version, configTxt }:
+
+let
+ isAarch64 = pkgs.stdenv.hostPlatform.isAarch64;
+
+ uboot =
+ if version == 0 then
+ pkgs.ubootRaspberryPiZero
+ else if version == 1 then
+ pkgs.ubootRaspberryPi
+ else if version == 2 then
+ pkgs.ubootRaspberryPi2
+ else
+ if isAarch64 then
+ pkgs.ubootRaspberryPi3_64bit
+ else
+ pkgs.ubootRaspberryPi3_32bit;
+
+ extlinuxConfBuilder =
+ import ../generic-extlinux-compatible/extlinux-conf-builder.nix {
+ inherit pkgs;
+ };
+in
+pkgs.substituteAll {
+ src = ./uboot-builder.sh;
+ isExecutable = true;
+ inherit (pkgs) bash;
+ path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+ firmware = pkgs.raspberrypifw;
+ inherit uboot;
+ inherit configTxt;
+ inherit extlinuxConfBuilder;
+ inherit version;
+}
+
diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh
new file mode 100644
index 00000000000..ea591427179
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
new file mode 100644
index 00000000000..f48a085ce57
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -0,0 +1,240 @@
+#! @python3@/bin/python3 -B
+import argparse
+import shutil
+import os
+import sys
+import errno
+import subprocess
+import glob
+import tempfile
+import errno
+import warnings
+import ctypes
+libc = ctypes.CDLL("libc.so.6")
+import re
+import datetime
+import glob
+import os.path
+
+def copy_if_not_exists(source, dest):
+ if not os.path.exists(dest):
+ shutil.copyfile(source, dest)
+
+def system_dir(profile, generation):
+ if profile:
+ return "/nix/var/nix/profiles/system-profiles/%s-%d-link" % (profile, generation)
+ else:
+ return "/nix/var/nix/profiles/system-%d-link" % (generation)
+
+BOOT_ENTRY = """title NixOS{profile}
+version Generation {generation} {description}
+linux {kernel}
+initrd {initrd}
+options {kernel_params}
+"""
+
+# The boot loader entry for memtest86.
+#
+# TODO: This is hard-coded to use the 64-bit EFI app, but it could probably
+# be updated to use the 32-bit EFI app on 32-bit systems. The 32-bit EFI
+# app filename is BOOTIA32.efi.
+MEMTEST_BOOT_ENTRY = """title MemTest86
+efi /efi/memtest86/BOOTX64.efi
+"""
+
+def write_loader_conf(profile, generation):
+ with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
+ if "@timeout@" != "":
+ f.write("timeout @timeout@\n")
+ if profile:
+ f.write("default nixos-%s-generation-%d\n" % (profile, generation))
+ else:
+ f.write("default nixos-generation-%d\n" % (generation))
+ if not @editor@:
+ f.write("editor 0\n");
+ f.write("console-mode @consoleMode@\n");
+ os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
+
+def profile_path(profile, generation, name):
+ return os.readlink("%s/%s" % (system_dir(profile, generation), name))
+
+def copy_from_profile(profile, generation, name, dry_run=False):
+ store_file_path = profile_path(profile, generation, name)
+ suffix = os.path.basename(store_file_path)
+ store_dir = os.path.basename(os.path.dirname(store_file_path))
+ efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
+ if not dry_run:
+ copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
+ return efi_file_path
+
+def describe_generation(generation_dir):
+ try:
+ with open("%s/nixos-version" % generation_dir) as f:
+ nixos_version = f.read()
+ except IOError:
+ nixos_version = "Unknown"
+
+ kernel_dir = os.path.dirname(os.path.realpath("%s/kernel" % generation_dir))
+ module_dir = glob.glob("%s/lib/modules/*" % kernel_dir)[0]
+ kernel_version = os.path.basename(module_dir)
+
+ build_time = int(os.path.getctime(generation_dir))
+ build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
+
+ description = "NixOS {}, Linux Kernel {}, Built on {}".format(
+ nixos_version, kernel_version, build_date
+ )
+
+ return description
+
+def write_entry(profile, generation, machine_id):
+ kernel = copy_from_profile(profile, generation, "kernel")
+ initrd = copy_from_profile(profile, generation, "initrd")
+ try:
+ append_initrd_secrets = profile_path(profile, generation, "append-initrd-secrets")
+ subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)])
+ except FileNotFoundError:
+ pass
+ if profile:
+ entry_file = "@efiSysMountPoint@/loader/entries/nixos-%s-generation-%d.conf" % (profile, generation)
+ else:
+ entry_file = "@efiSysMountPoint@/loader/entries/nixos-generation-%d.conf" % (generation)
+ generation_dir = os.readlink(system_dir(profile, generation))
+ tmp_path = "%s.tmp" % (entry_file)
+ kernel_params = "systemConfig=%s init=%s/init " % (generation_dir, generation_dir)
+
+ with open("%s/kernel-params" % (generation_dir)) as params_file:
+ kernel_params = kernel_params + params_file.read()
+ with open(tmp_path, 'w') as f:
+ f.write(BOOT_ENTRY.format(profile=" [" + profile + "]" if profile else "",
+ generation=generation,
+ kernel=kernel,
+ initrd=initrd,
+ kernel_params=kernel_params,
+ description=describe_generation(generation_dir)))
+ if machine_id is not None:
+ f.write("machine-id %s\n" % machine_id)
+ os.rename(tmp_path, entry_file)
+
+def mkdir_p(path):
+ try:
+ os.makedirs(path)
+ except OSError as e:
+ if e.errno != errno.EEXIST or not os.path.isdir(path):
+ raise
+
+def get_generations(profile=None):
+ gen_list = subprocess.check_output([
+ "@nix@/bin/nix-env",
+ "--list-generations",
+ "-p",
+ "/nix/var/nix/profiles/%s" % ("system-profiles/" + profile if profile else "system"),
+ "--option", "build-users-group", ""],
+ universal_newlines=True)
+ gen_lines = gen_list.split('\n')
+ gen_lines.pop()
+
+ configurationLimit = @configurationLimit@
+ return [ (profile, int(line.split()[0])) for line in gen_lines ][-configurationLimit:]
+
+def remove_old_entries(gens):
+ rex_profile = re.compile("^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$")
+ rex_generation = re.compile("^@efiSysMountPoint@/loader/entries/nixos.*-generation-(.*)\.conf$")
+ known_paths = []
+ for gen in gens:
+ known_paths.append(copy_from_profile(*gen, "kernel", True))
+ known_paths.append(copy_from_profile(*gen, "initrd", True))
+ for path in glob.iglob("@efiSysMountPoint@/loader/entries/nixos*-generation-[1-9]*.conf"):
+ try:
+ if rex_profile.match(path):
+ prof = rex_profile.sub(r"\1", path)
+ else:
+ prof = "system"
+ gen = int(rex_generation.sub(r"\1", path))
+ if not (prof, gen) in gens:
+ os.unlink(path)
+ except ValueError:
+ pass
+ for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"):
+ if not path in known_paths and not os.path.isdir(path):
+ os.unlink(path)
+
+def get_profiles():
+ if os.path.isdir("/nix/var/nix/profiles/system-profiles/"):
+ return [x
+ for x in os.listdir("/nix/var/nix/profiles/system-profiles/")
+ if not x.endswith("-link")]
+ else:
+ return []
+
+def main():
+ parser = argparse.ArgumentParser(description='Update NixOS-related systemd-boot files')
+ parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default NixOS config to boot')
+ args = parser.parse_args()
+
+ try:
+ with open("/etc/machine-id") as machine_file:
+ machine_id = machine_file.readlines()[0]
+ except IOError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ # Since systemd version 232 a machine ID is required and it might not
+ # be there on newly installed systems, so let's generate one so that
+ # bootctl can find it and we can also pass it to write_entry() later.
+ cmd = ["@systemd@/bin/systemd-machine-id-setup", "--print"]
+ machine_id = subprocess.check_output(cmd).rstrip()
+
+ if os.getenv("NIXOS_INSTALL_GRUB") == "1":
+ warnings.warn("NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER", DeprecationWarning)
+ os.environ["NIXOS_INSTALL_BOOTLOADER"] = "1"
+
+ if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1":
+ # bootctl uses fopen() with modes "wxe" and fails if the file exists.
+ if os.path.exists("@efiSysMountPoint@/loader/loader.conf"):
+ os.unlink("@efiSysMountPoint@/loader/loader.conf")
+
+ if "@canTouchEfiVariables@" == "1":
+ subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "install"])
+ else:
+ subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "--no-variables", "install"])
+
+ mkdir_p("@efiSysMountPoint@/efi/nixos")
+ mkdir_p("@efiSysMountPoint@/loader/entries")
+
+ gens = get_generations()
+ for profile in get_profiles():
+ gens += get_generations(profile)
+ remove_old_entries(gens)
+ for gen in gens:
+ write_entry(*gen, machine_id)
+ if os.readlink(system_dir(*gen)) == args.default_config:
+ write_loader_conf(*gen)
+
+ memtest_entry_file = "@efiSysMountPoint@/loader/entries/memtest86.conf"
+ if os.path.exists(memtest_entry_file):
+ os.unlink(memtest_entry_file)
+ shutil.rmtree("@efiSysMountPoint@/efi/memtest86", ignore_errors=True)
+ if "@memtest86@" != "":
+ mkdir_p("@efiSysMountPoint@/efi/memtest86")
+ for path in glob.iglob("@memtest86@/*"):
+ if os.path.isdir(path):
+ shutil.copytree(path, os.path.join("@efiSysMountPoint@/efi/memtest86", os.path.basename(path)))
+ else:
+ shutil.copy(path, "@efiSysMountPoint@/efi/memtest86/")
+
+ memtest_entry_file = "@efiSysMountPoint@/loader/entries/memtest86.conf"
+ memtest_entry_file_tmp_path = "%s.tmp" % memtest_entry_file
+ with open(memtest_entry_file_tmp_path, 'w') as f:
+ f.write(MEMTEST_BOOT_ENTRY)
+ os.rename(memtest_entry_file_tmp_path, memtest_entry_file)
+
+ # Since fat32 provides little recovery facilities after a crash,
+ # it can leave the system in an unbootable state, when a crash/outage
+ # happens shortly after an update. To decrease the likelihood of this
+ # event sync the efi filesystem after each update.
+ rc = libc.syncfs(os.open("@efiSysMountPoint@", os.O_RDONLY))
+ if rc != 0:
+ print("could not sync @efiSysMountPoint@: {}".format(os.strerror(rc)), file=sys.stderr)
+
+if __name__ == '__main__':
+ main()
diff --git a/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
new file mode 100644
index 00000000000..22d459ceb04
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -0,0 +1,143 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.boot.loader.systemd-boot;
+
+ efi = config.boot.loader.efi;
+
+ gummibootBuilder = pkgs.substituteAll {
+ src = ./systemd-boot-builder.py;
+
+ isExecutable = true;
+
+ inherit (pkgs) python3;
+
+ systemd = config.systemd.package;
+
+ nix = config.nix.package.out;
+
+ timeout = if config.boot.loader.timeout != null then config.boot.loader.timeout else "";
+
+ editor = if cfg.editor then "True" else "False";
+
+ configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit;
+
+ inherit (cfg) consoleMode;
+
+ inherit (efi) efiSysMountPoint canTouchEfiVariables;
+
+ memtest86 = if cfg.memtest86.enable then pkgs.memtest86-efi else "";
+ };
+in {
+
+ imports =
+ [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ])
+ ];
+
+ options.boot.loader.systemd-boot = {
+ enable = mkOption {
+ default = false;
+
+ type = types.bool;
+
+ description = "Whether to enable the systemd-boot (formerly gummiboot) EFI boot manager";
+ };
+
+ editor = mkOption {
+ default = true;
+
+ type = types.bool;
+
+ description = ''
+ Whether to allow editing the kernel command-line before
+ boot. It is recommended to set this to false, as it allows
+ gaining root access by passing init=/bin/sh as a kernel
+ parameter. However, it is enabled by default for backwards
+ compatibility.
+ '';
+ };
+
+ configurationLimit = mkOption {
+ default = null;
+ example = 120;
+ type = types.nullOr types.int;
+ description = ''
+ Maximum number of latest generations in the boot menu.
+ Useful to prevent boot partition running out of disk space.
+
+ <literal>null</literal> means no limit i.e. all generations
+ that were not garbage collected yet.
+ '';
+ };
+
+ consoleMode = mkOption {
+ default = "keep";
+
+ type = types.enum [ "0" "1" "2" "auto" "max" "keep" ];
+
+ description = ''
+ The resolution of the console. The following values are valid:
+
+ <itemizedlist>
+ <listitem><para>
+ <literal>"0"</literal>: Standard UEFI 80x25 mode
+ </para></listitem>
+ <listitem><para>
+ <literal>"1"</literal>: 80x50 mode, not supported by all devices
+ </para></listitem>
+ <listitem><para>
+ <literal>"2"</literal>: The first non-standard mode provided by the device firmware, if any
+ </para></listitem>
+ <listitem><para>
+ <literal>"auto"</literal>: Pick a suitable mode automatically using heuristics
+ </para></listitem>
+ <listitem><para>
+ <literal>"max"</literal>: Pick the highest-numbered available mode
+ </para></listitem>
+ <listitem><para>
+ <literal>"keep"</literal>: Keep the mode selected by firmware (the default)
+ </para></listitem>
+ </itemizedlist>
+ '';
+ };
+
+ memtest86 = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Make MemTest86 available from the systemd-boot menu. MemTest86 is a
+ program for testing memory. MemTest86 is an unfree program, so
+ this requires <literal>allowUnfree</literal> to be set to
+ <literal>true</literal>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub;
+
+ message = "This kernel does not support the EFI boot stub";
+ }
+ ];
+
+ boot.loader.grub.enable = mkDefault false;
+
+ boot.loader.supportsInitrdSecrets = true;
+
+ system = {
+ build.installBootLoader = gummibootBuilder;
+
+ boot.loader.id = "systemd-boot";
+
+ requiredKernelConfig = with config.lib.kernelConfig; [
+ (isYes "EFI_STUB")
+ ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/luksroot.nix b/nixpkgs/nixos/modules/system/boot/luksroot.nix
new file mode 100644
index 00000000000..a4029d766b0
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/luksroot.nix
@@ -0,0 +1,791 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ luks = config.boot.initrd.luks;
+
+ commonFunctions = ''
+ die() {
+ 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() {
+ local name="$1"
+ local target="$2"
+ local secs="''${3:-10}"
+ local desc="''${4:-$name $target to appear}"
+
+ 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 dev_exist $target; then
+ success=true
+ break
+ fi
+ done
+ if [ $success == true ]; then
+ echo " - success";
+ return 0
+ else
+ echo " - failure";
+ return 1
+ fi
+ fi
+ return 0
+ }
+
+ 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
+ }
+
+ wait_gpgcard() {
+ local secs="''${1:-10}"
+
+ gpg --card-status > /dev/null 2> /dev/null
+ if [ $? != 0 ]; then
+ echo -n "Waiting $secs seconds for GPG Card to appear"
+ local success=false
+ for try in $(seq $secs); do
+ echo -n .
+ sleep 1
+ gpg --card-status > /dev/null 2> /dev/null
+ if [ $? == 0 ]; then
+ success=true
+ break
+ fi
+ done
+ if [ $success == true ]; then
+ echo " - success";
+ return 0
+ else
+ echo " - failure";
+ return 1
+ fi
+ fi
+ return 0
+ }
+ '';
+
+ preCommands = ''
+ # A place to store crypto things
+
+ # A ramfs is used here to ensure that the file used to update
+ # the key slot with cryptsetup will never get swapped out.
+ # Warning: Do NOT replace with tmpfs!
+ mkdir -p /crypt-ramfs
+ mount -t ramfs none /crypt-ramfs
+
+ # Cryptsetup locking directory
+ mkdir -p /run/cryptsetup
+
+ # For Yubikey salt storage
+ mkdir -p /crypt-storage
+
+ ${optionalString luks.gpgSupport ''
+ export GPG_TTY=$(tty)
+ export GNUPGHOME=/crypt-ramfs/.gnupg
+
+ gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null
+ ''}
+
+ # Disable all input echo for the whole stage. We could use read -s
+ # instead but that would ocasionally leak characters between read
+ # invocations.
+ stty -echo
+ '';
+
+ postCommands = ''
+ stty echo
+ umount /crypt-storage 2>/dev/null
+ umount /crypt-ramfs 2>/dev/null
+ '';
+
+ openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fallbackToPassword, ... }: assert name' == name;
+ let
+ csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}";
+ cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}";
+ in ''
+ # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g.
+ # if on a USB drive.
+ wait_target "device" ${device} || die "${device} is unavailable"
+
+ ${optionalString (header != null) ''
+ wait_target "header" ${header} || die "${header} is unavailable"
+ ''}
+
+ do_open_passphrase() {
+ local passphrase
+
+ while true; do
+ echo -n "Passphrase for ${device}: "
+ passphrase=
+ while true; do
+ if [ -e /crypt-ramfs/passphrase ]; then
+ echo "reused"
+ passphrase=$(cat /crypt-ramfs/passphrase)
+ break
+ else
+ # ask cryptsetup-askpass
+ echo -n "${device}" > /crypt-ramfs/device
+
+ # and try reading it from /dev/console with a timeout
+ IFS= read -t 1 -r passphrase
+ if [ -n "$passphrase" ]; then
+ ${if luks.reusePassphrases then ''
+ # remember it for the next device
+ echo -n "$passphrase" > /crypt-ramfs/passphrase
+ '' else ''
+ # Don't save it to ramfs. We are very paranoid
+ ''}
+ echo
+ break
+ fi
+ fi
+ done
+ echo -n "Verifying passphrase for ${device}..."
+ echo -n "$passphrase" | ${csopen} --key-file=-
+ if [ $? == 0 ]; then
+ echo " - success"
+ ${if luks.reusePassphrases then ''
+ # we don't rm here because we might reuse it for the next device
+ '' else ''
+ rm -f /crypt-ramfs/passphrase
+ ''}
+ break
+ else
+ echo " - failure"
+ # ask for a different one
+ rm -f /crypt-ramfs/passphrase
+ fi
+ done
+ }
+
+ # LUKS
+ open_normally() {
+ ${if (keyFile != null) then ''
+ if wait_target "key file" ${keyFile}; then
+ ${csopen} --key-file=${keyFile} \
+ ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \
+ ${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"}
+ else
+ ${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable"
+ echo " - failing back to interactive password prompt"
+ do_open_passphrase
+ fi
+ '' else ''
+ do_open_passphrase
+ ''}
+ }
+
+ ${optionalString (luks.yubikeySupport && (yubikey != null)) ''
+ # Yubikey
+ rbtohex() {
+ ( od -An -vtx1 | tr -d ' \n' )
+ }
+
+ hextorb() {
+ ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf )
+ }
+
+ do_open_yubikey() {
+ # Make all of these local to this function
+ # to prevent their values being leaked
+ local salt
+ local iterations
+ local k_user
+ local challenge
+ local response
+ local k_luks
+ local opened
+ local new_salt
+ local new_iterations
+ local new_challenge
+ local new_response
+ local new_k_luks
+
+ mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \
+ die "Failed to mount Yubikey salt storage device"
+
+ salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')"
+ iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')"
+ challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
+ response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)"
+
+ for try in $(seq 3); do
+ ${optionalString yubikey.twoFactor ''
+ echo -n "Enter two-factor passphrase: "
+ read -r k_user
+ echo
+ ''}
+
+ if [ ! -z "$k_user" ]; then
+ k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
+ else
+ k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
+ fi
+
+ echo -n "$k_luks" | hextorb | ${csopen} --key-file=-
+
+ if [ $? == 0 ]; then
+ opened=true
+ break
+ else
+ opened=false
+ echo "Authentication failed!"
+ fi
+ done
+
+ [ "$opened" == false ] && die "Maximum authentication errors reached"
+
+ echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..."
+ for i in $(seq ${toString yubikey.saltLength}); do
+ byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)";
+ new_salt="$new_salt$byte";
+ echo -n .
+ done;
+ echo "ok"
+
+ new_iterations="$iterations"
+ ${optionalString (yubikey.iterationStep > 0) ''
+ new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))"
+ ''}
+
+ new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
+
+ new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)"
+
+ if [ ! -z "$k_user" ]; then
+ new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
+ else
+ new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
+ fi
+
+ echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key
+ echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key
+
+ if [ $? == 0 ]; then
+ echo -ne "$new_salt\n$new_iterations" > /crypt-storage${yubikey.storage.path}
+ else
+ echo "Warning: Could not update LUKS key, current challenge persists!"
+ fi
+
+ rm -f /crypt-ramfs/new_key
+ umount /crypt-storage
+ }
+
+ open_with_hardware() {
+ if wait_yubikey ${toString yubikey.gracePeriod}; then
+ do_open_yubikey
+ else
+ echo "No yubikey found, falling back to non-yubikey open procedure"
+ open_normally
+ fi
+ }
+ ''}
+
+ ${optionalString (luks.gpgSupport && (gpgCard != null)) ''
+
+ do_open_gpg_card() {
+ # Make all of these local to this function
+ # to prevent their values being leaked
+ local pin
+ local opened
+
+ gpg --import /gpg-keys/${device}/pubkey.asc > /dev/null 2> /dev/null
+
+ gpg --card-status > /dev/null 2> /dev/null
+
+ for try in $(seq 3); do
+ echo -n "PIN for GPG Card associated with device ${device}: "
+ pin=
+ while true; do
+ if [ -e /crypt-ramfs/passphrase ]; then
+ echo "reused"
+ pin=$(cat /crypt-ramfs/passphrase)
+ break
+ else
+ # and try reading it from /dev/console with a timeout
+ IFS= read -t 1 -r pin
+ if [ -n "$pin" ]; then
+ ${if luks.reusePassphrases then ''
+ # remember it for the next device
+ echo -n "$pin" > /crypt-ramfs/passphrase
+ '' else ''
+ # Don't save it to ramfs. We are very paranoid
+ ''}
+ echo
+ break
+ fi
+ fi
+ done
+ echo -n "Verifying passphrase for ${device}..."
+ echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null
+ if [ $? == 0 ]; then
+ echo " - success"
+ ${if luks.reusePassphrases then ''
+ # we don't rm here because we might reuse it for the next device
+ '' else ''
+ rm -f /crypt-ramfs/passphrase
+ ''}
+ break
+ else
+ echo " - failure"
+ # ask for a different one
+ rm -f /crypt-ramfs/passphrase
+ fi
+ done
+
+ [ "$opened" == false ] && die "Maximum authentication errors reached"
+ }
+
+ open_with_hardware() {
+ if wait_gpgcard ${toString gpgCard.gracePeriod}; then
+ do_open_gpg_card
+ else
+ echo "No GPG Card found, falling back to normal open procedure"
+ open_normally
+ fi
+ }
+ ''}
+
+ ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) then ''
+ open_with_hardware
+ '' else ''
+ open_normally
+ ''}
+ '';
+
+ askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
+ #!/bin/sh
+
+ ${commonFunctions}
+
+ while true; do
+ wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now"
+ device=$(cat /crypt-ramfs/device)
+
+ echo -n "Passphrase for $device: "
+ IFS= read -rs passphrase
+ echo
+
+ rm /crypt-ramfs/device
+ echo -n "$passphrase" > /crypt-ramfs/passphrase
+ done
+ '';
+
+ preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
+ postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
+
+in
+{
+
+ options = {
+
+ boot.initrd.luks.mitigateDMAAttacks = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Unless enabled, encryption keys can be easily recovered by an attacker with physical
+ access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
+ More information is available at <link xlink:href="http://en.wikipedia.org/wiki/DMA_attack"/>.
+
+ This option blacklists FireWire drivers, but doesn't remove them. You can manually
+ load the drivers if you need to use a FireWire device, but don't forget to unload them!
+ '';
+ };
+
+ boot.initrd.luks.cryptoModules = mkOption {
+ type = types.listOf types.str;
+ default =
+ [ "aes" "aes_generic" "blowfish" "twofish"
+ "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
+ "af_alg" "algif_skcipher"
+
+ (if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "aes_x86_64" else "aes_i586")
+ ];
+ description = ''
+ A list of cryptographic kernel modules needed to decrypt the root device(s).
+ The default includes all common modules.
+ '';
+ };
+
+ boot.initrd.luks.forceLuksSupportInInitrd = mkOption {
+ type = types.bool;
+ default = false;
+ internal = true;
+ description = ''
+ Whether to configure luks support in the initrd, when no luks
+ devices are configured.
+ '';
+ };
+
+ boot.initrd.luks.reusePassphrases = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ When opening a new LUKS device try reusing last successful
+ passphrase.
+
+ Useful for mounting a number of devices that use the same
+ passphrase without retyping it several times.
+
+ Such setup can be useful if you use <command>cryptsetup
+ luksSuspend</command>. Different LUKS devices will still have
+ different master keys even when using the same passphrase.
+ '';
+ };
+
+ boot.initrd.luks.devices = mkOption {
+ default = { };
+ example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
+ description = ''
+ The encrypted disk that should be opened before the root
+ filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM
+ setups are supported. The unencrypted devices can be accessed as
+ <filename>/dev/mapper/<replaceable>name</replaceable></filename>.
+ '';
+
+ type = with types; loaOf (submodule (
+ { name, ... }: { options = {
+
+ name = mkOption {
+ visible = false;
+ default = name;
+ example = "luksroot";
+ type = types.str;
+ description = "Name of the unencrypted device in <filename>/dev/mapper</filename>.";
+ };
+
+ device = mkOption {
+ example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08";
+ type = types.str;
+ description = "Path of the underlying encrypted block device.";
+ };
+
+ header = mkOption {
+ default = null;
+ example = "/root/header.img";
+ type = types.nullOr types.str;
+ description = ''
+ The name of the file or block device that
+ should be used as header for the encrypted device.
+ '';
+ };
+
+ keyFile = mkOption {
+ default = null;
+ example = "/dev/sdb1";
+ type = types.nullOr types.str;
+ description = ''
+ The name of the file (can be a raw device or a partition) that
+ should be used as the decryption key for the encrypted device. If
+ not specified, you will be prompted for a passphrase instead.
+ '';
+ };
+
+ keyFileSize = mkOption {
+ default = null;
+ example = 4096;
+ type = types.nullOr types.int;
+ description = ''
+ The size of the key file. Use this if only the beginning of the
+ key file should be used as a key (often the case if a raw device
+ or partition is used as key file). If not specified, the whole
+ <literal>keyFile</literal> will be used decryption, instead of just
+ the first <literal>keyFileSize</literal> bytes.
+ '';
+ };
+
+ keyFileOffset = mkOption {
+ default = null;
+ example = 4096;
+ type = types.nullOr types.int;
+ description = ''
+ The offset of the key file. Use this in combination with
+ <literal>keyFileSize</literal> to use part of a file as key file
+ (often the case if a raw device or partition is used as a key file).
+ If not specified, the key begins at the first byte of
+ <literal>keyFile</literal>.
+ '';
+ };
+
+ # FIXME: get rid of this option.
+ preLVM = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether the luksOpen will be attempted before LVM scan or after it.";
+ };
+
+ allowDiscards = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to allow TRIM requests to the underlying device. This option
+ has security implications; please read the LUKS documentation before
+ activating it.
+ '';
+ };
+
+ fallbackToPassword = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to fallback to interactive passphrase prompt if the keyfile
+ cannot be found. This will prevent unattended boot should the keyfile
+ go missing.
+ '';
+ };
+
+ gpgCard = mkOption {
+ default = null;
+ description = ''
+ The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
+ If null (the default), GPG-Smartcard will be disabled for this device.
+ '';
+
+ type = with types; nullOr (submodule {
+ options = {
+ gracePeriod = mkOption {
+ default = 10;
+ type = types.int;
+ description = "Time in seconds to wait for the GPG Smartcard.";
+ };
+
+ encryptedPass = mkOption {
+ default = "";
+ type = types.path;
+ description = "Path to the GPG encrypted passphrase.";
+ };
+
+ publicKey = mkOption {
+ default = "";
+ type = types.path;
+ description = "Path to the Public Key.";
+ };
+ };
+ });
+ };
+
+ yubikey = mkOption {
+ default = null;
+ description = ''
+ The options to use for this LUKS device in Yubikey-PBA.
+ If null (the default), Yubikey-PBA will be disabled for this device.
+ '';
+
+ type = with types; nullOr (submodule {
+ options = {
+ twoFactor = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether to use a passphrase and a Yubikey (true), or only a Yubikey (false).";
+ };
+
+ slot = mkOption {
+ default = 2;
+ type = types.int;
+ description = "Which slot on the Yubikey to challenge.";
+ };
+
+ saltLength = mkOption {
+ default = 16;
+ type = types.int;
+ description = "Length of the new salt in byte (64 is the effective maximum).";
+ };
+
+ keyLength = mkOption {
+ default = 64;
+ type = types.int;
+ description = "Length of the LUKS slot key derived with PBKDF2 in byte.";
+ };
+
+ iterationStep = mkOption {
+ default = 0;
+ type = types.int;
+ description = "How much the iteration count for PBKDF2 is increased at each successful authentication.";
+ };
+
+ gracePeriod = mkOption {
+ default = 10;
+ type = types.int;
+ description = "Time in seconds to wait for the Yubikey.";
+ };
+
+ /* TODO: Add to the documentation of the current module:
+
+ Options related to the storing the salt.
+ */
+ storage = {
+ device = mkOption {
+ default = "/dev/sda1";
+ type = types.path;
+ description = ''
+ An unencrypted device that will temporarily be mounted in stage-1.
+ Must contain the current salt to create the challenge for this LUKS device.
+ '';
+ };
+
+ fsType = mkOption {
+ default = "vfat";
+ type = types.str;
+ description = "The filesystem of the unencrypted device.";
+ };
+
+ path = mkOption {
+ default = "/crypt-storage/default";
+ type = types.str;
+ description = ''
+ Absolute path of the salt on the unencrypted device with
+ that device's root directory as "/".
+ '';
+ };
+ };
+ };
+ });
+ };
+ };
+ }));
+ };
+
+ boot.initrd.luks.gpgSupport = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enables support for authenticating with a GPG encrypted password.
+ '';
+ };
+
+ boot.initrd.luks.yubikeySupport = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enables support for authenticating with a Yubikey on LUKS devices.
+ See the NixOS wiki for information on how to properly setup a LUKS device
+ and a Yubikey to work with this feature.
+ '';
+ };
+ };
+
+ config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
+
+ assertions =
+ [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
+ message = "Yubikey and GPG Card may not be used at the same time.";
+ }
+ ];
+
+ # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
+ boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
+ ["firewire_ohci" "firewire_core" "firewire_sbp2"];
+
+ # Some modules that may be needed for mounting anything ciphered
+ boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ]
+ ++ luks.cryptoModules
+ # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged
+ # remove once 'modprobe --show-depends xts' shows ecb as a dependency
+ ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []);
+
+ # copy the cryptsetup binary and it's dependencies
+ boot.initrd.extraUtilsCommands = ''
+ copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
+ copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
+ sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
+
+ ${optionalString luks.yubikeySupport ''
+ copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp
+ copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
+ copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
+
+ cc -O3 -I${pkgs.openssl.dev}/include -L${pkgs.openssl.out}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
+ strip -s pbkdf2-sha512
+ copy_bin_and_libs pbkdf2-sha512
+
+ mkdir -p $out/etc/ssl
+ cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl
+
+ cat > $out/bin/openssl-wrap <<EOF
+ #!$out/bin/sh
+ export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
+ $out/bin/openssl "\$@"
+ EOF
+ chmod +x $out/bin/openssl-wrap
+ ''}
+
+ ${optionalString luks.gpgSupport ''
+ copy_bin_and_libs ${pkgs.gnupg}/bin/gpg
+ copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent
+ copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
+
+ ${concatMapStringsSep "\n" (x:
+ if x.gpgCard != null then
+ ''
+ mkdir -p $out/secrets/gpg-keys/${x.device}
+ cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
+ cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
+ ''
+ else ""
+ ) (attrValues luks.devices)
+ }
+ ''}
+ '';
+
+ boot.initrd.extraUtilsCommandsTest = ''
+ $out/bin/cryptsetup --version
+ ${optionalString luks.yubikeySupport ''
+ $out/bin/ykchalresp -V
+ $out/bin/ykinfo -V
+ $out/bin/openssl-wrap version
+ ''}
+ ${optionalString luks.gpgSupport ''
+ $out/bin/gpg --version
+ $out/bin/gpg-agent --version
+ $out/bin/scdaemon --version
+ ''}
+ '';
+
+ boot.initrd.preFailCommands = postCommands;
+ boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands;
+ boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands;
+
+ environment.systemPackages = [ pkgs.cryptsetup ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/modprobe.nix b/nixpkgs/nixos/modules/system/boot/modprobe.nix
new file mode 100644
index 00000000000..dee0ab470c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/modprobe.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ boot.blacklistedKernelModules = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "cirrusfb" "i2c_piix4" ];
+ description = ''
+ List of names of kernel modules that should not be loaded
+ automatically by the hardware probing code.
+ '';
+ };
+
+ boot.extraModprobeConfig = mkOption {
+ default = "";
+ example =
+ ''
+ options parport_pc io=0x378 irq=7 dma=1
+ '';
+ description = ''
+ Any additional configuration to be appended to the generated
+ <filename>modprobe.conf</filename>. This is typically used to
+ specify module options. See
+ <citerefentry><refentrytitle>modprobe.conf</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ type = types.lines;
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf (!config.boot.isContainer) {
+
+ environment.etc."modprobe.d/ubuntu.conf".source = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
+
+ environment.etc."modprobe.d/nixos.conf".text =
+ ''
+ ${flip concatMapStrings config.boot.blacklistedKernelModules (name: ''
+ blacklist ${name}
+ '')}
+ ${config.boot.extraModprobeConfig}
+ '';
+ environment.etc."modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases;
+
+ environment.systemPackages = [ pkgs.kmod ];
+
+ system.activationScripts.modprobe = stringAfter ["specialfs"]
+ ''
+ # Allow the kernel to find our wrapped modprobe (which searches
+ # in the right location in the Nix store for kernel modules).
+ # We need this when the kernel (or some module) auto-loads a
+ # module.
+ echo ${pkgs.kmod}/bin/modprobe > /proc/sys/kernel/modprobe
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/networkd.nix b/nixpkgs/nixos/modules/system/boot/networkd.nix
new file mode 100644
index 00000000000..f2060e21509
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/networkd.nix
@@ -0,0 +1,963 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with import ./systemd-unit-options.nix { inherit config lib; };
+with import ./systemd-lib.nix { inherit config lib pkgs; };
+
+let
+
+ cfg = config.systemd.network;
+
+ checkLink = checkUnitConfig "Link" [
+ (assertOnlyFields [
+ "Description" "Alias" "MACAddressPolicy" "MACAddress" "NamePolicy" "OriginalName"
+ "MTUBytes" "BitsPerSecond" "Duplex" "AutoNegotiation" "WakeOnLan" "Port"
+ "TCPSegmentationOffload" "TCP6SegmentationOffload" "GenericSegmentationOffload"
+ "GenericReceiveOffload" "LargeReceiveOffload" "RxChannels" "TxChannels"
+ "OtherChannels" "CombinedChannels"
+ ])
+ (assertValueOneOf "MACAddressPolicy" ["persistent" "random" "none"])
+ (assertMacAddress "MACAddress")
+ (assertByteFormat "MTUBytes")
+ (assertByteFormat "BitsPerSecond")
+ (assertValueOneOf "Duplex" ["half" "full"])
+ (assertValueOneOf "AutoNegotiation" boolValues)
+ (assertValueOneOf "WakeOnLan" ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon" "off"])
+ (assertValueOneOf "Port" ["tp" "aui" "bnc" "mii" "fibre"])
+ (assertValueOneOf "TCPSegmentationOffload" boolValues)
+ (assertValueOneOf "TCP6SegmentationOffload" boolValues)
+ (assertValueOneOf "GenericSegmentationOffload" boolValues)
+ (assertValueOneOf "UDPSegmentationOffload" boolValues)
+ (assertValueOneOf "GenericReceiveOffload" boolValues)
+ (assertValueOneOf "LargeReceiveOffload" boolValues)
+ (assertInt "RxChannels")
+ (assertMinimum "RxChannels" 1)
+ (assertInt "TxChannels")
+ (assertMinimum "TxChannels" 1)
+ (assertInt "OtherChannels")
+ (assertMinimum "OtherChannels" 1)
+ (assertInt "CombinedChannels")
+ (assertMinimum "CombinedChannels" 1)
+ ];
+
+ checkNetdev = checkUnitConfig "Netdev" [
+ (assertOnlyFields [
+ "Description" "Name" "Kind" "MTUBytes" "MACAddress"
+ ])
+ (assertHasField "Name")
+ (assertHasField "Kind")
+ (assertValueOneOf "Kind" [
+ "bond" "bridge" "dummy" "gre" "gretap" "ip6gre" "ip6tnl" "ip6gretap" "ipip"
+ "ipvlan" "macvlan" "macvtap" "sit" "tap" "tun" "veth" "vlan" "vti" "vti6"
+ "vxlan" "geneve" "vrf" "vcan" "vxcan" "wireguard" "netdevsim"
+ ])
+ (assertByteFormat "MTUBytes")
+ (assertMacAddress "MACAddress")
+ ];
+
+ # NOTE The PrivateKey directive is missing on purpose here, please
+ # do not add it to this list. The nix store is world-readable let's
+ # refrain ourselves from providing a footgun.
+ checkWireGuard = checkUnitConfig "WireGuard" [
+ (assertOnlyFields [
+ "PrivateKeyFile" "ListenPort" "FwMark"
+ ])
+ (assertRange "FwMark" 1 4294967295)
+ ];
+
+ # NOTE The PresharedKey directive is missing on purpose here, please
+ # do not add it to this list. The nix store is world-readable,let's
+ # refrain ourselves from providing a footgun.
+ checkWireGuardPeer = checkUnitConfig "WireGuardPeer" [
+ (assertOnlyFields [
+ "PublicKey" "PresharedKeyFile" "AllowedIPs"
+ "Endpoint" "PersistentKeepalive"
+ ])
+ (assertRange "PersistentKeepalive" 1 65535)
+ ];
+
+ checkVlan = checkUnitConfig "VLAN" [
+ (assertOnlyFields ["Id" "GVRP" "MVRP" "LooseBinding" "ReorderHeader"])
+ (assertRange "Id" 0 4094)
+ (assertValueOneOf "GVRP" boolValues)
+ (assertValueOneOf "MVRP" boolValues)
+ (assertValueOneOf "LooseBinding" boolValues)
+ (assertValueOneOf "ReorderHeader" boolValues)
+ ];
+
+ checkMacvlan = checkUnitConfig "MACVLAN" [
+ (assertOnlyFields ["Mode"])
+ (assertValueOneOf "Mode" ["private" "vepa" "bridge" "passthru"])
+ ];
+
+ checkVxlan = checkUnitConfig "VXLAN" [
+ (assertOnlyFields [
+ "Id" "Remote" "Local" "TOS" "TTL" "MacLearning" "FDBAgeingSec"
+ "MaximumFDBEntries" "ReduceARPProxy" "L2MissNotification"
+ "L3MissNotification" "RouteShortCircuit" "UDPChecksum"
+ "UDP6ZeroChecksumTx" "UDP6ZeroChecksumRx" "RemoteChecksumTx"
+ "RemoteChecksumRx" "GroupPolicyExtension" "DestinationPort" "PortRange"
+ "FlowLabel"
+ ])
+ (assertRange "TTL" 0 255)
+ (assertValueOneOf "MacLearning" boolValues)
+ (assertValueOneOf "ReduceARPProxy" boolValues)
+ (assertValueOneOf "L2MissNotification" boolValues)
+ (assertValueOneOf "L3MissNotification" boolValues)
+ (assertValueOneOf "RouteShortCircuit" boolValues)
+ (assertValueOneOf "UDPChecksum" boolValues)
+ (assertValueOneOf "UDP6ZeroChecksumTx" boolValues)
+ (assertValueOneOf "UDP6ZeroChecksumRx" boolValues)
+ (assertValueOneOf "RemoteChecksumTx" boolValues)
+ (assertValueOneOf "RemoteChecksumRx" boolValues)
+ (assertValueOneOf "GroupPolicyExtension" boolValues)
+ (assertRange "FlowLabel" 0 1048575)
+ ];
+
+ checkTunnel = checkUnitConfig "Tunnel" [
+ (assertOnlyFields [
+ "Local" "Remote" "TOS" "TTL" "DiscoverPathMTU" "IPv6FlowLabel" "CopyDSCP"
+ "EncapsulationLimit" "Key" "InputKey" "OutputKey" "Mode" "Independent"
+ "AllowLocalRemote"
+ ])
+ (assertRange "TTL" 0 255)
+ (assertValueOneOf "DiscoverPathMTU" boolValues)
+ (assertValueOneOf "CopyDSCP" boolValues)
+ (assertValueOneOf "Mode" ["ip6ip6" "ipip6" "any"])
+ (assertValueOneOf "Independent" boolValues)
+ (assertValueOneOf "AllowLocalRemote" boolValues)
+ ];
+
+ checkPeer = checkUnitConfig "Peer" [
+ (assertOnlyFields ["Name" "MACAddress"])
+ (assertMacAddress "MACAddress")
+ ];
+
+ tunTapChecks = [
+ (assertOnlyFields ["OneQueue" "MultiQueue" "PacketInfo" "VNetHeader" "User" "Group"])
+ (assertValueOneOf "OneQueue" boolValues)
+ (assertValueOneOf "MultiQueue" boolValues)
+ (assertValueOneOf "PacketInfo" boolValues)
+ (assertValueOneOf "VNetHeader" boolValues)
+ ];
+
+ checkTun = checkUnitConfig "Tun" tunTapChecks;
+
+ checkTap = checkUnitConfig "Tap" tunTapChecks;
+
+ checkBond = checkUnitConfig "Bond" [
+ (assertOnlyFields [
+ "Mode" "TransmitHashPolicy" "LACPTransmitRate" "MIIMonitorSec"
+ "UpDelaySec" "DownDelaySec" "LearnPacketIntervalSec" "AdSelect"
+ "FailOverMACPolicy" "ARPValidate" "ARPIntervalSec" "ARPIPTargets"
+ "ARPAllTargets" "PrimaryReselectPolicy" "ResendIGMP" "PacketsPerSlave"
+ "GratuitousARP" "AllSlavesActive" "MinLinks"
+ ])
+ (assertValueOneOf "Mode" [
+ "balance-rr" "active-backup" "balance-xor"
+ "broadcast" "802.3ad" "balance-tlb" "balance-alb"
+ ])
+ (assertValueOneOf "TransmitHashPolicy" [
+ "layer2" "layer3+4" "layer2+3" "encap2+3" "encap3+4"
+ ])
+ (assertValueOneOf "LACPTransmitRate" ["slow" "fast"])
+ (assertValueOneOf "AdSelect" ["stable" "bandwidth" "count"])
+ (assertValueOneOf "FailOverMACPolicy" ["none" "active" "follow"])
+ (assertValueOneOf "ARPValidate" ["none" "active" "backup" "all"])
+ (assertValueOneOf "ARPAllTargets" ["any" "all"])
+ (assertValueOneOf "PrimaryReselectPolicy" ["always" "better" "failure"])
+ (assertRange "ResendIGMP" 0 255)
+ (assertRange "PacketsPerSlave" 0 65535)
+ (assertRange "GratuitousARP" 0 255)
+ (assertValueOneOf "AllSlavesActive" boolValues)
+ ];
+
+ checkNetwork = checkUnitConfig "Network" [
+ (assertOnlyFields [
+ "Description" "DHCP" "DHCPServer" "LinkLocalAddressing" "IPv4LLRoute"
+ "IPv6Token" "LLMNR" "MulticastDNS" "DNSOverTLS" "DNSSEC"
+ "DNSSECNegativeTrustAnchors" "LLDP" "EmitLLDP" "BindCarrier" "Address"
+ "Gateway" "DNS" "Domains" "NTP" "IPForward" "IPMasquerade"
+ "IPv6PrivacyExtensions" "IPv6AcceptRA" "IPv6DuplicateAddressDetection"
+ "IPv6HopLimit" "IPv4ProxyARP" "IPv6ProxyNDP" "IPv6ProxyNDPAddress"
+ "IPv6PrefixDelegation" "IPv6MTUBytes" "Bridge" "Bond" "VRF" "VLAN"
+ "IPVLAN" "MACVLAN" "VXLAN" "Tunnel" "ActiveSlave" "PrimarySlave"
+ "ConfigureWithoutCarrier"
+ ])
+ # Note: For DHCP the values both, none, v4, v6 are deprecated
+ (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6" "both" "none" "v4" "v6"])
+ (assertValueOneOf "DHCPServer" boolValues)
+ (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6"])
+ (assertValueOneOf "IPv4LLRoute" boolValues)
+ (assertValueOneOf "LLMNR" ["yes" "resolve" "no"])
+ (assertValueOneOf "MulticastDNS" ["yes" "resolve" "no"])
+ (assertValueOneOf "DNSOverTLS" ["opportunistic" "no"])
+ (assertValueOneOf "DNSSEC" ["yes" "allow-downgrade" "no"])
+ (assertValueOneOf "LLDP" ["yes" "routers-only" "no"])
+ (assertValueOneOf "EmitLLDP" ["yes" "no" "nearest-bridge" "non-tpmr-bridge" "customer-bridge"])
+ (assertValueOneOf "IPForward" ["yes" "no" "ipv4" "ipv6"])
+ (assertValueOneOf "IPMasquerade" boolValues)
+ (assertValueOneOf "IPv6PrivacyExtensions" ["yes" "no" "prefer-public" "kernel"])
+ (assertValueOneOf "IPv6AcceptRA" boolValues)
+ (assertValueOneOf "IPv4ProxyARP" boolValues)
+ (assertValueOneOf "IPv6ProxyNDP" boolValues)
+ (assertValueOneOf "IPv6PrefixDelegation" boolValues)
+ (assertValueOneOf "ActiveSlave" boolValues)
+ (assertValueOneOf "PrimarySlave" boolValues)
+ (assertValueOneOf "ConfigureWithoutCarrier" boolValues)
+ ];
+
+ checkAddress = checkUnitConfig "Address" [
+ (assertOnlyFields [
+ "Address" "Peer" "Broadcast" "Label" "PreferredLifetime" "Scope"
+ "HomeAddress" "DuplicateAddressDetection" "ManageTemporaryAddress"
+ "PrefixRoute" "AutoJoin"
+ ])
+ (assertHasField "Address")
+ (assertValueOneOf "PreferredLifetime" ["forever" "infinity" "0" 0])
+ (assertValueOneOf "HomeAddress" boolValues)
+ (assertValueOneOf "DuplicateAddressDetection" boolValues)
+ (assertValueOneOf "ManageTemporaryAddress" boolValues)
+ (assertValueOneOf "PrefixRoute" boolValues)
+ (assertValueOneOf "AutoJoin" boolValues)
+ ];
+
+ checkRoute = checkUnitConfig "Route" [
+ (assertOnlyFields [
+ "Gateway" "GatewayOnLink" "Destination" "Source" "Metric"
+ "IPv6Preference" "Scope" "PreferredSource" "Table" "Protocol" "Type"
+ "InitialCongestionWindow" "InitialAdvertisedReceiveWindow" "QuickAck"
+ "MTUBytes"
+ ])
+ ];
+
+ checkDhcp = checkUnitConfig "DHCP" [
+ (assertOnlyFields [
+ "UseDNS" "UseNTP" "UseMTU" "Anonymize" "SendHostname" "UseHostname"
+ "Hostname" "UseDomains" "UseRoutes" "UseTimezone" "CriticalConnection"
+ "ClientIdentifier" "VendorClassIdentifier" "UserClass" "DUIDType"
+ "DUIDRawData" "IAID" "RequestBroadcast" "RouteMetric" "RouteTable"
+ "ListenPort" "RapidCommit"
+ ])
+ (assertValueOneOf "UseDNS" boolValues)
+ (assertValueOneOf "UseNTP" boolValues)
+ (assertValueOneOf "UseMTU" boolValues)
+ (assertValueOneOf "Anonymize" boolValues)
+ (assertValueOneOf "SendHostname" boolValues)
+ (assertValueOneOf "UseHostname" boolValues)
+ (assertValueOneOf "UseDomains" ["yes" "no" "route"])
+ (assertValueOneOf "UseRoutes" boolValues)
+ (assertValueOneOf "UseTimezone" boolValues)
+ (assertValueOneOf "CriticalConnection" boolValues)
+ (assertValueOneOf "RequestBroadcast" boolValues)
+ (assertInt "RouteTable")
+ (assertMinimum "RouteTable" 0)
+ (assertValueOneOf "RapidCommit" boolValues)
+ ];
+
+ checkDhcpServer = checkUnitConfig "DHCPServer" [
+ (assertOnlyFields [
+ "PoolOffset" "PoolSize" "DefaultLeaseTimeSec" "MaxLeaseTimeSec"
+ "EmitDNS" "DNS" "EmitNTP" "NTP" "EmitRouter" "EmitTimezone" "Timezone"
+ ])
+ (assertValueOneOf "EmitDNS" boolValues)
+ (assertValueOneOf "EmitNTP" boolValues)
+ (assertValueOneOf "EmitRouter" boolValues)
+ (assertValueOneOf "EmitTimezone" boolValues)
+ ];
+
+ # .network files have a [Link] section with different options than in .netlink files
+ checkNetworkLink = checkUnitConfig "Link" [
+ (assertOnlyFields [
+ "MACAddress" "MTUBytes" "ARP" "Multicast" "Unmanaged" "RequiredForOnline"
+ ])
+ (assertMacAddress "MACAddress")
+ (assertByteFormat "MTUBytes")
+ (assertValueOneOf "ARP" boolValues)
+ (assertValueOneOf "Multicast" boolValues)
+ (assertValueOneOf "Unmanaged" boolValues)
+ (assertValueOneOf "RequiredForOnline" boolValues)
+ ];
+
+
+ commonNetworkOptions = {
+
+ enable = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to manage network configuration using <command>systemd-network</command>.
+ '';
+ };
+
+ matchConfig = mkOption {
+ default = {};
+ example = { Name = "eth0"; };
+ type = types.attrsOf unitOption;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Match]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Extra configuration append to unit";
+ };
+ };
+
+ linkOptions = commonNetworkOptions // {
+
+ linkConfig = mkOption {
+ default = {};
+ example = { MACAddress = "00:ff:ee:aa:cc:dd"; };
+ type = types.addCheck (types.attrsOf unitOption) checkLink;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Link]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.link</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ };
+
+ netdevOptions = commonNetworkOptions // {
+
+ netdevConfig = mkOption {
+ default = {};
+ example = { Name = "mybridge"; Kind = "bridge"; };
+ type = types.addCheck (types.attrsOf unitOption) checkNetdev;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Netdev]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ wireguardConfig = mkOption {
+ default = {};
+ example = {
+ PrivateKeyFile = "/etc/wireguard/secret.key";
+ ListenPort = 51820;
+ FwMark = 42;
+ };
+ type = types.addCheck (types.attrsOf unitOption) checkWireGuard;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[WireGuard]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ Use <literal>PrivateKeyFile</literal> instead of
+ <literal>PrivateKey</literal>: the nix store is
+ world-readable.
+ '';
+ };
+
+ wireguardPeers = mkOption {
+ default = [];
+ example = [ { wireguardPeerConfig={
+ Endpoint = "192.168.1.1:51820";
+ PublicKey = "27s0OvaBBdHoJYkH9osZpjpgSOVNw+RaKfboT/Sfq0g=";
+ PresharedKeyFile = "/etc/wireguard/psk.key";
+ AllowedIPs = [ "10.0.0.1/32" ];
+ PersistentKeepalive = 15;
+ };}];
+ type = with types; listOf (submodule wireguardPeerOptions);
+ description = ''
+ Each item in this array specifies an option in the
+ <literal>[WireGuardPeer]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ Use <literal>PresharedKeyFile</literal> instead of
+ <literal>PresharedKey</literal>: the nix store is
+ world-readable.
+ '';
+ };
+
+ vlanConfig = mkOption {
+ default = {};
+ example = { Id = "4"; };
+ type = types.addCheck (types.attrsOf unitOption) checkVlan;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[VLAN]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ macvlanConfig = mkOption {
+ default = {};
+ example = { Mode = "private"; };
+ type = types.addCheck (types.attrsOf unitOption) checkMacvlan;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[MACVLAN]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ vxlanConfig = mkOption {
+ default = {};
+ example = { Id = "4"; };
+ type = types.addCheck (types.attrsOf unitOption) checkVxlan;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[VXLAN]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ tunnelConfig = mkOption {
+ default = {};
+ example = { Remote = "192.168.1.1"; };
+ type = types.addCheck (types.attrsOf unitOption) checkTunnel;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Tunnel]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ peerConfig = mkOption {
+ default = {};
+ example = { Name = "veth2"; };
+ type = types.addCheck (types.attrsOf unitOption) checkPeer;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Peer]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ tunConfig = mkOption {
+ default = {};
+ example = { User = "openvpn"; };
+ type = types.addCheck (types.attrsOf unitOption) checkTun;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Tun]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ tapConfig = mkOption {
+ default = {};
+ example = { User = "openvpn"; };
+ type = types.addCheck (types.attrsOf unitOption) checkTap;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Tap]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ bondConfig = mkOption {
+ default = {};
+ example = { Mode = "802.3ad"; };
+ type = types.addCheck (types.attrsOf unitOption) checkBond;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Bond]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ };
+
+ addressOptions = {
+ options = {
+ addressConfig = mkOption {
+ default = {};
+ example = { Address = "192.168.0.100/24"; };
+ type = types.addCheck (types.attrsOf unitOption) checkAddress;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Address]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+ };
+ };
+
+ routeOptions = {
+ options = {
+ routeConfig = mkOption {
+ default = {};
+ example = { Gateway = "192.168.0.1"; };
+ type = types.addCheck (types.attrsOf unitOption) checkRoute;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Route]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+ };
+ };
+
+ wireguardPeerOptions = {
+ options = {
+ wireguardPeerConfig = mkOption {
+ default = {};
+ example = { };
+ type = types.addCheck (types.attrsOf unitOption) checkWireGuardPeer;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[WireGuardPeer]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+ };
+ };
+
+
+ networkOptions = commonNetworkOptions // {
+
+ networkConfig = mkOption {
+ default = {};
+ example = { Description = "My Network"; };
+ type = types.addCheck (types.attrsOf unitOption) checkNetwork;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Network]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ dhcpConfig = mkOption {
+ default = {};
+ example = { UseDNS = true; UseRoutes = true; };
+ type = types.addCheck (types.attrsOf unitOption) checkDhcp;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[DHCP]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ dhcpServerConfig = mkOption {
+ default = {};
+ example = { PoolOffset = 50; EmitDNS = false; };
+ type = types.addCheck (types.attrsOf unitOption) checkDhcpServer;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[DHCPServer]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ linkConfig = mkOption {
+ default = {};
+ example = { Unmanaged = true; };
+ type = types.addCheck (types.attrsOf unitOption) checkNetworkLink;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Link]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ name = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The name of the network interface to match against.
+ '';
+ };
+
+ DHCP = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Whether to enable DHCP on the interfaces matched.
+ '';
+ };
+
+ domains = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = ''
+ A list of domains to pass to the network config.
+ '';
+ };
+
+ address = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of addresses to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ gateway = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of gateways to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ dns = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of dns servers to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ ntp = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of ntp servers to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ bridge = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of bridge interfaces to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ bond = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of bond interfaces to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ vrf = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of vrf interfaces to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ vlan = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of vlan interfaces to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ macvlan = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of macvlan interfaces to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ vxlan = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of vxlan interfaces to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ tunnel = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ A list of tunnel interfaces to be added to the network section of the
+ unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ addresses = mkOption {
+ default = [ ];
+ type = with types; listOf (submodule addressOptions);
+ description = ''
+ A list of address sections to be added to the unit. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ routes = mkOption {
+ default = [ ];
+ type = with types; listOf (submodule routeOptions);
+ description = ''
+ A list of route sections to be added to the unit. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ };
+
+ networkConfig = { config, ... }: {
+ config = {
+ matchConfig = optionalAttrs (config.name != null) {
+ Name = config.name;
+ };
+ networkConfig = optionalAttrs (config.DHCP != null) {
+ DHCP = config.DHCP;
+ } // optionalAttrs (config.domains != null) {
+ Domains = concatStringsSep " " config.domains;
+ };
+ };
+ };
+
+ commonMatchText = def: optionalString (def.matchConfig != {}) ''
+ [Match]
+ ${attrsToSection def.matchConfig}
+ '';
+
+ linkToUnit = name: def:
+ { inherit (def) enable;
+ text = commonMatchText def +
+ ''
+ [Link]
+ ${attrsToSection def.linkConfig}
+
+ ${def.extraConfig}
+ '';
+ };
+
+ netdevToUnit = name: def:
+ { inherit (def) enable;
+ text = commonMatchText def +
+ ''
+ [NetDev]
+ ${attrsToSection def.netdevConfig}
+
+ ${optionalString (def.vlanConfig != { }) ''
+ [VLAN]
+ ${attrsToSection def.vlanConfig}
+
+ ''}
+ ${optionalString (def.macvlanConfig != { }) ''
+ [MACVLAN]
+ ${attrsToSection def.macvlanConfig}
+
+ ''}
+ ${optionalString (def.vxlanConfig != { }) ''
+ [VXLAN]
+ ${attrsToSection def.vxlanConfig}
+
+ ''}
+ ${optionalString (def.tunnelConfig != { }) ''
+ [Tunnel]
+ ${attrsToSection def.tunnelConfig}
+
+ ''}
+ ${optionalString (def.peerConfig != { }) ''
+ [Peer]
+ ${attrsToSection def.peerConfig}
+
+ ''}
+ ${optionalString (def.tunConfig != { }) ''
+ [Tun]
+ ${attrsToSection def.tunConfig}
+
+ ''}
+ ${optionalString (def.tapConfig != { }) ''
+ [Tap]
+ ${attrsToSection def.tapConfig}
+
+ ''}
+ ${optionalString (def.bondConfig != { }) ''
+ [Bond]
+ ${attrsToSection def.bondConfig}
+
+ ''}
+ ${optionalString (def.wireguardConfig != { }) ''
+ [WireGuard]
+ ${attrsToSection def.wireguardConfig}
+
+ ''}
+ ${flip concatMapStrings def.wireguardPeers (x: ''
+ [WireGuardPeer]
+ ${attrsToSection x.wireguardPeerConfig}
+
+ '')}
+ ${def.extraConfig}
+ '';
+ };
+
+ networkToUnit = name: def:
+ { inherit (def) enable;
+ text = commonMatchText def +
+ ''
+ ${optionalString (def.linkConfig != { }) ''
+ [Link]
+ ${attrsToSection def.linkConfig}
+
+ ''}
+
+ [Network]
+ ${attrsToSection def.networkConfig}
+ ${concatStringsSep "\n" (map (s: "Address=${s}") def.address)}
+ ${concatStringsSep "\n" (map (s: "Gateway=${s}") def.gateway)}
+ ${concatStringsSep "\n" (map (s: "DNS=${s}") def.dns)}
+ ${concatStringsSep "\n" (map (s: "NTP=${s}") def.ntp)}
+ ${concatStringsSep "\n" (map (s: "Bridge=${s}") def.bridge)}
+ ${concatStringsSep "\n" (map (s: "Bond=${s}") def.bond)}
+ ${concatStringsSep "\n" (map (s: "VRF=${s}") def.vrf)}
+ ${concatStringsSep "\n" (map (s: "VLAN=${s}") def.vlan)}
+ ${concatStringsSep "\n" (map (s: "MACVLAN=${s}") def.macvlan)}
+ ${concatStringsSep "\n" (map (s: "VXLAN=${s}") def.vxlan)}
+ ${concatStringsSep "\n" (map (s: "Tunnel=${s}") def.tunnel)}
+
+ ${optionalString (def.dhcpConfig != { }) ''
+ [DHCP]
+ ${attrsToSection def.dhcpConfig}
+
+ ''}
+ ${optionalString (def.dhcpServerConfig != { }) ''
+ [DHCPServer]
+ ${attrsToSection def.dhcpServerConfig}
+
+ ''}
+ ${flip concatMapStrings def.addresses (x: ''
+ [Address]
+ ${attrsToSection x.addressConfig}
+
+ '')}
+ ${flip concatMapStrings def.routes (x: ''
+ [Route]
+ ${attrsToSection x.routeConfig}
+
+ '')}
+ ${def.extraConfig}
+ '';
+ };
+
+ unitFiles = map (name: {
+ target = "systemd/network/${name}";
+ source = "${cfg.units.${name}.unit}/${name}";
+ }) (attrNames cfg.units);
+in
+
+{
+
+ options = {
+
+ systemd.network.enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable networkd or not.
+ '';
+ };
+
+ systemd.network.links = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = linkOptions; } ]);
+ description = "Definition of systemd network links.";
+ };
+
+ systemd.network.netdevs = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = netdevOptions; } ]);
+ description = "Definition of systemd network devices.";
+ };
+
+ systemd.network.networks = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = networkOptions; } networkConfig ]);
+ description = "Definition of systemd networks.";
+ };
+
+ systemd.network.units = mkOption {
+ description = "Definition of networkd units.";
+ default = {};
+ type = with types; attrsOf (submodule (
+ { name, config, ... }:
+ { options = concreteUnitOptions;
+ config = {
+ unit = mkDefault (makeUnit name config);
+ };
+ }));
+ };
+
+ };
+
+ config = mkIf config.systemd.network.enable {
+
+ systemd.additionalUpstreamSystemUnits = [
+ "systemd-networkd.service" "systemd-networkd-wait-online.service"
+ ];
+
+ systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links
+ // mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs
+ // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks;
+
+ environment.etc = unitFiles;
+
+ systemd.services.systemd-networkd = {
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = map (f: f.source) (unitFiles);
+ # prevent race condition with interface renaming (#39069)
+ requires = [ "systemd-udev-settle.service" ];
+ after = [ "systemd-udev-settle.service" ];
+ };
+
+ systemd.services.systemd-networkd-wait-online = {
+ wantedBy = [ "network-online.target" ];
+ };
+
+ systemd.services."systemd-network-wait-online@" = {
+ description = "Wait for Network Interface %I to be Configured";
+ conflicts = [ "shutdown.target" ];
+ requisite = [ "systemd-networkd.service" ];
+ after = [ "systemd-networkd.service" ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I";
+ };
+ };
+
+ services.resolved.enable = mkDefault true;
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/pbkdf2-sha512.c b/nixpkgs/nixos/modules/system/boot/pbkdf2-sha512.c
new file mode 100644
index 00000000000..b40c383ac02
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/pbkdf2-sha512.c
@@ -0,0 +1,38 @@
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <openssl/evp.h>
+
+void hextorb(uint8_t* hex, uint8_t* rb)
+{
+ while(sscanf(hex, "%2x", rb) == 1)
+ {
+ hex += 2;
+ rb += 1;
+ }
+ *rb = '\0';
+}
+
+int main(int argc, char** argv)
+{
+ uint8_t k_user[2048];
+ uint8_t salt[2048];
+ uint8_t key[4096];
+
+ uint32_t key_length = atoi(argv[1]);
+ uint32_t iteration_count = atoi(argv[2]);
+
+ hextorb(argv[3], salt);
+ uint32_t salt_length = strlen(argv[3]) / 2;
+
+ fgets(k_user, 2048, stdin);
+ uint32_t k_user_length = strlen(k_user);
+ if(k_user[k_user_length - 1] == '\n') {
+ k_user[k_user_length - 1] = '\0';
+ }
+
+ PKCS5_PBKDF2_HMAC(k_user, k_user_length, salt, salt_length, iteration_count, EVP_sha512(), key_length, key);
+ fwrite(key, 1, key_length, stdout);
+
+ return 0;
+} \ No newline at end of file
diff --git a/nixpkgs/nixos/modules/system/boot/plymouth.nix b/nixpkgs/nixos/modules/system/boot/plymouth.nix
new file mode 100644
index 00000000000..e4223bae7d3
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/plymouth.nix
@@ -0,0 +1,163 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs) plymouth;
+
+ cfg = config.boot.plymouth;
+
+ breezePlymouth = pkgs.breeze-plymouth.override {
+ nixosBranding = true;
+ nixosVersion = config.system.nixos.release;
+ };
+
+ themesEnv = pkgs.buildEnv {
+ name = "plymouth-themes";
+ paths = [ plymouth breezePlymouth ] ++ cfg.themePackages;
+ };
+
+ configFile = pkgs.writeText "plymouthd.conf" ''
+ [Daemon]
+ ShowDelay=0
+ Theme=${cfg.theme}
+ '';
+
+in
+
+{
+
+ options = {
+
+ boot.plymouth = {
+
+ enable = mkEnableOption "Plymouth boot splash screen";
+
+ themePackages = mkOption {
+ default = [];
+ type = types.listOf types.package;
+ description = ''
+ Extra theme packages for plymouth.
+ '';
+ };
+
+ theme = mkOption {
+ default = "breeze";
+ type = types.str;
+ description = ''
+ Splash screen theme.
+ '';
+ };
+
+ logo = mkOption {
+ type = types.path;
+ default = pkgs.fetchurl {
+ url = "https://nixos.org/logo/nixos-hires.png";
+ sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si";
+ };
+ defaultText = ''pkgs.fetchurl {
+ url = "https://nixos.org/logo/nixos-hires.png";
+ sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si";
+ }'';
+ description = ''
+ Logo which is displayed on the splash screen.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ boot.kernelParams = [ "splash" ];
+
+ # To be discoverable by systemd.
+ environment.systemPackages = [ plymouth ];
+
+ environment.etc."plymouth/plymouthd.conf".source = configFile;
+ environment.etc."plymouth/plymouthd.defaults".source = "${plymouth}/share/plymouth/plymouthd.defaults";
+ environment.etc."plymouth/logo.png".source = cfg.logo;
+ environment.etc."plymouth/themes".source = "${themesEnv}/share/plymouth/themes";
+ # XXX: Needed because we supply a different set of plugins in initrd.
+ environment.etc."plymouth/plugins".source = "${plymouth}/lib/plymouth";
+
+ systemd.packages = [ plymouth ];
+
+ systemd.services.plymouth-kexec.wantedBy = [ "kexec.target" ];
+ systemd.services.plymouth-halt.wantedBy = [ "halt.target" ];
+ systemd.services.plymouth-quit-wait.wantedBy = [ "multi-user.target" ];
+ systemd.services.plymouth-quit = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "display-manager.service" ];
+ };
+ systemd.services.plymouth-poweroff.wantedBy = [ "poweroff.target" ];
+ systemd.services.plymouth-reboot.wantedBy = [ "reboot.target" ];
+ systemd.services.plymouth-read-write.wantedBy = [ "sysinit.target" ];
+
+ boot.initrd.extraUtilsCommands = ''
+ copy_bin_and_libs ${pkgs.plymouth}/bin/plymouthd
+ copy_bin_and_libs ${pkgs.plymouth}/bin/plymouth
+
+ moduleName="$(sed -n 's,ModuleName *= *,,p' ${themesEnv}/share/plymouth/themes/${cfg.theme}/${cfg.theme}.plymouth)"
+
+ mkdir -p $out/lib/plymouth/renderers
+ # module might come from a theme
+ cp ${themesEnv}/lib/plymouth/{text,details,$moduleName}.so $out/lib/plymouth
+ cp ${plymouth}/lib/plymouth/renderers/{drm,frame-buffer}.so $out/lib/plymouth/renderers
+
+ mkdir -p $out/share/plymouth/themes
+ cp ${plymouth}/share/plymouth/plymouthd.defaults $out/share/plymouth
+
+ # copy themes into working directory for patching
+ mkdir themes
+ # use -L to copy the directories proper, not the symlinks to them
+ cp -r -L ${themesEnv}/share/plymouth/themes/{text,details,${cfg.theme}} themes
+
+ # patch out any attempted references to the theme or plymouth's themes directory
+ chmod -R +w themes
+ find themes -type f | while read file
+ do
+ sed -i "s,/nix/.*/share/plymouth/themes,$out/share/plymouth/themes,g" $file
+ done
+
+ cp -r themes/* $out/share/plymouth/themes
+ cp ${cfg.logo} $out/share/plymouth/logo.png
+ '';
+
+ boot.initrd.extraUtilsCommandsTest = ''
+ $out/bin/plymouthd --help >/dev/null
+ $out/bin/plymouth --help >/dev/null
+ '';
+
+ boot.initrd.extraUdevRulesCommands = ''
+ cp ${config.systemd.package}/lib/udev/rules.d/{70-uaccess,71-seat}.rules $out
+ sed -i '/loginctl/d' $out/71-seat.rules
+ '';
+
+ # We use `mkAfter` to ensure that LUKS password prompt would be shown earlier than the splash screen.
+ boot.initrd.preLVMCommands = mkAfter ''
+ mkdir -p /etc/plymouth
+ ln -s ${configFile} /etc/plymouth/plymouthd.conf
+ ln -s $extraUtils/share/plymouth/plymouthd.defaults /etc/plymouth/plymouthd.defaults
+ ln -s $extraUtils/share/plymouth/logo.png /etc/plymouth/logo.png
+ ln -s $extraUtils/share/plymouth/themes /etc/plymouth/themes
+ ln -s $extraUtils/lib/plymouth /etc/plymouth/plugins
+
+ plymouthd --mode=boot --pid-file=/run/plymouth/pid --attach-to-session
+ plymouth show-splash
+ '';
+
+ boot.initrd.postMountCommands = ''
+ plymouth update-root-fs --new-root-dir="$targetRoot"
+ '';
+
+ # `mkBefore` to ensure that any custom prompts would be visible.
+ boot.initrd.preFailCommands = mkBefore ''
+ plymouth quit --wait
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/resolved.nix b/nixpkgs/nixos/modules/system/boot/resolved.nix
new file mode 100644
index 00000000000..3ea96f8e464
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/resolved.nix
@@ -0,0 +1,174 @@
+{ config, lib, ... }:
+
+with lib;
+let
+ cfg = config.services.resolved;
+
+ dnsmasqResolve = config.services.dnsmasq.enable &&
+ config.services.dnsmasq.resolveLocalQueries;
+
+in
+{
+
+ options = {
+
+ services.resolved.enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable the systemd DNS resolver daemon.
+ '';
+ };
+
+ services.resolved.fallbackDns = mkOption {
+ default = [ ];
+ example = [ "8.8.8.8" "2001:4860:4860::8844" ];
+ type = types.listOf types.str;
+ description = ''
+ A list of IPv4 and IPv6 addresses to use as the fallback DNS servers.
+ If this option is empty, a compiled-in list of DNS servers is used instead.
+ '';
+ };
+
+ services.resolved.domains = mkOption {
+ default = config.networking.search;
+ example = [ "example.com" ];
+ type = types.listOf types.str;
+ description = ''
+ A list of domains. These domains are used as search suffixes
+ when resolving single-label host names (domain names which
+ contain no dot), in order to qualify them into fully-qualified
+ domain names (FQDNs).
+
+ For compatibility reasons, if this setting is not specified,
+ the search domains listed in
+ <filename>/etc/resolv.conf</filename> are used instead, if
+ that file exists and any domains are configured in it.
+ '';
+ };
+
+ services.resolved.llmnr = mkOption {
+ default = "true";
+ example = "false";
+ type = types.enum [ "true" "resolve" "false" ];
+ description = ''
+ Controls Link-Local Multicast Name Resolution support
+ (RFC 4795) on the local host.
+
+ If set to
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>"true"</literal></term>
+ <listitem><para>
+ Enables full LLMNR responder and resolver support.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>"false"</literal></term>
+ <listitem><para>
+ Disables both.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>"resolve"</literal></term>
+ <listitem><para>
+ Only resolution support is enabled, but responding is disabled.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+
+ services.resolved.dnssec = mkOption {
+ default = "allow-downgrade";
+ example = "true";
+ type = types.enum [ "true" "allow-downgrade" "false" ];
+ description = ''
+ If set to
+ <variablelist>
+ <varlistentry>
+ <term><literal>"true"</literal></term>
+ <listitem><para>
+ all DNS lookups are DNSSEC-validated locally (excluding
+ LLMNR and Multicast DNS). Note that this mode requires a
+ DNS server that supports DNSSEC. If the DNS server does
+ not properly support DNSSEC all validations will fail.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>"allow-downgrade"</literal></term>
+ <listitem><para>
+ DNSSEC validation is attempted, but if the server does not
+ support DNSSEC properly, DNSSEC mode is automatically
+ disabled. Note that this mode makes DNSSEC validation
+ vulnerable to "downgrade" attacks, where an attacker might
+ be able to trigger a downgrade to non-DNSSEC mode by
+ synthesizing a DNS response that suggests DNSSEC was not
+ supported.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>"false"</literal></term>
+ <listitem><para>
+ DNS lookups are not DNSSEC validated.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+
+ services.resolved.extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra config to append to resolved.conf.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = !config.networking.useHostResolvConf;
+ message = "Using host resolv.conf is not supported with systemd-resolved";
+ }
+ ];
+
+ systemd.additionalUpstreamSystemUnits = [
+ "systemd-resolved.service"
+ ];
+
+ systemd.services.systemd-resolved = {
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ];
+ };
+
+ environment.etc = {
+ "systemd/resolved.conf".text = ''
+ [Resolve]
+ ${optionalString (config.networking.nameservers != [])
+ "DNS=${concatStringsSep " " config.networking.nameservers}"}
+ ${optionalString (cfg.fallbackDns != [])
+ "FallbackDNS=${concatStringsSep " " cfg.fallbackDns}"}
+ ${optionalString (cfg.domains != [])
+ "Domains=${concatStringsSep " " cfg.domains}"}
+ LLMNR=${cfg.llmnr}
+ DNSSEC=${cfg.dnssec}
+ ${config.services.resolved.extraConfig}
+ '';
+
+ # symlink the dynamic stub resolver of resolv.conf as recommended by upstream:
+ # https://www.freedesktop.org/software/systemd/man/systemd-resolved.html#/etc/resolv.conf
+ "resolv.conf".source = "/run/systemd/resolve/stub-resolv.conf";
+ } // optionalAttrs dnsmasqResolve {
+ "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf";
+ };
+
+ # If networkmanager is enabled, ask it to interface with resolved.
+ networking.networkmanager.dns = "systemd-resolved";
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/shutdown.nix b/nixpkgs/nixos/modules/system/boot/shutdown.nix
new file mode 100644
index 00000000000..11041066e07
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/shutdown.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ # This unit saves the value of the system clock to the hardware
+ # clock on shutdown.
+ systemd.services.save-hwclock =
+ { description = "Save Hardware Clock";
+
+ wantedBy = [ "shutdown.target" ];
+
+ unitConfig = {
+ DefaultDependencies = false;
+ ConditionPathExists = "/dev/rtc";
+ };
+
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.utillinux}/sbin/hwclock --systohc ${if config.time.hardwareClockInLocalTime then "--localtime" else "--utc"}";
+ };
+ };
+
+ boot.kernel.sysctl."kernel.poweroff_cmd" = "${config.systemd.package}/sbin/poweroff";
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/stage-1-init.sh b/nixpkgs/nixos/modules/system/boot/stage-1-init.sh
new file mode 100644
index 00000000000..b817a45deba
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/stage-1-init.sh
@@ -0,0 +1,572 @@
+#! @shell@
+
+targetRoot=/mnt-root
+console=tty1
+
+extraUtils="@extraUtils@"
+export LD_LIBRARY_PATH=@extraUtils@/lib
+export PATH=@extraUtils@/bin
+ln -s @extraUtils@/bin /bin
+
+# Copy the secrets to their needed location
+if [ -d "@extraUtils@/secrets" ]; then
+ for secret in $(cd "@extraUtils@/secrets"; find . -type f); do
+ mkdir -p $(dirname "/$secret")
+ ln -s "@extraUtils@/secrets/$secret" "$secret"
+ done
+fi
+
+# Stop LVM complaining about fd3
+export LVM_SUPPRESS_FD_WARNINGS=true
+
+fail() {
+ if [ -n "$panicOnFail" ]; then exit 1; fi
+
+ @preFailCommands@
+
+ # If starting stage 2 failed, allow the user to repair the problem
+ # in an interactive shell.
+ cat <<EOF
+
+An error occurred in stage 1 of the boot process, which must mount the
+root filesystem on \`$targetRoot' and then start stage 2. Press one
+of the following keys:
+
+EOF
+ if [ -n "$allowShell" ]; then cat <<EOF
+ i) to launch an interactive shell
+ f) to start an interactive shell having pid 1 (needed if you want to
+ start stage 2's init manually)
+EOF
+ fi
+ cat <<EOF
+ r) to reboot immediately
+ *) to ignore the error and continue
+EOF
+
+ read -n 1 reply
+
+ if [ -n "$allowShell" -a "$reply" = f ]; then
+ exec setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console"
+ elif [ -n "$allowShell" -a "$reply" = i ]; then
+ echo "Starting interactive shell..."
+ setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console" || fail
+ elif [ "$reply" = r ]; then
+ echo "Rebooting..."
+ reboot -f
+ else
+ echo "Continuing..."
+ fi
+}
+
+trap 'fail' 0
+
+
+# Print a greeting.
+echo
+echo "<<< NixOS Stage 1 >>>"
+echo
+
+# Make several required directories.
+mkdir -p /etc/udev
+touch /etc/fstab # to shut up mount
+ln -s /proc/mounts /etc/mtab # to shut up mke2fs
+touch /etc/udev/hwdb.bin # to shut up udev
+touch /etc/initrd-release
+
+# Function for waiting a device to appear.
+waitDevice() {
+ local device="$1"
+
+ # USB storage devices tend to appear with some delay. It would be
+ # great if we had a way to synchronously wait for them, but
+ # alas... So just wait for a few seconds for the device to
+ # appear.
+ if test ! -e $device; then
+ echo -n "waiting for device $device to appear..."
+ try=20
+ while [ $try -gt 0 ]; do
+ sleep 1
+ # also re-try lvm activation now that new block devices might have appeared
+ lvm vgchange -ay
+ # and tell udev to create nodes for the new LVs
+ udevadm trigger --action=add
+ if test -e $device; then break; fi
+ echo -n "."
+ try=$((try - 1))
+ done
+ echo
+ [ $try -ne 0 ]
+ fi
+}
+
+# Mount special file systems.
+specialMount() {
+ local device="$1"
+ local mountPoint="$2"
+ local options="$3"
+ local fsType="$4"
+
+ mkdir -m 0755 -p "$mountPoint"
+ mount -n -t "$fsType" -o "$options" "$device" "$mountPoint"
+}
+source @earlyMountScript@
+
+# Log the script output to /dev/kmsg or /run/log/stage-1-init.log.
+mkdir -p /tmp
+mkfifo /tmp/stage-1-init.log.fifo
+logOutFd=8 && logErrFd=9
+eval "exec $logOutFd>&1 $logErrFd>&2"
+if test -w /dev/kmsg; then
+ tee -i < /tmp/stage-1-init.log.fifo /proc/self/fd/"$logOutFd" | while read -r line; do
+ if test -n "$line"; then
+ echo "<7>stage-1-init: $line" > /dev/kmsg
+ fi
+ done &
+else
+ mkdir -p /run/log
+ tee -i < /tmp/stage-1-init.log.fifo /run/log/stage-1-init.log &
+fi
+exec > /tmp/stage-1-init.log.fifo 2>&1
+
+
+# Process the kernel command line.
+export stage2Init=/init
+for o in $(cat /proc/cmdline); do
+ case $o in
+ console=*)
+ set -- $(IFS==; echo $o)
+ params=$2
+ set -- $(IFS=,; echo $params)
+ console=$1
+ ;;
+ init=*)
+ set -- $(IFS==; echo $o)
+ stage2Init=$2
+ ;;
+ boot.trace|debugtrace)
+ # Show each command.
+ set -x
+ ;;
+ boot.shell_on_fail)
+ allowShell=1
+ ;;
+ boot.debug1|debug1) # stop right away
+ allowShell=1
+ fail
+ ;;
+ boot.debug1devices) # stop after loading modules and creating device nodes
+ allowShell=1
+ debug1devices=1
+ ;;
+ boot.debug1mounts) # stop after mounting file systems
+ allowShell=1
+ debug1mounts=1
+ ;;
+ boot.panic_on_fail|stage1panic=1)
+ panicOnFail=1
+ ;;
+ root=*)
+ # If a root device is specified on the kernel command
+ # line, make it available through the symlink /dev/root.
+ # Recognise LABEL= and UUID= to support UNetbootin.
+ set -- $(IFS==; echo $o)
+ if [ $2 = "LABEL" ]; then
+ root="/dev/disk/by-label/$3"
+ elif [ $2 = "UUID" ]; then
+ root="/dev/disk/by-uuid/$3"
+ else
+ root=$2
+ fi
+ ln -s "$root" /dev/root
+ ;;
+ copytoram)
+ copytoram=1
+ ;;
+ esac
+done
+
+# Set hostid before modules are loaded.
+# This is needed by the spl/zfs modules.
+@setHostId@
+
+# Load the required kernel modules.
+mkdir -p /lib
+ln -s @modulesClosure@/lib/modules /lib/modules
+ln -s @modulesClosure@/lib/firmware /lib/firmware
+echo @extraUtils@/bin/modprobe > /proc/sys/kernel/modprobe
+for i in @kernelModules@; do
+ echo "loading module $(basename $i)..."
+ modprobe $i
+done
+
+
+# Create device nodes in /dev.
+@preDeviceCommands@
+echo "running udev..."
+mkdir -p /etc/udev
+ln -sfn @udevRules@ /etc/udev/rules.d
+mkdir -p /dev/.mdadm
+systemd-udevd --daemon
+udevadm trigger --action=add
+udevadm settle
+
+
+# XXX: Use case usb->lvm will still fail, usb->luks->lvm is covered
+@preLVMCommands@
+
+
+echo "starting device mapper and LVM..."
+lvm vgchange -ay
+
+if test -n "$debug1devices"; then fail; fi
+
+
+@postDeviceCommands@
+
+
+# Return true if the machine is on AC power, or if we can't determine
+# whether it's on AC power.
+onACPower() {
+ ! test -d "/proc/acpi/battery" ||
+ ! ls /proc/acpi/battery/BAT[0-9]* > /dev/null 2>&1 ||
+ ! cat /proc/acpi/battery/BAT*/state | grep "^charging state" | grep -q "discharg"
+}
+
+
+# Check the specified file system, if appropriate.
+checkFS() {
+ local device="$1"
+ local fsType="$2"
+
+ # Only check block devices.
+ if [ ! -b "$device" ]; then return 0; fi
+
+ # Don't check ROM filesystems.
+ 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 -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
+
+ # Skip fsck for inherently readonly filesystems.
+ if [ "$fsType" = squashfs ]; then return 0; fi
+
+ # If we couldn't figure out the FS type, then skip fsck.
+ if [ "$fsType" = auto ]; then
+ echo 'cannot check filesystem with type "auto"!'
+ return 0
+ fi
+
+ # Device might be already mounted manually
+ # e.g. NBD-device or the host filesystem of the file which contains encrypted root fs
+ if mount | grep -q "^$device on "; then
+ echo "skip checking already mounted $device"
+ return 0
+ fi
+
+ # Optionally, skip fsck on journaling filesystems. This option is
+ # a hack - it's mostly because e2fsck on ext3 takes much longer to
+ # recover the journal than the ext3 implementation in the kernel
+ # does (minutes versus seconds).
+ if test -z "@checkJournalingFS@" -a \
+ \( "$fsType" = ext3 -o "$fsType" = ext4 -o "$fsType" = reiserfs \
+ -o "$fsType" = xfs -o "$fsType" = jfs -o "$fsType" = f2fs \)
+ then
+ return 0
+ fi
+
+ # Don't run `fsck' if the machine is on battery power. !!! Is
+ # this a good idea?
+ if ! onACPower; then
+ echo "on battery power, so no \`fsck' will be performed on \`$device'"
+ return 0
+ fi
+
+ echo "checking $device..."
+
+ fsckFlags=
+ if test "$fsType" != "btrfs"; then
+ fsckFlags="-V -a"
+ fi
+ fsck $fsckFlags "$device"
+ fsckResult=$?
+
+ if test $(($fsckResult | 2)) = $fsckResult; then
+ echo "fsck finished, rebooting..."
+ sleep 3
+ reboot -f
+ fi
+
+ if test $(($fsckResult | 4)) = $fsckResult; then
+ echo "$device has unrepaired errors, please fix them manually."
+ fail
+ fi
+
+ if test $fsckResult -ge 8; then
+ echo "fsck on $device failed."
+ fail
+ fi
+
+ return 0
+}
+
+
+# Function for mounting a file system.
+mountFS() {
+ local device="$1"
+ local mountPoint="$2"
+ local options="$3"
+ local fsType="$4"
+
+ if [ "$fsType" = auto ]; then
+ fsType=$(blkid -o value -s TYPE "$device")
+ if [ -z "$fsType" ]; then fsType=auto; fi
+ fi
+
+ # Filter out x- options, which busybox doesn't do yet.
+ local optionsFiltered="$(IFS=,; for i in $options; do if [ "${i:0:2}" != "x-" ]; then echo -n $i,; fi; done)"
+
+ echo "$device /mnt-root$mountPoint $fsType $optionsFiltered" >> /etc/fstab
+
+ checkFS "$device" "$fsType"
+
+ # Optionally resize the filesystem.
+ case $options in
+ *x-nixos.autoresize*)
+ if [ "$fsType" = ext2 -o "$fsType" = ext3 -o "$fsType" = ext4 ]; then
+ echo "resizing $device..."
+ e2fsck -fp "$device"
+ resize2fs "$device"
+ elif [ "$fsType" = f2fs ]; then
+ echo "resizing $device..."
+ fsck.f2fs -fp "$device"
+ resize.f2fs "$device"
+ fi
+ ;;
+ esac
+
+ # Create backing directories for unionfs-fuse.
+ if [ "$fsType" = unionfs-fuse ]; then
+ for i in $(IFS=:; echo ${options##*,dirs=}); do
+ mkdir -m 0700 -p /mnt-root"${i%=*}"
+ done
+ fi
+
+ echo "mounting $device on $mountPoint..."
+
+ mkdir -p "/mnt-root$mountPoint"
+
+ # For CIFS mounts, retry a few times before giving up.
+ local n=0
+ while true; do
+ mount "/mnt-root$mountPoint" && break
+ if [ "$fsType" != cifs -o "$n" -ge 10 ]; then fail; break; fi
+ echo "retrying..."
+ n=$((n + 1))
+ done
+
+ [ "$mountPoint" == "/" ] &&
+ [ -f "/mnt-root/etc/NIXOS_LUSTRATE" ] &&
+ lustrateRoot "/mnt-root"
+
+ true
+}
+
+lustrateRoot () {
+ local root="$1"
+
+ echo
+ echo -e "\e[1;33m<<< NixOS is now lustrating the root filesystem (cruft goes to /old-root) >>>\e[0m"
+ echo
+
+ mkdir -m 0755 -p "$root/old-root.tmp"
+
+ echo
+ echo "Moving impurities out of the way:"
+ for d in "$root"/*
+ do
+ [ "$d" == "$root/nix" ] && continue
+ [ "$d" == "$root/boot" ] && continue # Don't render the system unbootable
+ [ "$d" == "$root/old-root.tmp" ] && continue
+
+ mv -v "$d" "$root/old-root.tmp"
+ done
+
+ # Use .tmp to make sure subsequent invokations don't clash
+ mv -v "$root/old-root.tmp" "$root/old-root"
+
+ mkdir -m 0755 -p "$root/etc"
+ touch "$root/etc/NIXOS"
+
+ exec 4< "$root/old-root/etc/NIXOS_LUSTRATE"
+
+ echo
+ echo "Restoring selected impurities:"
+ while read -u 4 keeper; do
+ dirname="$(dirname "$keeper")"
+ mkdir -m 0755 -p "$root/$dirname"
+ cp -av "$root/old-root/$keeper" "$root/$keeper"
+ done
+
+ exec 4>&-
+}
+
+
+
+if test -e /sys/power/resume -a -e /sys/power/disk; then
+ if test -n "@resumeDevice@" && waitDevice "@resumeDevice@"; then
+ resumeDev="@resumeDevice@"
+ resumeInfo="$(udevadm info -q property "$resumeDev" )"
+ else
+ for sd in @resumeDevices@; do
+ # Try to detect resume device. According to Ubuntu bug:
+ # https://bugs.launchpad.net/ubuntu/+source/pm-utils/+bug/923326/comments/1
+ # when there are multiple swap devices, we can't know where the hibernate
+ # image will reside. We can check all of them for swsuspend blkid.
+ if waitDevice "$sd"; then
+ resumeInfo="$(udevadm info -q property "$sd")"
+ if [ "$(echo "$resumeInfo" | sed -n 's/^ID_FS_TYPE=//p')" = "swsuspend" ]; then
+ resumeDev="$sd"
+ break
+ fi
+ fi
+ done
+ fi
+ if test -n "$resumeDev"; then
+ resumeMajor="$(echo "$resumeInfo" | sed -n 's/^MAJOR=//p')"
+ resumeMinor="$(echo "$resumeInfo" | sed -n 's/^MINOR=//p')"
+ echo "$resumeMajor:$resumeMinor" > /sys/power/resume 2> /dev/null || echo "failed to resume..."
+ fi
+fi
+
+
+# Try to find and mount the root device.
+mkdir -p $targetRoot
+
+exec 3< @fsInfo@
+
+while read -u 3 mountPoint; do
+ read -u 3 device
+ read -u 3 fsType
+ read -u 3 options
+
+ # !!! Really quick hack to support bind mounts, i.e., where the
+ # "device" should be taken relative to /mnt-root, not /. Assume
+ # that every device that starts with / but doesn't start with /dev
+ # is a bind mount.
+ pseudoDevice=
+ case $device in
+ /dev/*)
+ ;;
+ //*)
+ # Don't touch SMB/CIFS paths.
+ pseudoDevice=1
+ ;;
+ /*)
+ device=/mnt-root$device
+ ;;
+ *)
+ # Not an absolute path; assume that it's a pseudo-device
+ # like an NFS path (e.g. "server:/path").
+ pseudoDevice=1
+ ;;
+ esac
+
+ if test -z "$pseudoDevice" && ! waitDevice "$device"; then
+ # If it doesn't appear, try to mount it anyway (and
+ # probably fail). This is a fallback for non-device "devices"
+ # that we don't properly recognise.
+ echo "Timed out waiting for device $device, trying to mount anyway."
+ fi
+
+ # Wait once more for the udev queue to empty, just in case it's
+ # doing something with $device right now.
+ udevadm settle
+
+ # If copytoram is enabled: skip mounting the ISO and copy its content to a tmpfs.
+ if [ -n "$copytoram" ] && [ "$device" = /dev/root ] && [ "$mountPoint" = /iso ]; then
+ fsType=$(blkid -o value -s TYPE "$device")
+ fsSize=$(blockdev --getsize64 "$device")
+
+ mkdir -p /tmp-iso
+ mount -t "$fsType" /dev/root /tmp-iso
+ mountFS tmpfs /iso size="$fsSize" tmpfs
+
+ cp -r /tmp-iso/* /mnt-root/iso/
+
+ umount /tmp-iso
+ rmdir /tmp-iso
+ continue
+ fi
+
+ mountFS "$device" "$mountPoint" "$options" "$fsType"
+done
+
+exec 3>&-
+
+
+@postMountCommands@
+
+
+# Emit a udev rule for /dev/root to prevent systemd from complaining.
+if [ -e /mnt-root/iso ]; then
+ eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=/mnt-root/iso)
+else
+ eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=$targetRoot)
+fi
+if [ "$ROOT_MAJOR" -a "$ROOT_MINOR" -a "$ROOT_MAJOR" != 0 ]; then
+ mkdir -p /run/udev/rules.d
+ echo 'ACTION=="add|change", SUBSYSTEM=="block", ENV{MAJOR}=="'$ROOT_MAJOR'", ENV{MINOR}=="'$ROOT_MINOR'", SYMLINK+="root"' > /run/udev/rules.d/61-dev-root-link.rules
+fi
+
+
+# Stop udevd.
+udevadm control --exit
+
+# Reset the logging file descriptors.
+# Do this just before pkill, which will kill the tee process.
+exec 1>&$logOutFd 2>&$logErrFd
+eval "exec $logOutFd>&- $logErrFd>&-"
+
+# Kill any remaining processes, just to be sure we're not taking any
+# with us into stage 2. But keep storage daemons like unionfs-fuse.
+#
+# Storage daemons are distinguished by an @ in front of their command line:
+# https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
+for pid in $(pgrep -v -f '^@'); do
+ # Make sure we don't kill kernel processes, see #15226 and:
+ # http://stackoverflow.com/questions/12213445/identifying-kernel-threads
+ readlink "/proc/$pid/exe" &> /dev/null || continue
+ # Try to avoid killing ourselves.
+ [ $pid -eq $$ ] && continue
+ kill -9 "$pid"
+done
+
+if test -n "$debug1mounts"; then fail; fi
+
+
+# Restore /proc/sys/kernel/modprobe to its original value.
+echo /sbin/modprobe > /proc/sys/kernel/modprobe
+
+
+# Start stage 2. `switch_root' deletes all files in the ramfs on the
+# current root. Note that $stage2Init might be an absolute symlink,
+# in which case "-e" won't work because we're not in the chroot yet.
+if [ ! -e "$targetRoot/$stage2Init" ] && [ ! -L "$targetRoot/$stage2Init" ] ; then
+ echo "stage 2 init script ($targetRoot/$stage2Init) not found"
+ fail
+fi
+
+mkdir -m 0755 -p $targetRoot/proc $targetRoot/sys $targetRoot/dev $targetRoot/run
+
+mount --move /proc $targetRoot/proc
+mount --move /sys $targetRoot/sys
+mount --move /dev $targetRoot/dev
+mount --move /run $targetRoot/run
+
+exec env -i $(type -P switch_root) "$targetRoot" "$stage2Init"
+
+fail # should never be reached
diff --git a/nixpkgs/nixos/modules/system/boot/stage-1.nix b/nixpkgs/nixos/modules/system/boot/stage-1.nix
new file mode 100644
index 00000000000..4c2d130d5a5
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/stage-1.nix
@@ -0,0 +1,570 @@
+# This module builds the initial ramdisk, which contains an init
+# script that performs the first stage of booting the system: it loads
+# the modules necessary to mount the root file system, then calls the
+# init in the root file system to start the second boot stage.
+
+{ config, lib, utils, pkgs, ... }:
+
+with lib;
+
+let
+
+ udev = config.systemd.package;
+
+ kernel-name = config.boot.kernelPackages.kernel.name or "kernel";
+
+ modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; };
+ firmware = config.hardware.firmware;
+
+
+ # Determine the set of modules that we need to mount the root FS.
+ modulesClosure = pkgs.makeModulesClosure {
+ rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules;
+ kernel = modulesTree;
+ firmware = firmware;
+ allowMissing = true;
+ };
+
+
+ # The initrd only has to mount `/` or any FS marked as necessary for
+ # booting (such as the FS containing `/nix/store`, or an FS needed for
+ # mounting `/`, like `/` on a loopback).
+ fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
+
+ # A utility for enumerating the shared-library dependencies of a program
+ findLibs = pkgs.buildPackages.writeShellScriptBin "find-libs" ''
+ set -euo pipefail
+
+ declare -A seen
+ declare -a left
+
+ patchelf="${pkgs.buildPackages.patchelf}/bin/patchelf"
+
+ function add_needed {
+ rpath="$($patchelf --print-rpath $1)"
+ dir="$(dirname $1)"
+ for lib in $($patchelf --print-needed $1); do
+ left+=("$lib" "$rpath" "$dir")
+ done
+ }
+
+ add_needed $1
+
+ while [ ''${#left[@]} -ne 0 ]; do
+ next=''${left[0]}
+ rpath=''${left[1]}
+ ORIGIN=''${left[2]}
+ left=("''${left[@]:3}")
+ if [ -z ''${seen[$next]+x} ]; then
+ seen[$next]=1
+
+ # Ignore the dynamic linker which for some reason appears as a DT_NEEDED of glibc but isn't in glibc's RPATH.
+ case "$next" in
+ ld*.so.?) continue;;
+ esac
+
+ IFS=: read -ra paths <<< $rpath
+ res=
+ for path in "''${paths[@]}"; do
+ path=$(eval "echo $path")
+ if [ -f "$path/$next" ]; then
+ res="$path/$next"
+ echo "$res"
+ add_needed "$res"
+ break
+ fi
+ done
+ if [ -z "$res" ]; then
+ echo "Couldn't satisfy dependency $next" >&2
+ exit 1
+ fi
+ fi
+ done
+ '';
+
+ # Some additional utilities needed in stage 1, like mount, lvm, fsck
+ # etc. We don't want to bring in all of those packages, so we just
+ # copy what we need. Instead of using statically linked binaries,
+ # we just copy what we need from Glibc and use patchelf to make it
+ # work.
+ extraUtils = pkgs.runCommandCC "extra-utils"
+ { nativeBuildInputs = [pkgs.buildPackages.nukeReferences];
+ allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd
+ }
+ ''
+ set +o pipefail
+
+ mkdir -p $out/bin $out/lib
+ ln -s $out/bin $out/sbin
+
+ copy_bin_and_libs () {
+ [ -f "$out/bin/$(basename $1)" ] && rm "$out/bin/$(basename $1)"
+ cp -pdv $1 $out/bin
+ }
+
+ # Copy BusyBox.
+ for BIN in ${pkgs.busybox}/{s,}bin/*; do
+ copy_bin_and_libs $BIN
+ done
+
+ # Copy some utillinux stuff.
+ copy_bin_and_libs ${pkgs.utillinux}/sbin/blkid
+
+ # Copy dmsetup and lvm.
+ copy_bin_and_libs ${pkgs.lvm2}/sbin/dmsetup
+ copy_bin_and_libs ${pkgs.lvm2}/sbin/lvm
+
+ # Add RAID mdadm tool.
+ copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm
+ copy_bin_and_libs ${pkgs.mdadm}/sbin/mdmon
+
+ # Copy udev.
+ copy_bin_and_libs ${udev}/lib/systemd/systemd-udevd
+ copy_bin_and_libs ${udev}/bin/udevadm
+ for BIN in ${udev}/lib/udev/*_id; do
+ copy_bin_and_libs $BIN
+ done
+
+ # Copy modprobe.
+ copy_bin_and_libs ${pkgs.kmod}/bin/kmod
+ ln -sf kmod $out/bin/modprobe
+
+ # 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
+ ''}
+
+ # Copy secrets if needed.
+ ${optionalString (!config.boot.loader.supportsInitrdSecrets)
+ (concatStringsSep "\n" (mapAttrsToList (dest: source:
+ let source' = if source == null then dest else source; in
+ ''
+ mkdir -p $(dirname "$out/secrets/${dest}")
+ cp -a ${source'} "$out/secrets/${dest}"
+ ''
+ ) config.boot.initrd.secrets))
+ }
+
+ ${config.boot.initrd.extraUtilsCommands}
+
+ # Copy ld manually since it isn't detected correctly
+ 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
+ echo "Copying libs for executable $BIN"
+ for LIB in $(${findLibs}/bin/find-libs $BIN); do
+ TGT="$out/lib/$(basename $LIB)"
+ if [ ! -f "$TGT" ]; then
+ SRC="$(readlink -e $LIB)"
+ cp -pdv "$SRC" "$TGT"
+ fi
+ done
+ done
+
+ # Strip binaries further than normal.
+ chmod -R u+w $out
+ stripDirs "$STRIP" "lib bin" "-s"
+
+ # Run patchelf to make the programs refer to the copied libraries.
+ find $out/bin $out/lib -type f | while read i; do
+ if ! test -L $i; then
+ nuke-refs -e $out $i
+ fi
+ done
+
+ find $out/bin -type f | while read i; do
+ if ! test -L $i; then
+ echo "patching $i..."
+ patchelf --set-interpreter $out/lib/ld*.so.? --set-rpath $out/lib $i || true
+ fi
+ done
+
+ if [ -z "${toString (pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform)}" ]; then
+ # Make sure that the patchelf'ed binaries still work.
+ echo "testing patched programs..."
+ $out/bin/ash -c 'echo hello world' | grep "hello world"
+ export LD_LIBRARY_PATH=$out/lib
+ $out/bin/mount --help 2>&1 | grep -q "BusyBox"
+ $out/bin/blkid -V 2>&1 | grep -q 'libblkid'
+ $out/bin/udevadm --version
+ $out/bin/dmsetup --version 2>&1 | tee -a log | grep -q "version:"
+ LVM_SYSTEM_DIR=$out $out/bin/lvm version 2>&1 | tee -a log | grep -q "LVM"
+ $out/bin/mdadm --version
+
+ ${config.boot.initrd.extraUtilsCommandsTest}
+ fi
+ ''; # */
+
+
+ udevRules = pkgs.runCommand "udev-rules" {
+ allowedReferences = [ extraUtils ];
+ preferLocalBuild = true;
+ } ''
+ mkdir -p $out
+
+ echo 'ENV{LD_LIBRARY_PATH}="${extraUtils}/lib"' > $out/00-env.rules
+
+ cp -v ${udev}/lib/udev/rules.d/60-cdrom_id.rules $out/
+ cp -v ${udev}/lib/udev/rules.d/60-persistent-storage.rules $out/
+ cp -v ${udev}/lib/udev/rules.d/80-drivers.rules $out/
+ cp -v ${pkgs.lvm2}/lib/udev/rules.d/*.rules $out/
+ ${config.boot.initrd.extraUdevRulesCommands}
+
+ for i in $out/*.rules; do
+ substituteInPlace $i \
+ --replace ata_id ${extraUtils}/bin/ata_id \
+ --replace scsi_id ${extraUtils}/bin/scsi_id \
+ --replace cdrom_id ${extraUtils}/bin/cdrom_id \
+ --replace ${pkgs.coreutils}/bin/basename ${extraUtils}/bin/basename \
+ --replace ${pkgs.utillinux}/bin/blkid ${extraUtils}/bin/blkid \
+ --replace ${pkgs.lvm2}/sbin ${extraUtils}/bin \
+ --replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \
+ --replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \
+ --replace ${udev}/bin/udevadm ${extraUtils}/bin/udevadm
+ done
+
+ # Work around a bug in QEMU, which doesn't implement the "READ
+ # DISC INFORMATION" SCSI command:
+ # https://bugzilla.redhat.com/show_bug.cgi?id=609049
+ # As a result, `cdrom_id' doesn't print
+ # ID_CDROM_MEDIA_TRACK_COUNT_DATA, which in turn prevents the
+ # /dev/disk/by-label symlinks from being created. We need these
+ # in the NixOS installation CD, so use ID_CDROM_MEDIA in the
+ # corresponding udev rules for now. This was the behaviour in
+ # udev <= 154. See also
+ # http://www.spinics.net/lists/hotplug/msg03935.html
+ substituteInPlace $out/60-persistent-storage.rules \
+ --replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA
+ ''; # */
+
+
+ # The init script of boot stage 1 (loading kernel modules for
+ # mounting the root FS).
+ bootStage1 = pkgs.substituteAll {
+ src = ./stage-1-init.sh;
+
+ shell = "${extraUtils}/bin/ash";
+
+ isExecutable = true;
+
+ postInstall = ''
+ echo checking syntax
+ # check both with bash
+ ${pkgs.buildPackages.bash}/bin/sh -n $target
+ # and with ash shell, just in case
+ ${pkgs.buildPackages.busybox}/bin/ash -n $target
+ '';
+
+ inherit udevRules extraUtils modulesClosure;
+
+ inherit (config.boot) resumeDevice;
+
+ inherit (config.system.build) earlyMountScript;
+
+ inherit (config.boot.initrd) checkJournalingFS
+ preLVMCommands preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules;
+
+ resumeDevices = map (sd: if sd ? device then sd.device else "/dev/disk/by-label/${sd.label}")
+ (filter (sd: hasPrefix "/dev/" sd.device && !sd.randomEncryption.enable
+ # Don't include zram devices
+ && !(hasPrefix "/dev/zram" sd.device)
+ ) config.swapDevices);
+
+ fsInfo =
+ let f = fs: [ fs.mountPoint (if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}") fs.fsType (builtins.concatStringsSep "," fs.options) ];
+ in pkgs.writeText "initrd-fsinfo" (concatStringsSep "\n" (concatMap f fileSystems));
+
+ setHostId = optionalString (config.networking.hostId != null) ''
+ hi="${config.networking.hostId}"
+ ${if pkgs.stdenv.isBigEndian then ''
+ echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > /etc/hostid
+ '' else ''
+ echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > /etc/hostid
+ ''}
+ '';
+ };
+
+
+ # The closure of the init script of boot stage 1 is what we put in
+ # the initial RAM disk.
+ initialRamdisk = pkgs.makeInitrd {
+ name = "initrd-${kernel-name}";
+ inherit (config.boot.initrd) compressor prepend;
+
+ contents =
+ [ { object = bootStage1;
+ symlink = "/init";
+ }
+ { object = pkgs.writeText "mdadm.conf" config.boot.initrd.mdadmConf;
+ symlink = "/etc/mdadm.conf";
+ }
+ { object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" {
+ src = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
+ preferLocalBuild = true;
+ } ''
+ target=$out
+ ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out
+ '';
+ symlink = "/etc/modprobe.d/ubuntu.conf";
+ }
+ { object = pkgs.kmod-debian-aliases;
+ symlink = "/etc/modprobe.d/debian.conf";
+ }
+ ];
+ };
+
+ # Script to add secret files to the initrd at bootloader update time
+ initialRamdiskSecretAppender =
+ pkgs.writeScriptBin "append-initrd-secrets"
+ ''
+ #!${pkgs.bash}/bin/bash -e
+ function usage {
+ echo "USAGE: $0 INITRD_FILE" >&2
+ echo "Appends this configuration's secrets to INITRD_FILE" >&2
+ }
+
+ if [ $# -ne 1 ]; then
+ usage
+ exit 1
+ fi
+
+ if [ "$1"x = "--helpx" ]; then
+ usage
+ exit 0
+ fi
+
+ ${lib.optionalString (config.boot.initrd.secrets == {})
+ "exit 0"}
+
+ export PATH=${pkgs.coreutils}/bin:${pkgs.cpio}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin
+
+ function cleanup {
+ if [ -n "$tmp" -a -d "$tmp" ]; then
+ rm -fR "$tmp"
+ fi
+ }
+ trap cleanup EXIT
+
+ tmp=$(mktemp -d initrd-secrets.XXXXXXXXXX)
+
+ ${lib.concatStringsSep "\n" (mapAttrsToList (dest: source:
+ let source' = if source == null then dest else toString source; in
+ ''
+ mkdir -p $(dirname "$tmp/${dest}")
+ cp -a ${source'} "$tmp/${dest}"
+ ''
+ ) config.boot.initrd.secrets)
+ }
+
+ (cd "$tmp" && find . | cpio -H newc -o) | gzip >>"$1"
+ '';
+
+in
+
+{
+ options = {
+
+ boot.resumeDevice = mkOption {
+ type = types.str;
+ default = "";
+ example = "/dev/sda3";
+ description = ''
+ Device for manual resume attempt during boot. This should be used primarily
+ if you want to resume from file. If left empty, the swap partitions are used.
+ Specify here the device where the file resides.
+ You should also use <varname>boot.kernelParams</varname> to specify
+ <literal><replaceable>resume_offset</replaceable></literal>.
+ '';
+ };
+
+ boot.initrd.prepend = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ Other initrd files to prepend to the final initrd we are building.
+ '';
+ };
+
+ boot.initrd.checkJournalingFS = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to run <command>fsck</command> on journaling filesystems such as ext3.
+ '';
+ };
+
+ boot.initrd.mdadmConf = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Contents of <filename>/etc/mdadm.conf</filename> in stage 1.
+ '';
+ };
+
+ boot.initrd.preLVMCommands = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands to be executed immediately before LVM discovery.
+ '';
+ };
+
+ boot.initrd.preDeviceCommands = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands to be executed before udev is started to create
+ device nodes.
+ '';
+ };
+
+ boot.initrd.postDeviceCommands = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands to be executed immediately after stage 1 of the
+ boot has loaded kernel modules and created device nodes in
+ <filename>/dev</filename>.
+ '';
+ };
+
+ boot.initrd.postMountCommands = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands to be executed immediately after the stage 1
+ filesystems have been mounted.
+ '';
+ };
+
+ boot.initrd.preFailCommands = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands to be executed before the failure prompt is shown.
+ '';
+ };
+
+ boot.initrd.extraUtilsCommands = mkOption {
+ internal = true;
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands to be executed in the builder of the
+ extra-utils derivation. This can be used to provide
+ additional utilities in the initial ramdisk.
+ '';
+ };
+
+ boot.initrd.extraUtilsCommandsTest = mkOption {
+ internal = true;
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands to be executed in the builder of the
+ extra-utils derivation after patchelf has done its
+ job. This can be used to test additional utilities
+ copied in extraUtilsCommands.
+ '';
+ };
+
+ boot.initrd.extraUdevRulesCommands = mkOption {
+ internal = true;
+ default = "";
+ type = types.lines;
+ description = ''
+ Shell commands to be executed in the builder of the
+ udev-rules derivation. This can be used to add
+ additional udev rules in the initial ramdisk.
+ '';
+ };
+
+ boot.initrd.compressor = mkOption {
+ internal = true;
+ default = "gzip -9n";
+ type = types.str;
+ description = "The compressor to use on the initrd image.";
+ example = "xz";
+ };
+
+ boot.initrd.secrets = mkOption
+ { internal = true;
+ default = {};
+ type = types.attrsOf (types.nullOr types.path);
+ description =
+ ''
+ Secrets to append to the initrd. The attribute name is the
+ path the secret should have inside the initrd, the value
+ is the path it should be copied from (or null for the same
+ path inside and out).
+ '';
+ example = literalExample
+ ''
+ { "/etc/dropbear/dropbear_rsa_host_key" =
+ ./secret-dropbear-key;
+ }
+ '';
+ };
+
+ boot.initrd.supportedFilesystems = mkOption {
+ default = [ ];
+ example = [ "btrfs" ];
+ type = types.listOf types.str;
+ description = "Names of supported filesystem types in the initial ramdisk.";
+ };
+
+ boot.loader.supportsInitrdSecrets = mkOption
+ { internal = true;
+ default = false;
+ type = types.bool;
+ description =
+ ''
+ Whether the bootloader setup runs append-initrd-secrets.
+ If not, any needed secrets must be copied into the initrd
+ and thus added to the store.
+ '';
+ };
+
+ fileSystems = mkOption {
+ 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>.
+ '';
+ };
+ });
+ };
+
+ };
+
+ config = mkIf (!config.boot.isContainer) {
+ assertions = [
+ { assertion = any (fs: fs.mountPoint == "/") fileSystems;
+ message = "The ‘fileSystems’ option does not specify your root file system.";
+ }
+ { assertion = let inherit (config.boot) resumeDevice; in
+ resumeDevice == "" || builtins.substring 0 1 resumeDevice == "/";
+ message = "boot.resumeDevice has to be an absolute path."
+ + " Old \"x:y\" style is no longer supported.";
+ }
+ ];
+
+ system.build =
+ { inherit bootStage1 initialRamdisk initialRamdiskSecretAppender extraUtils; };
+
+ system.requiredKernelConfig = with config.lib.kernelConfig; [
+ (isYes "TMPFS")
+ (isYes "BLK_DEV_INITRD")
+ ];
+
+ boot.initrd.supportedFilesystems = map (fs: fs.fsType) fileSystems;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/stage-2-init.sh b/nixpkgs/nixos/modules/system/boot/stage-2-init.sh
new file mode 100644
index 00000000000..03daafa1ce4
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/stage-2-init.sh
@@ -0,0 +1,172 @@
+#! @shell@
+
+systemConfig=@systemConfig@
+
+export HOME=/root PATH="@path@"
+
+
+# Process the kernel command line.
+for o in $(</proc/cmdline); do
+ case $o in
+ boot.debugtrace)
+ # Show each command.
+ set -x
+ ;;
+ resume=*)
+ set -- $(IFS==; echo $o)
+ resumeDevice=$2
+ ;;
+ esac
+done
+
+
+# Print a greeting.
+echo
+echo -e "\e[1;32m<<< NixOS Stage 2 >>>\e[0m"
+echo
+
+
+# Normally, stage 1 mounts the root filesystem read/writable.
+# However, in some environments, stage 2 is executed directly, and the
+# root is read-only. So make it writable here.
+if [ -z "$container" ]; then
+ mount -n -o remount,rw none /
+fi
+
+
+# Likewise, stage 1 mounts /proc, /dev and /sys, so if we don't have a
+# stage 1, we need to do that here.
+if [ ! -e /proc/1 ]; then
+ specialMount() {
+ local device="$1"
+ local mountPoint="$2"
+ local options="$3"
+ local fsType="$4"
+
+ install -m 0755 -d "$mountPoint"
+ mount -n -t "$fsType" -o "$options" "$device" "$mountPoint"
+ }
+ source @earlyMountScript@
+fi
+
+
+echo "booting system configuration $systemConfig" > /dev/kmsg
+
+
+# Make /nix/store a read-only bind mount to enforce immutability of
+# the Nix store. Note that we can't use "chown root:nixbld" here
+# because users/groups might not exist yet.
+# Silence chown/chmod to fail gracefully on a readonly filesystem
+# like squashfs.
+chown -f 0:30000 /nix/store
+chmod -f 1775 /nix/store
+if [ -n "@readOnlyStore@" ]; then
+ if ! [[ "$(findmnt --noheadings --output OPTIONS /nix/store)" =~ ro(,|$) ]]; then
+ # FIXME when linux < 4.5 is EOL, switch to atomic bind mounts
+ #mount /nix/store /nix/store -o bind,remount,ro
+ mount --bind /nix/store /nix/store
+ mount -o remount,ro,bind /nix/store
+ fi
+fi
+
+
+# Provide a /etc/mtab.
+install -m 0755 -d /etc
+test -e /etc/fstab || touch /etc/fstab # to shut up mount
+rm -f /etc/mtab* # not that we care about stale locks
+ln -s /proc/mounts /etc/mtab
+
+
+# More special file systems, initialise required directories.
+[ -e /proc/bus/usb ] && mount -t usbfs usbfs /proc/bus/usb # UML doesn't have USB by default
+install -m 01777 -d /tmp
+install -m 0755 -d /var/{log,lib,db} /nix/var /etc/nixos/ \
+ /run/lock /home /bin # for the /bin/sh symlink
+
+
+# Miscellaneous boot time cleanup.
+rm -rf /var/run /var/lock
+rm -f /etc/{group,passwd,shadow}.lock
+
+
+# Also get rid of temporary GC roots.
+rm -rf /nix/var/nix/gcroots/tmp /nix/var/nix/temproots
+
+
+# For backwards compatibility, symlink /var/run to /run, and /var/lock
+# to /run/lock.
+ln -s /run /var/run
+ln -s /run/lock /var/lock
+
+
+# Clear the resume device.
+if test -n "$resumeDevice"; then
+ mkswap "$resumeDevice" || echo 'Failed to clear saved image.'
+fi
+
+
+# Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
+if [ -n "@useHostResolvConf@" ] && [ -e /etc/resolv.conf ]; then
+ resolvconf -m 1000 -a host </etc/resolv.conf
+fi
+
+# Log the script output to /dev/kmsg or /run/log/stage-2-init.log.
+# Only at this point are all the necessary prerequisites ready for these commands.
+exec {logOutFd}>&1 {logErrFd}>&2
+if test -w /dev/kmsg; then
+ exec > >(tee -i /proc/self/fd/"$logOutFd" | while read -r line; do
+ if test -n "$line"; then
+ echo "<7>stage-2-init: $line" > /dev/kmsg
+ fi
+ done) 2>&1
+else
+ mkdir -p /run/log
+ exec > >(tee -i /run/log/stage-2-init.log) 2>&1
+fi
+
+
+# Run the script that performs all configuration activation that does
+# not have to be done at boot time.
+echo "running activation script..."
+$systemConfig/activate
+
+
+# Restore the system time from the hardware clock. We do this after
+# running the activation script to be sure that /etc/localtime points
+# at the current time zone.
+if [ -e /dev/rtc ]; then
+ hwclock --hctosys
+fi
+
+
+# Record the boot configuration.
+ln -sfn "$systemConfig" /run/booted-system
+
+# Prevent the booted system form being garbage-collected If it weren't
+# a gcroot, if we were running a different kernel, switched system,
+# and garbage collected all, we could not load kernel modules anymore.
+ln -sfn /run/booted-system /nix/var/nix/gcroots/booted-system
+
+
+# Run any user-specified commands.
+@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}>&-
+
+
+# Start systemd.
+echo "starting systemd..."
+PATH=/run/current-system/systemd/lib/systemd:@fsPackagesPath@ \
+ LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive \
+ exec systemd
diff --git a/nixpkgs/nixos/modules/system/boot/stage-2.nix b/nixpkgs/nixos/modules/system/boot/stage-2.nix
new file mode 100644
index 00000000000..6b0b4722730
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/stage-2.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ useHostResolvConf = config.networking.resolvconf.enable && config.networking.useHostResolvConf;
+
+ bootStage2 = pkgs.substituteAll {
+ src = ./stage-2-init.sh;
+ shellDebug = "${pkgs.bashInteractive}/bin/bash";
+ shell = "${pkgs.bash}/bin/bash";
+ isExecutable = true;
+ inherit (config.nix) readOnlyStore;
+ inherit useHostResolvConf;
+ inherit (config.system.build) earlyMountScript;
+ path = lib.makeBinPath ([
+ pkgs.coreutils
+ pkgs.utillinux
+ ] ++ lib.optional useHostResolvConf pkgs.openresolv);
+ fsPackagesPath = lib.makeBinPath config.system.fsPackages;
+ postBootCommands = pkgs.writeText "local-cmds"
+ ''
+ ${config.boot.postBootCommands}
+ ${config.powerManagement.powerUpCommands}
+ '';
+ };
+
+in
+
+{
+ options = {
+
+ boot = {
+
+ postBootCommands = mkOption {
+ default = "";
+ example = "rm -f /var/log/messages";
+ type = types.lines;
+ description = ''
+ Shell commands to be executed just before systemd is started.
+ '';
+ };
+
+ devSize = mkOption {
+ default = "5%";
+ example = "32m";
+ type = types.str;
+ description = ''
+ Size limit for the /dev tmpfs. Look at mount(8), tmpfs size option,
+ for the accepted syntax.
+ '';
+ };
+
+ devShmSize = mkOption {
+ default = "50%";
+ example = "256m";
+ type = types.str;
+ description = ''
+ Size limit for the /dev/shm tmpfs. Look at mount(8), tmpfs size option,
+ for the accepted syntax.
+ '';
+ };
+
+ runSize = mkOption {
+ default = "25%";
+ example = "256m";
+ type = types.str;
+ description = ''
+ Size limit for the /run tmpfs. Look at mount(8), tmpfs size option,
+ for the accepted syntax.
+ '';
+ };
+
+ };
+
+ };
+
+
+ config = {
+
+ system.build.bootStage2 = bootStage2;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/systemd-lib.nix b/nixpkgs/nixos/modules/system/boot/systemd-lib.nix
new file mode 100644
index 00000000000..28ad4f121bb
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/systemd-lib.nix
@@ -0,0 +1,217 @@
+{ config, lib, pkgs }:
+
+with lib;
+
+let
+ cfg = config.systemd;
+ lndir = "${pkgs.xorg.lndir}/bin/lndir";
+in rec {
+
+ shellEscape = s: (replaceChars [ "\\" ] [ "\\\\" ] s);
+
+ mkPathSafeName = lib.replaceChars ["@" ":" "\\" "[" "]"] ["-" "-" "-" "" ""];
+
+ makeUnit = name: unit:
+ if unit.enable then
+ pkgs.runCommand "unit-${mkPathSafeName name}"
+ { preferLocalBuild = true;
+ allowSubstitutes = false;
+ inherit (unit) text;
+ }
+ ''
+ mkdir -p $out
+ echo -n "$text" > $out/${shellEscape name}
+ ''
+ else
+ pkgs.runCommand "unit-${mkPathSafeName name}-disabled"
+ { preferLocalBuild = true;
+ allowSubstitutes = false;
+ }
+ ''
+ mkdir -p $out
+ ln -s /dev/null $out/${shellEscape name}
+ '';
+
+ boolValues = [true false "yes" "no"];
+
+ digits = map toString (range 0 9);
+
+ isByteFormat = s:
+ let
+ l = reverseList (stringToCharacters s);
+ suffix = head l;
+ nums = tail l;
+ in elem suffix (["K" "M" "G" "T"] ++ digits)
+ && all (num: elem num digits) nums;
+
+ assertByteFormat = name: group: attr:
+ optional (attr ? ${name} && ! isByteFormat attr.${name})
+ "Systemd ${group} field `${name}' must be in byte format [0-9]+[KMGT].";
+
+ hexChars = stringToCharacters "0123456789abcdefABCDEF";
+
+ isMacAddress = s: stringLength s == 17
+ && flip all (splitString ":" s) (bytes:
+ all (byte: elem byte hexChars) (stringToCharacters bytes)
+ );
+
+ assertMacAddress = name: group: attr:
+ optional (attr ? ${name} && ! isMacAddress attr.${name})
+ "Systemd ${group} field `${name}' must be a valid mac address.";
+
+
+ assertValueOneOf = name: values: group: attr:
+ optional (attr ? ${name} && !elem attr.${name} values)
+ "Systemd ${group} field `${name}' cannot have value `${toString attr.${name}}'.";
+
+ assertHasField = name: group: attr:
+ optional (!(attr ? ${name}))
+ "Systemd ${group} field `${name}' must exist.";
+
+ assertRange = name: min: max: group: attr:
+ optional (attr ? ${name} && !(min <= attr.${name} && max >= attr.${name}))
+ "Systemd ${group} field `${name}' is outside the range [${toString min},${toString max}]";
+
+ assertMinimum = name: min: group: attr:
+ optional (attr ? ${name} && attr.${name} < min)
+ "Systemd ${group} field `${name}' must be greater than or equal to ${toString min}";
+
+ assertOnlyFields = fields: group: attr:
+ let badFields = filter (name: ! elem name fields) (attrNames attr); in
+ optional (badFields != [ ])
+ "Systemd ${group} has extra fields [${concatStringsSep " " badFields}].";
+
+ assertInt = name: group: attr:
+ optional (attr ? ${name} && !isInt attr.${name})
+ "Systemd ${group} field `${name}' is not an integer";
+
+ checkUnitConfig = group: checks: attrs: let
+ # We're applied at the top-level type (attrsOf unitOption), so the actual
+ # unit options might contain attributes from mkOverride that we need to
+ # convert into single values before checking them.
+ defs = mapAttrs (const (v:
+ if v._type or "" == "override" then v.content else v
+ )) attrs;
+ errors = concatMap (c: c group defs) checks;
+ in if errors == [] then true
+ else builtins.trace (concatStringsSep "\n" errors) false;
+
+ toOption = x:
+ if x == true then "true"
+ else if x == false then "false"
+ else toString x;
+
+ attrsToSection = as:
+ concatStrings (concatLists (mapAttrsToList (name: value:
+ map (x: ''
+ ${name}=${toOption x}
+ '')
+ (if isList value then value else [value]))
+ as));
+
+ generateUnits = type: units: upstreamUnits: upstreamWants:
+ pkgs.runCommand "${type}-units"
+ { preferLocalBuild = true;
+ allowSubstitutes = false;
+ } ''
+ mkdir -p $out
+
+ # Copy the upstream systemd units we're interested in.
+ for i in ${toString upstreamUnits}; do
+ fn=${cfg.package}/example/systemd/${type}/$i
+ if ! [ -e $fn ]; then echo "missing $fn"; false; fi
+ if [ -L $fn ]; then
+ target="$(readlink "$fn")"
+ if [ ''${target:0:3} = ../ ]; then
+ ln -s "$(readlink -f "$fn")" $out/
+ else
+ cp -pd $fn $out/
+ fi
+ else
+ ln -s $fn $out/
+ fi
+ done
+
+ # Copy .wants links, but only those that point to units that
+ # we're interested in.
+ for i in ${toString upstreamWants}; do
+ fn=${cfg.package}/example/systemd/${type}/$i
+ if ! [ -e $fn ]; then echo "missing $fn"; false; fi
+ x=$out/$(basename $fn)
+ mkdir $x
+ for i in $fn/*; do
+ y=$x/$(basename $i)
+ cp -pd $i $y
+ if ! [ -e $y ]; then rm $y; fi
+ done
+ done
+
+ # Symlink all units provided listed in systemd.packages.
+ for i in ${toString cfg.packages}; do
+ for fn in $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*; do
+ if ! [[ "$fn" =~ .wants$ ]]; then
+ if [[ -d "$fn" ]]; then
+ targetDir="$out/$(basename "$fn")"
+ mkdir -p "$targetDir"
+ ${lndir} "$fn" "$targetDir"
+ else
+ ln -s $fn $out/
+ fi
+ fi
+ done
+ done
+
+ # Symlink all units defined by systemd.units. If these are also
+ # provided by systemd or systemd.packages, then add them as
+ # <unit-name>.d/overrides.conf, which makes them extend the
+ # upstream unit.
+ for i in ${toString (mapAttrsToList (n: v: v.unit) units)}; do
+ fn=$(basename $i/*)
+ if [ -e $out/$fn ]; then
+ if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
+ ln -sfn /dev/null $out/$fn
+ else
+ mkdir -p $out/$fn.d
+ ln -s $i/$fn $out/$fn.d/overrides.conf
+ fi
+ else
+ ln -fs $i/$fn $out/
+ fi
+ done
+
+ # Create service aliases from aliases option.
+ ${concatStrings (mapAttrsToList (name: unit:
+ concatMapStrings (name2: ''
+ ln -sfn '${name}' $out/'${name2}'
+ '') unit.aliases) units)}
+
+ # Create .wants and .requires symlinks from the wantedBy and
+ # requiredBy options.
+ ${concatStrings (mapAttrsToList (name: unit:
+ concatMapStrings (name2: ''
+ mkdir -p $out/'${name2}.wants'
+ ln -sfn '../${name}' $out/'${name2}.wants'/
+ '') unit.wantedBy) units)}
+
+ ${concatStrings (mapAttrsToList (name: unit:
+ concatMapStrings (name2: ''
+ mkdir -p $out/'${name2}.requires'
+ ln -sfn '../${name}' $out/'${name2}.requires'/
+ '') unit.requiredBy) units)}
+
+ ${optionalString (type == "system") ''
+ # Stupid misc. symlinks.
+ ln -s ${cfg.defaultUnit} $out/default.target
+ ln -s ${cfg.ctrlAltDelUnit} $out/ctrl-alt-del.target
+ ln -s rescue.target $out/kbrequest.target
+
+ mkdir -p $out/getty.target.wants/
+ ln -s ../autovt@tty1.service $out/getty.target.wants/
+
+ ln -s ../local-fs.target ../remote-fs.target \
+ ../nss-lookup.target ../nss-user-lookup.target ../swap.target \
+ $out/multi-user.target.wants/
+ ''}
+ ''; # */
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix b/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix
new file mode 100644
index 00000000000..db6e06b4107
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix
@@ -0,0 +1,123 @@
+{ config, lib , pkgs, ...}:
+
+with lib;
+with import ./systemd-unit-options.nix { inherit config lib; };
+with import ./systemd-lib.nix { inherit config lib pkgs; };
+
+let
+ cfg = config.systemd.nspawn;
+
+ checkExec = checkUnitConfig "Exec" [
+ (assertOnlyFields [
+ "Boot" "ProcessTwo" "Parameters" "Environment" "User" "WorkingDirectory"
+ "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)
+ (assertValueOneOf "NotifyReady" boolValues)
+ ];
+
+ checkFiles = checkUnitConfig "Files" [
+ (assertOnlyFields [
+ "ReadOnly" "Volatile" "Bind" "BindReadOnly" "TemporaryFileSystem"
+ "Overlay" "OverlayReadOnly" "PrivateUsersChown"
+ ])
+ (assertValueOneOf "ReadOnly" boolValues)
+ (assertValueOneOf "Volatile" (boolValues ++ [ "state" ]))
+ (assertValueOneOf "PrivateUsersChown" boolValues)
+ ];
+
+ checkNetwork = checkUnitConfig "Network" [
+ (assertOnlyFields [
+ "Private" "VirtualEthernet" "VirtualEthernetExtra" "Interface" "MACVLAN"
+ "IPVLAN" "Bridge" "Zone" "Port"
+ ])
+ (assertValueOneOf "Private" boolValues)
+ (assertValueOneOf "VirtualEthernet" boolValues)
+ ];
+
+ instanceOptions = {
+ options = sharedOptions // {
+ execConfig = mkOption {
+ default = {};
+ example = { Parameters = "/bin/sh"; };
+ type = types.addCheck (types.attrsOf unitOption) checkExec;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Exec]</literal> section of this unit. See
+ <citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ filesConfig = mkOption {
+ default = {};
+ example = { Bind = [ "/home/alice" ]; };
+ type = types.addCheck (types.attrsOf unitOption) checkFiles;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Files]</literal> section of this unit. See
+ <citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ networkConfig = mkOption {
+ default = {};
+ example = { Private = false; };
+ type = types.addCheck (types.attrsOf unitOption) checkNetwork;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Network]</literal> section of this unit. See
+ <citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+ };
+
+ };
+
+ instanceToUnit = name: def:
+ let base = {
+ text = ''
+ [Exec]
+ ${attrsToSection def.execConfig}
+
+ [Files]
+ ${attrsToSection def.filesConfig}
+
+ [Network]
+ ${attrsToSection def.networkConfig}
+ '';
+ } // def;
+ in base // { unit = makeUnit name base; };
+
+in {
+
+ options = {
+
+ systemd.nspawn = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule instanceOptions);
+ description = "Definition of systemd-nspawn configurations.";
+ };
+
+ };
+
+ config =
+ let
+ units = mapAttrs' (n: v: let nspawnFile = "${n}.nspawn"; in nameValuePair nspawnFile (instanceToUnit nspawnFile v)) cfg;
+ in mkIf (cfg != {}) {
+
+ environment.etc."systemd/nspawn".source = generateUnits "nspawn" units [] [];
+
+ systemd.targets.multi-user.wants = [ "machines.target" ];
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix b/nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix
new file mode 100644
index 00000000000..c1f2c98afcd
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix
@@ -0,0 +1,518 @@
+{ config, lib }:
+
+with lib;
+with import ./systemd-lib.nix { inherit config lib pkgs; };
+
+let
+ checkService = checkUnitConfig "Service" [
+ (assertValueOneOf "Type" [
+ "exec" "simple" "forking" "oneshot" "dbus" "notify" "idle"
+ ])
+ (assertValueOneOf "Restart" [
+ "no" "on-success" "on-failure" "on-abnormal" "on-abort" "always"
+ ])
+ ];
+
+in rec {
+
+ unitOption = mkOptionType {
+ name = "systemd option";
+ merge = loc: defs:
+ let
+ defs' = filterOverrides defs;
+ defs'' = getValues defs';
+ in
+ if isList (head defs'')
+ then concatLists defs''
+ else mergeOneOption loc defs';
+ };
+
+ sharedOptions = {
+
+ enable = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ If set to false, this unit will be a symlink to
+ /dev/null. This is primarily useful to prevent specific
+ template instances
+ (e.g. <literal>serial-getty@ttyS0</literal>) from being
+ started. Note that <literal>enable=true</literal> does not
+ make a unit start by default at boot; if you want that, see
+ <literal>wantedBy</literal>.
+ '';
+ };
+
+ requiredBy = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ Units that require (i.e. depend on and need to go down with)
+ this unit. The discussion under <literal>wantedBy</literal>
+ applies here as well: inverse <literal>.requires</literal>
+ symlinks are established.
+ '';
+ };
+
+ wantedBy = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ Units that want (i.e. depend on) this unit. The standard way
+ to make a unit start by default at boot is to set this option
+ to <literal>[ "multi-user.target" ]</literal>. That's despite
+ the fact that the systemd.unit(5) manpage says this option
+ goes in the <literal>[Install]</literal> section that controls
+ the behaviour of <literal>systemctl enable</literal>. Since
+ such a process is stateful and thus contrary to the design of
+ NixOS, setting this option instead causes the equivalent
+ inverse <literal>.wants</literal> symlink to be present,
+ establishing the same desired relationship in a stateless way.
+ '';
+ };
+
+ aliases = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = "Aliases of that unit.";
+ };
+
+ };
+
+ concreteUnitOptions = sharedOptions // {
+
+ text = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Text of this systemd unit.";
+ };
+
+ unit = mkOption {
+ internal = true;
+ description = "The generated unit.";
+ };
+
+ };
+
+ commonUnitOptions = sharedOptions // {
+
+ description = mkOption {
+ default = "";
+ type = types.str;
+ description = "Description of this unit used in systemd messages and progress indicators.";
+ };
+
+ documentation = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = "A list of URIs referencing documentation for this unit or its configuration.";
+ };
+
+ requires = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ Start the specified units when this unit is started, and stop
+ this unit when the specified units are stopped or fail.
+ '';
+ };
+
+ wants = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ Start the specified units when this unit is started.
+ '';
+ };
+
+ after = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ If the specified units are started at the same time as
+ this unit, delay this unit until they have started.
+ '';
+ };
+
+ before = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ If the specified units are started at the same time as
+ this unit, delay them until this unit has started.
+ '';
+ };
+
+ bindsTo = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ Like ‘requires’, but in addition, if the specified units
+ unexpectedly disappear, this unit will be stopped as well.
+ '';
+ };
+
+ partOf = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ If the specified units are stopped or restarted, then this
+ unit is stopped or restarted as well.
+ '';
+ };
+
+ conflicts = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ If the specified units are started, then this unit is stopped
+ and vice versa.
+ '';
+ };
+
+ requisite = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ Similar to requires. However if the units listed are not started,
+ they will not be started and the transaction will fail.
+ '';
+ };
+
+ unitConfig = mkOption {
+ default = {};
+ example = { RequiresMountsFor = "/data"; };
+ type = types.attrsOf unitOption;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Unit]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.unit</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ restartTriggers = mkOption {
+ default = [];
+ type = types.listOf types.unspecified;
+ description = ''
+ An arbitrary list of items such as derivations. If any item
+ in the list changes between reconfigurations, the service will
+ be restarted.
+ '';
+ };
+
+ onFailure = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ description = ''
+ A list of one or more units that are activated when
+ this unit enters the "failed" state.
+ '';
+ };
+
+ 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.
+ '';
+ };
+
+ };
+
+
+ serviceOptions = commonUnitOptions // {
+
+ environment = mkOption {
+ default = {};
+ type = with types; attrsOf (nullOr (oneOf [ str path package ]));
+ example = { PATH = "/foo/bar/bin"; LANG = "nl_NL.UTF-8"; };
+ description = "Environment variables passed to the service's processes.";
+ };
+
+ path = mkOption {
+ default = [];
+ apply = ps: "${makeBinPath ps}:${makeSearchPathOutput "bin" "sbin" ps}";
+ description = ''
+ Packages added to the service's <envar>PATH</envar>
+ environment variable. Both the <filename>bin</filename>
+ and <filename>sbin</filename> subdirectories of each
+ package are added.
+ '';
+ };
+
+ serviceConfig = mkOption {
+ default = {};
+ example =
+ { StartLimitInterval = 10;
+ RestartSec = 5;
+ };
+ type = types.addCheck (types.attrsOf unitOption) checkService;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Service]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.service</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ script = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Shell commands executed as the service's main process.";
+ };
+
+ scriptArgs = mkOption {
+ type = types.str;
+ default = "";
+ description = "Arguments passed to the main process script.";
+ };
+
+ preStart = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed before the service's main process
+ is started.
+ '';
+ };
+
+ postStart = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed after the service's main process
+ is started.
+ '';
+ };
+
+ reload = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed when the service's main process
+ is reloaded.
+ '';
+ };
+
+ preStop = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed to stop the service.
+ '';
+ };
+
+ postStop = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed after the service's main process
+ has exited.
+ '';
+ };
+
+ restartIfChanged = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether the service should be restarted during a NixOS
+ configuration switch if its definition has changed.
+ '';
+ };
+
+ reloadIfChanged = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether the service should be reloaded during a NixOS
+ configuration switch if its definition has changed. If
+ enabled, the value of <option>restartIfChanged</option> is
+ ignored.
+ '';
+ };
+
+ stopIfChanged = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If set, a changed unit is restarted by calling
+ <command>systemctl stop</command> in the old configuration,
+ then <command>systemctl start</command> in the new one.
+ Otherwise, it is restarted in a single step using
+ <command>systemctl restart</command> in the new configuration.
+ The latter is less correct because it runs the
+ <literal>ExecStop</literal> commands from the new
+ configuration.
+ '';
+ };
+
+ startAt = mkOption {
+ type = with types; either str (listOf str);
+ default = [];
+ example = "Sun 14:00:00";
+ description = ''
+ Automatically start this unit at the given date/time, which
+ must be in the format described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>. This is equivalent
+ to adding a corresponding timer unit with
+ <option>OnCalendar</option> set to the value given here.
+ '';
+ apply = v: if isList v then v else [ v ];
+ };
+
+ };
+
+
+ socketOptions = commonUnitOptions // {
+
+ listenStreams = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ example = [ "0.0.0.0:993" "/run/my-socket" ];
+ description = ''
+ For each item in this list, a <literal>ListenStream</literal>
+ option in the <literal>[Socket]</literal> section will be created.
+ '';
+ };
+
+ socketConfig = mkOption {
+ default = {};
+ example = { ListenStream = "/run/my-socket"; };
+ type = types.attrsOf unitOption;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Socket]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ };
+
+
+ timerOptions = commonUnitOptions // {
+
+ timerConfig = mkOption {
+ default = {};
+ example = { OnCalendar = "Sun 14:00:00"; Unit = "foo.service"; };
+ type = types.attrsOf unitOption;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Timer]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.timer</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> and
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ };
+
+
+ pathOptions = commonUnitOptions // {
+
+ pathConfig = mkOption {
+ default = {};
+ example = { PathChanged = "/some/path"; Unit = "changedpath.service"; };
+ type = types.attrsOf unitOption;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Path]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.path</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ };
+
+
+ mountOptions = commonUnitOptions // {
+
+ what = mkOption {
+ example = "/dev/sda1";
+ type = types.str;
+ description = "Absolute path of device node, file or other resource. (Mandatory)";
+ };
+
+ where = mkOption {
+ example = "/mnt";
+ type = types.str;
+ description = ''
+ Absolute path of a directory of the mount point.
+ Will be created if it doesn't exist. (Mandatory)
+ '';
+ };
+
+ type = mkOption {
+ default = "";
+ example = "ext4";
+ type = types.str;
+ description = "File system type.";
+ };
+
+ options = mkOption {
+ default = "";
+ example = "noatime";
+ type = types.commas;
+ description = "Options used to mount the file system.";
+ };
+
+ mountConfig = mkOption {
+ default = {};
+ example = { DirectoryMode = "0775"; };
+ type = types.attrsOf unitOption;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Mount]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.mount</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+ };
+
+ automountOptions = commonUnitOptions // {
+
+ where = mkOption {
+ example = "/mnt";
+ type = types.str;
+ description = ''
+ Absolute path of a directory of the mount point.
+ Will be created if it doesn't exist. (Mandatory)
+ '';
+ };
+
+ automountConfig = mkOption {
+ default = {};
+ example = { DirectoryMode = "0775"; };
+ type = types.attrsOf unitOption;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Automount]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.automount</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+ };
+
+ targetOptions = commonUnitOptions;
+
+ sliceOptions = commonUnitOptions // {
+
+ sliceConfig = mkOption {
+ default = {};
+ example = { MemoryMax = "2G"; };
+ type = types.attrsOf unitOption;
+ description = ''
+ Each attribute in this set specifies an option in the
+ <literal>[Slice]</literal> section of the unit. See
+ <citerefentry><refentrytitle>systemd.slice</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> for details.
+ '';
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/systemd.nix b/nixpkgs/nixos/modules/system/boot/systemd.nix
new file mode 100644
index 00000000000..5cf437bfbcb
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/systemd.nix
@@ -0,0 +1,994 @@
+{ config, lib, pkgs, utils, ... }:
+
+with utils;
+with lib;
+with import ./systemd-unit-options.nix { inherit config lib; };
+with import ./systemd-lib.nix { inherit config lib pkgs; };
+
+let
+
+ cfg = config.systemd;
+
+ systemd = cfg.package;
+
+ upstreamSystemUnits =
+ [ # Targets.
+ "basic.target"
+ "sysinit.target"
+ "sockets.target"
+ "exit.target"
+ "graphical.target"
+ "multi-user.target"
+ "network.target"
+ "network-pre.target"
+ "network-online.target"
+ "nss-lookup.target"
+ "nss-user-lookup.target"
+ "time-sync.target"
+ #"cryptsetup.target"
+ "sigpwr.target"
+ "timers.target"
+ "paths.target"
+ "rpcbind.target"
+
+ # Rescue mode.
+ "rescue.target"
+ "rescue.service"
+
+ # Udev.
+ "systemd-udevd-control.socket"
+ "systemd-udevd-kernel.socket"
+ "systemd-udevd.service"
+ "systemd-udev-settle.service"
+ "systemd-udev-trigger.service"
+ # hwdb.bin is managed by NixOS
+ # "systemd-hwdb-update.service"
+
+ # Consoles.
+ "getty.target"
+ "getty-pre.target"
+ "getty@.service"
+ "serial-getty@.service"
+ "console-getty.service"
+ "container-getty@.service"
+ "systemd-vconsole-setup.service"
+
+ # Hardware (started by udev when a relevant device is plugged in).
+ "sound.target"
+ "bluetooth.target"
+ "printer.target"
+ "smartcard.target"
+
+ # Login stuff.
+ "systemd-logind.service"
+ "autovt@.service"
+ "systemd-user-sessions.service"
+ "dbus-org.freedesktop.machine1.service"
+ "user@.service"
+ "user-runtime-dir@.service"
+
+ # Journal.
+ "systemd-journald.socket"
+ "systemd-journald.service"
+ "systemd-journal-flush.service"
+ "systemd-journal-catalog-update.service"
+ "systemd-journald-audit.socket"
+ "systemd-journald-dev-log.socket"
+ "syslog.socket"
+
+ # Coredumps.
+ "systemd-coredump.socket"
+ "systemd-coredump@.service"
+
+ # SysV init compatibility.
+ "systemd-initctl.socket"
+ "systemd-initctl.service"
+
+ # Kernel module loading.
+ "systemd-modules-load.service"
+ "kmod-static-nodes.service"
+
+ # Filesystems.
+ "systemd-fsck@.service"
+ "systemd-fsck-root.service"
+ "systemd-remount-fs.service"
+ "local-fs.target"
+ "local-fs-pre.target"
+ "remote-fs.target"
+ "remote-fs-pre.target"
+ "swap.target"
+ "dev-hugepages.mount"
+ "dev-mqueue.mount"
+ "sys-fs-fuse-connections.mount"
+ "sys-kernel-config.mount"
+ "sys-kernel-debug.mount"
+
+ # Maintaining state across reboots.
+ "systemd-random-seed.service"
+ "systemd-backlight@.service"
+ "systemd-rfkill.service"
+ "systemd-rfkill.socket"
+
+ # Hibernate / suspend.
+ "hibernate.target"
+ "suspend.target"
+ "suspend-then-hibernate.target"
+ "sleep.target"
+ "hybrid-sleep.target"
+ "systemd-hibernate.service"
+ "systemd-hybrid-sleep.service"
+ "systemd-suspend.service"
+ "systemd-suspend-then-hibernate.service"
+
+ # Reboot stuff.
+ "reboot.target"
+ "systemd-reboot.service"
+ "poweroff.target"
+ "systemd-poweroff.service"
+ "halt.target"
+ "systemd-halt.service"
+ "shutdown.target"
+ "umount.target"
+ "final.target"
+ "kexec.target"
+ "systemd-kexec.service"
+ "systemd-update-utmp.service"
+
+ # Password entry.
+ "systemd-ask-password-console.path"
+ "systemd-ask-password-console.service"
+ "systemd-ask-password-wall.path"
+ "systemd-ask-password-wall.service"
+
+ # Slices / containers.
+ "slices.target"
+ "user.slice"
+ "machine.slice"
+ "machines.target"
+ "systemd-machined.service"
+ "systemd-nspawn@.service"
+
+ # Temporary file creation / cleanup.
+ "systemd-tmpfiles-clean.service"
+ "systemd-tmpfiles-clean.timer"
+ "systemd-tmpfiles-setup.service"
+ "systemd-tmpfiles-setup-dev.service"
+
+ # Misc.
+ "systemd-sysctl.service"
+ "dbus-org.freedesktop.timedate1.service"
+ "dbus-org.freedesktop.locale1.service"
+ "dbus-org.freedesktop.hostname1.service"
+ "systemd-timedated.service"
+ "systemd-localed.service"
+ "systemd-hostnamed.service"
+ "systemd-binfmt.service"
+ "systemd-exit.service"
+ "systemd-update-done.service"
+ ] ++ optionals config.services.journald.enableHttpGateway [
+ "systemd-journal-gatewayd.socket"
+ "systemd-journal-gatewayd.service"
+ ] ++ cfg.additionalUpstreamSystemUnits;
+
+ upstreamSystemWants =
+ [ "sysinit.target.wants"
+ "sockets.target.wants"
+ "local-fs.target.wants"
+ "multi-user.target.wants"
+ "timers.target.wants"
+ ];
+
+ upstreamUserUnits =
+ [ "basic.target"
+ "bluetooth.target"
+ "default.target"
+ "exit.target"
+ "graphical-session-pre.target"
+ "graphical-session.target"
+ "paths.target"
+ "printer.target"
+ "shutdown.target"
+ "smartcard.target"
+ "sockets.target"
+ "sound.target"
+ "systemd-exit.service"
+ "systemd-tmpfiles-clean.service"
+ "systemd-tmpfiles-clean.timer"
+ "systemd-tmpfiles-setup.service"
+ "timers.target"
+ ];
+
+ makeJobScript = name: text:
+ let mkScriptName = s: "unit-script-" + (replaceChars [ "\\" "@" ] [ "-" "_" ] (shellEscape s) );
+ in pkgs.writeTextFile { name = mkScriptName name; executable = true; inherit text; };
+
+ unitConfig = { config, options, ... }: {
+ config = {
+ unitConfig =
+ optionalAttrs (config.requires != [])
+ { Requires = toString config.requires; }
+ // optionalAttrs (config.wants != [])
+ { Wants = toString config.wants; }
+ // optionalAttrs (config.after != [])
+ { After = toString config.after; }
+ // optionalAttrs (config.before != [])
+ { Before = toString config.before; }
+ // optionalAttrs (config.bindsTo != [])
+ { BindsTo = toString config.bindsTo; }
+ // optionalAttrs (config.partOf != [])
+ { PartOf = toString config.partOf; }
+ // optionalAttrs (config.conflicts != [])
+ { Conflicts = toString config.conflicts; }
+ // optionalAttrs (config.requisite != [])
+ { Requisite = toString config.requisite; }
+ // optionalAttrs (config.restartTriggers != [])
+ { X-Restart-Triggers = toString config.restartTriggers; }
+ // optionalAttrs (config.description != "") {
+ Description = config.description; }
+ // optionalAttrs (config.documentation != []) {
+ Documentation = toString config.documentation; }
+ // optionalAttrs (config.onFailure != []) {
+ OnFailure = toString config.onFailure; }
+ // optionalAttrs (options.startLimitIntervalSec.isDefined) {
+ StartLimitIntervalSec = toString config.startLimitIntervalSec;
+ };
+ };
+ };
+
+ serviceConfig = { name, config, ... }: {
+ config = mkMerge
+ [ { # Default path for systemd services. Should be quite minimal.
+ path =
+ [ pkgs.coreutils
+ pkgs.findutils
+ pkgs.gnugrep
+ pkgs.gnused
+ systemd
+ ];
+ environment.PATH = config.path;
+ }
+ (mkIf (config.preStart != "")
+ { serviceConfig.ExecStartPre = makeJobScript "${name}-pre-start" ''
+ #! ${pkgs.runtimeShell} -e
+ ${config.preStart}
+ '';
+ })
+ (mkIf (config.script != "")
+ { serviceConfig.ExecStart = makeJobScript "${name}-start" ''
+ #! ${pkgs.runtimeShell} -e
+ ${config.script}
+ '' + " " + config.scriptArgs;
+ })
+ (mkIf (config.postStart != "")
+ { serviceConfig.ExecStartPost = makeJobScript "${name}-post-start" ''
+ #! ${pkgs.runtimeShell} -e
+ ${config.postStart}
+ '';
+ })
+ (mkIf (config.reload != "")
+ { serviceConfig.ExecReload = makeJobScript "${name}-reload" ''
+ #! ${pkgs.runtimeShell} -e
+ ${config.reload}
+ '';
+ })
+ (mkIf (config.preStop != "")
+ { serviceConfig.ExecStop = makeJobScript "${name}-pre-stop" ''
+ #! ${pkgs.runtimeShell} -e
+ ${config.preStop}
+ '';
+ })
+ (mkIf (config.postStop != "")
+ { serviceConfig.ExecStopPost = makeJobScript "${name}-post-stop" ''
+ #! ${pkgs.runtimeShell} -e
+ ${config.postStop}
+ '';
+ })
+ ];
+ };
+
+ mountConfig = { config, ... }: {
+ config = {
+ mountConfig =
+ { What = config.what;
+ Where = config.where;
+ } // optionalAttrs (config.type != "") {
+ Type = config.type;
+ } // optionalAttrs (config.options != "") {
+ Options = config.options;
+ };
+ };
+ };
+
+ automountConfig = { config, ... }: {
+ config = {
+ automountConfig =
+ { Where = config.where;
+ };
+ };
+ };
+
+ commonUnitText = def: ''
+ [Unit]
+ ${attrsToSection def.unitConfig}
+ '';
+
+ targetToUnit = name: def:
+ { inherit (def) aliases wantedBy requiredBy enable;
+ text =
+ ''
+ [Unit]
+ ${attrsToSection def.unitConfig}
+ '';
+ };
+
+ serviceToUnit = name: def:
+ { inherit (def) aliases wantedBy requiredBy enable;
+ text = commonUnitText def +
+ ''
+ [Service]
+ ${let env = cfg.globalEnvironment // def.environment;
+ in concatMapStrings (n:
+ let s = optionalString (env.${n} != null)
+ "Environment=${builtins.toJSON "${n}=${env.${n}}"}\n";
+ # systemd max line length is now 1MiB
+ # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
+ 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 ''
+ X-RestartIfChanged=false
+ '' else ""}
+ ${optionalString (!def.stopIfChanged) "X-StopIfChanged=false"}
+ ${attrsToSection def.serviceConfig}
+ '';
+ };
+
+ socketToUnit = name: def:
+ { inherit (def) aliases wantedBy requiredBy enable;
+ text = commonUnitText def +
+ ''
+ [Socket]
+ ${attrsToSection def.socketConfig}
+ ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)}
+ '';
+ };
+
+ timerToUnit = name: def:
+ { inherit (def) aliases wantedBy requiredBy enable;
+ text = commonUnitText def +
+ ''
+ [Timer]
+ ${attrsToSection def.timerConfig}
+ '';
+ };
+
+ pathToUnit = name: def:
+ { inherit (def) aliases wantedBy requiredBy enable;
+ text = commonUnitText def +
+ ''
+ [Path]
+ ${attrsToSection def.pathConfig}
+ '';
+ };
+
+ mountToUnit = name: def:
+ { inherit (def) aliases wantedBy requiredBy enable;
+ text = commonUnitText def +
+ ''
+ [Mount]
+ ${attrsToSection def.mountConfig}
+ '';
+ };
+
+ automountToUnit = name: def:
+ { inherit (def) aliases wantedBy requiredBy enable;
+ text = commonUnitText def +
+ ''
+ [Automount]
+ ${attrsToSection def.automountConfig}
+ '';
+ };
+
+ sliceToUnit = name: def:
+ { inherit (def) aliases wantedBy requiredBy enable;
+ text = commonUnitText def +
+ ''
+ [Slice]
+ ${attrsToSection def.sliceConfig}
+ '';
+ };
+
+ logindHandlerType = types.enum [
+ "ignore" "poweroff" "reboot" "halt" "kexec" "suspend"
+ "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock"
+ ];
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ systemd.package = mkOption {
+ default = pkgs.systemd;
+ defaultText = "pkgs.systemd";
+ type = types.package;
+ description = "The systemd package.";
+ };
+
+ systemd.units = mkOption {
+ description = "Definition of systemd units.";
+ default = {};
+ type = with types; attrsOf (submodule (
+ { name, config, ... }:
+ { options = concreteUnitOptions;
+ config = {
+ unit = mkDefault (makeUnit name config);
+ };
+ }));
+ };
+
+ systemd.packages = mkOption {
+ default = [];
+ type = types.listOf types.package;
+ example = literalExample "[ pkgs.systemd-cryptsetup-generator ]";
+ description = "Packages providing systemd units and hooks.";
+ };
+
+ systemd.targets = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
+ description = "Definition of systemd target units.";
+ };
+
+ systemd.services = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ]);
+ description = "Definition of systemd service units.";
+ };
+
+ systemd.sockets = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ]);
+ description = "Definition of systemd socket units.";
+ };
+
+ systemd.timers = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ]);
+ description = "Definition of systemd timer units.";
+ };
+
+ systemd.paths = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
+ description = "Definition of systemd path units.";
+ };
+
+ systemd.mounts = mkOption {
+ default = [];
+ type = with types; listOf (submodule [ { options = mountOptions; } unitConfig mountConfig ]);
+ description = ''
+ Definition of systemd mount units.
+ This is a list instead of an attrSet, because systemd mandates the names to be derived from
+ the 'where' attribute.
+ '';
+ };
+
+ systemd.automounts = mkOption {
+ default = [];
+ type = with types; listOf (submodule [ { options = automountOptions; } unitConfig automountConfig ]);
+ description = ''
+ Definition of systemd automount units.
+ This is a list instead of an attrSet, because systemd mandates the names to be derived from
+ the 'where' attribute.
+ '';
+ };
+
+ systemd.slices = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig] );
+ description = "Definition of slice configurations.";
+ };
+
+ systemd.generators = mkOption {
+ type = types.attrsOf types.path;
+ default = {};
+ example = { systemd-gpt-auto-generator = "/dev/null"; };
+ description = ''
+ Definition of systemd generators.
+ For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
+ <literal>/etc/systemd/system-generators/NAME</literal> to <literal>VALUE</literal>.
+ '';
+ };
+
+ systemd.shutdown = mkOption {
+ type = types.attrsOf types.path;
+ default = {};
+ description = ''
+ Definition of systemd shutdown executables.
+ For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
+ <literal>/etc/systemd/system-shutdown/NAME</literal> to <literal>VALUE</literal>.
+ '';
+ };
+
+ systemd.defaultUnit = mkOption {
+ default = "multi-user.target";
+ type = types.str;
+ description = "Default unit started when the system boots.";
+ };
+
+ systemd.ctrlAltDelUnit = mkOption {
+ default = "reboot.target";
+ type = types.str;
+ example = "poweroff.target";
+ description = ''
+ Target that should be started when Ctrl-Alt-Delete is pressed.
+ '';
+ };
+
+ systemd.globalEnvironment = mkOption {
+ type = with types; attrsOf (nullOr (oneOf [ str path package ]));
+ default = {};
+ example = { TZ = "CET"; };
+ description = ''
+ Environment variables passed to <emphasis>all</emphasis> systemd units.
+ '';
+ };
+
+ systemd.enableCgroupAccounting = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable cgroup accounting.
+ '';
+ };
+
+ systemd.coredump.extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ example = "Storage=journal";
+ description = ''
+ Extra config options for systemd-coredump. See coredump.conf(5) man page
+ for available options.
+ '';
+ };
+
+ systemd.extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ example = "DefaultLimitCORE=infinity";
+ description = ''
+ Extra config options for systemd. See man systemd-system.conf for
+ available options.
+ '';
+ };
+
+ services.journald.console = mkOption {
+ default = "";
+ type = types.str;
+ description = "If non-empty, write log messages to the specified TTY device.";
+ };
+
+ services.journald.rateLimitInterval = mkOption {
+ default = "30s";
+ type = types.str;
+ description = ''
+ Configures the rate limiting interval that is applied to all
+ messages generated on the system. This rate limiting is applied
+ per-service, so that two services which log do not interfere with
+ each other's limit. The value may be specified in the following
+ units: s, min, h, ms, us. To turn off any kind of rate limiting,
+ set either value to 0.
+ '';
+ };
+
+ services.journald.rateLimitBurst = mkOption {
+ default = 1000;
+ type = types.int;
+ description = ''
+ Configures the rate limiting burst limit (number of messages per
+ interval) that is applied to all messages generated on the system.
+ This rate limiting is applied per-service, so that two services
+ which log do not interfere with each other's limit.
+ '';
+ };
+
+ services.journald.extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ example = "Storage=volatile";
+ description = ''
+ Extra config options for systemd-journald. See man journald.conf
+ for available options.
+ '';
+ };
+
+ services.journald.enableHttpGateway = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable the HTTP gateway to the journal.
+ '';
+ };
+
+ services.journald.forwardToSyslog = mkOption {
+ default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
+ defaultText = "services.rsyslogd.enable || 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
+ <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.
+ '';
+ };
+
+ services.logind.lidSwitch = mkOption {
+ default = "suspend";
+ example = "ignore";
+ type = logindHandlerType;
+
+ description = ''
+ Specifies what to be done when the laptop lid is closed.
+ '';
+ };
+
+ services.logind.lidSwitchDocked = mkOption {
+ default = "ignore";
+ example = "suspend";
+ type = logindHandlerType;
+
+ description = ''
+ Specifies what to be done when the laptop lid is closed
+ and another screen is added.
+ '';
+ };
+
+ services.logind.lidSwitchExternalPower = mkOption {
+ default = config.services.logind.lidSwitch;
+ defaultText = "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;
+ example = "DefaultCPUAccounting=yes";
+ description = ''
+ Extra config options for systemd user instances. See man systemd-user.conf for
+ available options.
+ '';
+ };
+
+ systemd.tmpfiles.rules = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "d /tmp 1777 root root 10d" ];
+ description = ''
+ Rules for creating and cleaning up temporary files
+ automatically. See
+ <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for the exact format.
+ '';
+ };
+
+ systemd.user.units = mkOption {
+ description = "Definition of systemd per-user units.";
+ default = {};
+ type = with types; attrsOf (submodule (
+ { name, config, ... }:
+ { options = concreteUnitOptions;
+ config = {
+ unit = mkDefault (makeUnit name config);
+ };
+ }));
+ };
+
+ systemd.user.paths = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
+ description = "Definition of systemd per-user path units.";
+ };
+
+ systemd.user.services = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ] );
+ description = "Definition of systemd per-user service units.";
+ };
+
+ systemd.user.slices = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig ] );
+ description = "Definition of systemd per-user slice units.";
+ };
+
+ systemd.user.sockets = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ] );
+ description = "Definition of systemd per-user socket units.";
+ };
+
+ systemd.user.targets = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
+ description = "Definition of systemd per-user target units.";
+ };
+
+ systemd.user.timers = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ] );
+ description = "Definition of systemd per-user timer units.";
+ };
+
+ systemd.additionalUpstreamSystemUnits = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ example = [ "debug-shell.service" "systemd-quotacheck.service" ];
+ description = ''
+ Additional units shipped with systemd that shall be enabled.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ warnings = concatLists (mapAttrsToList (name: service:
+ optional (service.serviceConfig.Type or "" == "oneshot" && service.serviceConfig.Restart or "no" != "no")
+ "Service ‘${name}.service’ with ‘Type=oneshot’ must have ‘Restart=no’") cfg.services);
+
+ system.build.units = cfg.units;
+
+ environment.systemPackages = [ systemd ];
+
+ environment.etc = let
+ # generate contents for /etc/systemd/system-${type} from attrset of links and packages
+ hooks = type: links: pkgs.runCommand "system-${type}" {
+ preferLocalBuild = true;
+ packages = cfg.packages;
+ } ''
+ set -e
+ mkdir -p $out
+ for package in $packages
+ do
+ for hook in $package/lib/systemd/system-${type}/*
+ do
+ ln -s $hook $out/
+ done
+ done
+ ${concatStrings (mapAttrsToList (exec: target: "ln -s ${target} $out/${exec};\n") links)}
+ '';
+ in ({
+ "systemd/system".source = generateUnits "system" cfg.units upstreamSystemUnits upstreamSystemWants;
+
+ "systemd/user".source = generateUnits "user" cfg.user.units upstreamUserUnits [];
+
+ "systemd/system.conf".text = ''
+ [Manager]
+ ${optionalString config.systemd.enableCgroupAccounting ''
+ DefaultCPUAccounting=yes
+ DefaultBlockIOAccounting=yes
+ DefaultIOAccounting=yes
+ DefaultBlockIOAccounting=yes
+ DefaultIPAccounting=yes
+ ''}
+ DefaultLimitCORE=infinity
+ ${config.systemd.extraConfig}
+ '';
+
+ "systemd/user.conf".text = ''
+ [Manager]
+ ${config.systemd.user.extraConfig}
+ '';
+
+ "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/coredump.conf".text =
+ ''
+ [Coredump]
+ ${config.systemd.coredump.extraConfig}
+ '';
+
+ "systemd/logind.conf".text = ''
+ [Login]
+ 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}
+ '';
+
+ "systemd/sleep.conf".text = ''
+ [Sleep]
+ '';
+
+ # install provided sysctl snippets
+ "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
+ "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
+
+ "tmpfiles.d/journal-nocow.conf".source = "${systemd}/example/tmpfiles.d/journal-nocow.conf";
+ "tmpfiles.d/static-nodes-permissions.conf".source = "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf";
+ "tmpfiles.d/systemd.conf".source = "${systemd}/example/tmpfiles.d/systemd.conf";
+ "tmpfiles.d/systemd-nspawn.conf".source = "${systemd}/example/tmpfiles.d/system-nspawn.conf";
+ "tmpfiles.d/systemd-tmp.conf".source = "${systemd}/example/tmpfiles.d/system-tmp.conf";
+ "tmpfiles.d/var.conf".source = "${systemd}/example/tmpfiles.d/var.conf";
+ "tmpfiles.d/x11.conf".source = "${systemd}/example/tmpfiles.d/x11.conf";
+
+ "tmpfiles.d/nixos.conf".text = ''
+ # This file is created automatically and should not be modified.
+ # Please change the option ‘systemd.tmpfiles.rules’ instead.
+
+ ${concatStringsSep "\n" cfg.tmpfiles.rules}
+ '';
+
+ "systemd/system-generators" = { source = hooks "generators" cfg.generators; };
+ "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; };
+ });
+
+ services.dbus.enable = true;
+
+ 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;
+ users.groups.systemd-resolve.gid = config.ids.gids.systemd-resolve;
+
+ # Target for ‘charon send-keys’ to hook into.
+ users.groups.keys.gid = config.ids.gids.keys;
+
+ systemd.targets.keys =
+ { description = "Security Keys";
+ unitConfig.X-StopOnReconfiguration = true;
+ };
+
+ systemd.units =
+ mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths
+ // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
+ // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices
+ // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets
+ // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
+ // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers
+ // listToAttrs (map
+ (v: let n = escapeSystemdPath v.where;
+ in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
+ // listToAttrs (map
+ (v: let n = escapeSystemdPath v.where;
+ in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
+
+ systemd.user.units =
+ mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.user.paths
+ // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.user.services
+ // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.user.slices
+ // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.user.sockets
+ // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.user.targets
+ // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.user.timers;
+
+ system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled
+ [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET"
+ "SYSFS" "PROC_FS" "FHANDLE" "CRYPTO_USER_API_HASH" "CRYPTO_HMAC"
+ "CRYPTO_SHA256" "DMIID" "AUTOFS4_FS" "TMPFS_POSIX_ACL"
+ "TMPFS_XATTR" "SECCOMP"
+ ];
+
+ users.groups.systemd-journal.gid = config.ids.gids.systemd-journal;
+ users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway;
+ users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway;
+
+ # Generate timer units for all services that have a ‘startAt’ value.
+ systemd.timers =
+ mapAttrs (name: service:
+ { wantedBy = [ "timers.target" ];
+ timerConfig.OnCalendar = service.startAt;
+ })
+ (filterAttrs (name: service: service.enable && service.startAt != []) cfg.services);
+
+ # Generate timer units for all services that have a ‘startAt’ value.
+ systemd.user.timers =
+ mapAttrs (name: service:
+ { wantedBy = [ "timers.target" ];
+ timerConfig.OnCalendar = service.startAt;
+ })
+ (filterAttrs (name: service: service.startAt != []) cfg.user.services);
+
+ systemd.sockets.systemd-journal-gatewayd.wantedBy =
+ optional config.services.journald.enableHttpGateway "sockets.target";
+
+ # Provide the systemd-user PAM service, required to run systemd
+ # user instances.
+ security.pam.services.systemd-user =
+ { # Ensure that pam_systemd gets included. This is special-cased
+ # in systemd to provide XDG_RUNTIME_DIR.
+ startSession = true;
+ };
+
+ # Some overrides to upstream units.
+ systemd.services."systemd-backlight@".restartIfChanged = false;
+ systemd.services."systemd-fsck@".restartIfChanged = false;
+ systemd.services."systemd-fsck@".path = [ config.system.path ];
+ systemd.services."user@".restartIfChanged = false;
+ systemd.services.systemd-journal-flush.restartIfChanged = false;
+ systemd.services.systemd-random-seed.restartIfChanged = false;
+ 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
+ # - this might be addressed in the future by xorg
+ #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;
+ systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true;
+ systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
+ systemd.services.systemd-binfmt.wants = [ "proc-sys-fs-binfmt_misc.mount" ];
+
+ # Don't bother with certain units in containers.
+ systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
+ systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
+ };
+
+ # FIXME: Remove these eventually.
+ imports =
+ [ (mkRenamedOptionModule [ "boot" "systemd" "sockets" ] [ "systemd" "sockets" ])
+ (mkRenamedOptionModule [ "boot" "systemd" "targets" ] [ "systemd" "targets" ])
+ (mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ])
+ ];
+}
diff --git a/nixpkgs/nixos/modules/system/boot/timesyncd.nix b/nixpkgs/nixos/modules/system/boot/timesyncd.nix
new file mode 100644
index 00000000000..8282cdd6f3a
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/timesyncd.nix
@@ -0,0 +1,54 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+
+ options = {
+
+ services.timesyncd = {
+ enable = mkOption {
+ default = !config.boot.isContainer;
+ type = types.bool;
+ description = ''
+ Enables the systemd NTP client daemon.
+ '';
+ };
+ servers = mkOption {
+ default = config.networking.timeServers;
+ description = ''
+ The set of NTP servers from which to synchronise.
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.timesyncd.enable {
+
+ systemd.additionalUpstreamSystemUnits = [ "systemd-timesyncd.service" ];
+
+ systemd.services.systemd-timesyncd = {
+ wantedBy = [ "sysinit.target" ];
+ restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ];
+ };
+
+ environment.etc."systemd/timesyncd.conf".text = ''
+ [Time]
+ NTP=${concatStringsSep " " config.services.timesyncd.servers}
+ '';
+
+ users.users.systemd-timesync.uid = config.ids.uids.systemd-timesync;
+ users.groups.systemd-timesync.gid = config.ids.gids.systemd-timesync;
+
+ system.activationScripts.systemd-timesyncd-migration = mkIf (versionOlder config.system.stateVersion "19.09") ''
+ # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes
+ # - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742
+ # - https://github.com/systemd/systemd/issues/12131
+ if [ -L /var/lib/systemd/timesync ]; then
+ rm /var/lib/systemd/timesync
+ mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync
+ fi
+ '';
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/boot/tmp.nix b/nixpkgs/nixos/modules/system/boot/tmp.nix
new file mode 100644
index 00000000000..5bf5e2eb2ec
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/tmp.nix
@@ -0,0 +1,39 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+
+ ###### interface
+
+ options = {
+
+ boot.cleanTmpDir = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to delete all files in <filename>/tmp</filename> during boot.
+ '';
+ };
+
+ boot.tmpOnTmpfs = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to mount a tmpfs on <filename>/tmp</filename> during boot.
+ '';
+ };
+
+ };
+
+ ###### implementation
+
+ config = {
+
+ systemd.additionalUpstreamSystemUnits = optional config.boot.tmpOnTmpfs "tmp.mount";
+
+ systemd.tmpfiles.rules = optional config.boot.cleanTmpDir "D! /tmp 1777 root root";
+
+ };
+
+} \ No newline at end of file
diff --git a/nixpkgs/nixos/modules/system/etc/etc.nix b/nixpkgs/nixos/modules/system/etc/etc.nix
new file mode 100644
index 00000000000..57ade288096
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/etc/etc.nix
@@ -0,0 +1,162 @@
+# Management of static files in /etc.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ etc' = filter (f: f.enable) (attrValues config.environment.etc);
+
+ etc = pkgs.stdenvNoCC.mkDerivation {
+ name = "etc";
+
+ builder = ./make-etc.sh;
+
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+
+ /* !!! Use toXML. */
+ sources = map (x: x.source) etc';
+ targets = map (x: x.target) etc';
+ modes = map (x: x.mode) etc';
+ users = map (x: x.user) etc';
+ groups = map (x: x.group) etc';
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ environment.etc = mkOption {
+ default = {};
+ example = literalExample ''
+ { example-configuration-file =
+ { source = "/nix/store/.../etc/dir/file.conf.example";
+ mode = "0440";
+ };
+ "default/useradd".text = "GROUP=100 ...";
+ }
+ '';
+ description = ''
+ Set of files that have to be linked in <filename>/etc</filename>.
+ '';
+
+ type = with types; loaOf (submodule (
+ { name, config, ... }:
+ { options = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether this /etc file should be generated. This
+ option allows specific /etc files to be disabled.
+ '';
+ };
+
+ target = mkOption {
+ type = types.str;
+ description = ''
+ Name of symlink (relative to
+ <filename>/etc</filename>). Defaults to the attribute
+ name.
+ '';
+ };
+
+ 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.";
+ };
+
+ mode = mkOption {
+ type = types.str;
+ default = "symlink";
+ example = "0600";
+ description = ''
+ If set to something else than <literal>symlink</literal>,
+ the file is copied instead of symlinked, with the given
+ file mode.
+ '';
+ };
+
+ uid = mkOption {
+ default = 0;
+ type = types.int;
+ description = ''
+ UID of created file. Only takes affect when the file is
+ copied (that is, the mode is not 'symlink').
+ '';
+ };
+
+ gid = mkOption {
+ default = 0;
+ type = types.int;
+ description = ''
+ GID of created file. Only takes affect when the file is
+ copied (that is, the mode is not 'symlink').
+ '';
+ };
+
+ user = mkOption {
+ default = "+${toString config.uid}";
+ type = types.str;
+ description = ''
+ User name of created file.
+ Only takes affect when the file is copied (that is, the mode is not 'symlink').
+ Changing this option takes precedence over <literal>uid</literal>.
+ '';
+ };
+
+ group = mkOption {
+ default = "+${toString config.gid}";
+ type = types.str;
+ description = ''
+ Group name of created file.
+ Only takes affect when the file is copied (that is, the mode is not 'symlink').
+ Changing this option takes precedence over <literal>gid</literal>.
+ '';
+ };
+
+ };
+
+ config = {
+ target = mkDefault name;
+ source = mkIf (config.text != null) (
+ let name' = "etc-" + baseNameOf name;
+ in mkDefault (pkgs.writeText name' config.text));
+ };
+
+ }));
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ system.build.etc = etc;
+
+ system.activationScripts.etc = stringAfter [ "users" "groups" ]
+ ''
+ # Set up the statically computed bits of /etc.
+ echo "setting up /etc..."
+ ${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix} ${./setup-etc.pl} ${etc}/etc
+ '';
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/system/etc/make-etc.sh b/nixpkgs/nixos/modules/system/etc/make-etc.sh
new file mode 100644
index 00000000000..1ca4c3046f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/etc/make-etc.sh
@@ -0,0 +1,46 @@
+source $stdenv/setup
+
+mkdir -p $out/etc
+
+set -f
+sources_=($sources)
+targets_=($targets)
+modes_=($modes)
+users_=($users)
+groups_=($groups)
+set +f
+
+for ((i = 0; i < ${#targets_[@]}; i++)); do
+ source="${sources_[$i]}"
+ target="${targets_[$i]}"
+
+ if [[ "$source" =~ '*' ]]; then
+
+ # If the source name contains '*', perform globbing.
+ mkdir -p $out/etc/$target
+ for fn in $source; do
+ ln -s "$fn" $out/etc/$target/
+ done
+
+ else
+
+ mkdir -p $out/etc/$(dirname $target)
+ if ! [ -e $out/etc/$target ]; then
+ ln -s $source $out/etc/$target
+ else
+ echo "duplicate entry $target -> $source"
+ if test "$(readlink $out/etc/$target)" != "$source"; then
+ echo "mismatched duplicate entry $(readlink $out/etc/$target) <-> $source"
+ exit 1
+ fi
+ fi
+
+ if test "${modes_[$i]}" != symlink; then
+ echo "${modes_[$i]}" > $out/etc/$target.mode
+ echo "${users_[$i]}" > $out/etc/$target.uid
+ echo "${groups_[$i]}" > $out/etc/$target.gid
+ fi
+
+ fi
+done
+
diff --git a/nixpkgs/nixos/modules/system/etc/setup-etc.pl b/nixpkgs/nixos/modules/system/etc/setup-etc.pl
new file mode 100644
index 00000000000..eed20065087
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/etc/setup-etc.pl
@@ -0,0 +1,140 @@
+use strict;
+use File::Find;
+use File::Copy;
+use File::Path;
+use File::Basename;
+use File::Slurp;
+
+my $etc = $ARGV[0] or die;
+my $static = "/etc/static";
+
+sub atomicSymlink {
+ my ($source, $target) = @_;
+ my $tmp = "$target.tmp";
+ unlink $tmp;
+ symlink $source, $tmp or return 0;
+ rename $tmp, $target or return 0;
+ return 1;
+}
+
+
+# Atomically update /etc/static to point at the etc files of the
+# current configuration.
+atomicSymlink $etc, $static or die;
+
+# Returns 1 if the argument points to the files in /etc/static. That
+# means either argument is a symlink to a file in /etc/static or a
+# directory with all children being static.
+sub isStatic {
+ my $path = shift;
+
+ if (-l $path) {
+ my $target = readlink $path;
+ return substr($target, 0, length "/etc/static/") eq "/etc/static/";
+ }
+
+ if (-d $path) {
+ opendir DIR, "$path" or return 0;
+ my @names = readdir DIR or die;
+ closedir DIR;
+
+ foreach my $name (@names) {
+ next if $name eq "." || $name eq "..";
+ unless (isStatic("$path/$name")) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+# Remove dangling symlinks that point to /etc/static. These are
+# configuration files that existed in a previous configuration but not
+# in the current one. For efficiency, don't look under /etc/nixos
+# (where all the NixOS sources live).
+sub cleanup {
+ if ($File::Find::name eq "/etc/nixos") {
+ $File::Find::prune = 1;
+ return;
+ }
+ if (-l $_) {
+ my $target = readlink $_;
+ if (substr($target, 0, length $static) eq $static) {
+ my $x = "/etc/static/" . substr($File::Find::name, length "/etc/");
+ unless (-l $x) {
+ print STDERR "removing obsolete symlink ‘$File::Find::name’...\n";
+ unlink "$_";
+ }
+ }
+ }
+}
+
+find(\&cleanup, "/etc");
+
+
+# Use /etc/.clean to keep track of copied files.
+my @oldCopied = read_file("/etc/.clean", chomp => 1, err_mode => 'quiet');
+open CLEAN, ">>/etc/.clean";
+
+
+# For every file in the etc tree, create a corresponding symlink in
+# /etc to /etc/static. The indirection through /etc/static is to make
+# switching to a new configuration somewhat more atomic.
+my %created;
+my @copied;
+
+sub link {
+ my $fn = substr $File::Find::name, length($etc) + 1 or next;
+ my $target = "/etc/$fn";
+ File::Path::make_path(dirname $target);
+ $created{$fn} = 1;
+
+ # Rename doesn't work if target is directory.
+ if (-l $_ && -d $target) {
+ if (isStatic $target) {
+ rmtree $target or warn;
+ } else {
+ warn "$target directory contains user files. Symlinking may fail.";
+ }
+ }
+
+ if (-e "$_.mode") {
+ my $mode = read_file("$_.mode"); chomp $mode;
+ if ($mode eq "direct-symlink") {
+ atomicSymlink readlink("$static/$fn"), $target or warn;
+ } else {
+ my $uid = read_file("$_.uid"); chomp $uid;
+ my $gid = read_file("$_.gid"); chomp $gid;
+ copy "$static/$fn", "$target.tmp" or warn;
+ $uid = getpwnam $uid unless $uid =~ /^\+/;
+ $gid = getgrnam $gid unless $gid =~ /^\+/;
+ chown int($uid), int($gid), "$target.tmp" or warn;
+ chmod oct($mode), "$target.tmp" or warn;
+ rename "$target.tmp", $target or warn;
+ }
+ push @copied, $fn;
+ print CLEAN "$fn\n";
+ } elsif (-l "$_") {
+ atomicSymlink "$static/$fn", $target or warn;
+ }
+}
+
+find(\&link, $etc);
+
+
+# Delete files that were copied in a previous version but not in the
+# current.
+foreach my $fn (@oldCopied) {
+ if (!defined $created{$fn}) {
+ $fn = "/etc/$fn";
+ print STDERR "removing obsolete file ‘$fn’...\n";
+ unlink "$fn";
+ }
+}
+
+
+# Rewrite /etc/.clean.
+close CLEAN;
+write_file("/etc/.clean", map { "$_\n" } @copied);
diff --git a/nixpkgs/nixos/modules/tasks/auto-upgrade.nix b/nixpkgs/nixos/modules/tasks/auto-upgrade.nix
new file mode 100644
index 00000000000..7fe06699191
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/auto-upgrade.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.system.autoUpgrade; in
+
+{
+
+ options = {
+
+ system.autoUpgrade = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to periodically upgrade NixOS to the latest
+ version. If enabled, a systemd timer will run
+ <literal>nixos-rebuild switch --upgrade</literal> once a
+ day.
+ '';
+ };
+
+ channel = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = https://nixos.org/channels/nixos-14.12-small;
+ description = ''
+ The URI of the NixOS channel to use for automatic
+ upgrades. By default, this is the channel set using
+ <command>nix-channel</command> (run <literal>nix-channel
+ --list</literal> to see the current value).
+ '';
+ };
+
+ flags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "-I" "stuff=/home/alice/nixos-stuff" "--option" "extra-binary-caches" "http://my-cache.example.org/" ];
+ description = ''
+ Any additional flags passed to <command>nixos-rebuild</command>.
+ '';
+ };
+
+ dates = mkOption {
+ default = "04:40";
+ type = types.str;
+ description = ''
+ Specification (in the format described by
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>) of the time at
+ which the update will occur.
+ '';
+ };
+
+ allowReboot = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Reboot the system into the new generation instead of a switch
+ if the new generation uses a different kernel, kernel modules
+ or initrd than the booted system.
+ '';
+ };
+
+ };
+
+ };
+
+ config = lib.mkIf cfg.enable {
+
+ system.autoUpgrade.flags =
+ [ "--no-build-output" ]
+ ++ (if cfg.channel == null
+ then [ "--upgrade" ]
+ else [ "-I" "nixpkgs=${cfg.channel}/nixexprs.tar.xz" ]);
+
+ systemd.services.nixos-upgrade = {
+ description = "NixOS Upgrade";
+
+ restartIfChanged = false;
+ unitConfig.X-StopOnRemoval = false;
+
+ serviceConfig.Type = "oneshot";
+
+ environment = config.nix.envVars //
+ { inherit (config.environment.sessionVariables) NIX_PATH;
+ HOME = "/root";
+ } // config.networking.proxy.envVars;
+
+ path = with pkgs; [ coreutils gnutar xz.bin gzip gitMinimal config.nix.package.out ];
+
+ script = let
+ nixos-rebuild = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild";
+ in
+ if cfg.allowReboot then ''
+ ${nixos-rebuild} boot ${toString cfg.flags}
+ booted="$(readlink /run/booted-system/{initrd,kernel,kernel-modules})"
+ built="$(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
+ if [ "$booted" = "$built" ]; then
+ ${nixos-rebuild} switch ${toString cfg.flags}
+ else
+ /run/current-system/sw/bin/shutdown -r +1
+ fi
+ '' else ''
+ ${nixos-rebuild} switch ${toString cfg.flags}
+ '';
+
+ startAt = cfg.dates;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/tasks/bcache.nix b/nixpkgs/nixos/modules/tasks/bcache.nix
new file mode 100644
index 00000000000..8bab91c721f
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/bcache.nix
@@ -0,0 +1,13 @@
+{ pkgs, ... }:
+
+{
+
+ environment.systemPackages = [ pkgs.bcache-tools ];
+
+ services.udev.packages = [ pkgs.bcache-tools ];
+
+ boot.initrd.extraUdevRulesCommands = ''
+ cp -v ${pkgs.bcache-tools}/lib/udev/rules.d/*.rules $out/
+ '';
+
+}
diff --git a/nixpkgs/nixos/modules/tasks/cpu-freq.nix b/nixpkgs/nixos/modules/tasks/cpu-freq.nix
new file mode 100644
index 00000000000..513382936e4
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/cpu-freq.nix
@@ -0,0 +1,90 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cpupower = config.boot.kernelPackages.cpupower;
+ cfg = config.powerManagement;
+in
+
+{
+ ###### interface
+
+ options.powerManagement = {
+
+ # 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, 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 =
+ 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 = optional governorEnable "cpufreq_${cfg.cpuFreqGovernor}";
+
+ environment.systemPackages = [ cpupower ];
+
+ 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/nixpkgs/nixos/modules/tasks/encrypted-devices.nix b/nixpkgs/nixos/modules/tasks/encrypted-devices.nix
new file mode 100644
index 00000000000..2c9231f5523
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/encrypted-devices.nix
@@ -0,0 +1,76 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+ fileSystems = config.system.build.fileSystems ++ config.swapDevices;
+ encDevs = filter (dev: dev.encrypted.enable) fileSystems;
+ keyedEncDevs = filter (dev: dev.encrypted.keyFile != null) encDevs;
+ keylessEncDevs = filter (dev: dev.encrypted.keyFile == null) encDevs;
+ anyEncrypted =
+ fold (j: v: v || j.encrypted.enable) false encDevs;
+
+ encryptedFSOptions = {
+
+ options.encrypted = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "The block device is backed by an encrypted one, adds this device as a initrd luks entry.";
+ };
+
+ blkDev = mkOption {
+ default = null;
+ example = "/dev/sda1";
+ type = types.nullOr types.str;
+ description = "Location of the backing encrypted device.";
+ };
+
+ label = mkOption {
+ default = null;
+ example = "rootfs";
+ type = types.nullOr types.str;
+ description = "Label of the unlocked encrypted device. Set <literal>fileSystems.&lt;name?&gt;.device</literal> to <literal>/dev/mapper/&lt;label&gt;</literal> to mount the unlocked device.";
+ };
+
+ keyFile = mkOption {
+ default = null;
+ example = "/mnt-root/root/.swapkey";
+ type = types.nullOr types.str;
+ description = "File system location of keyfile. This unlocks the drive after the root has been mounted to <literal>/mnt-root</literal>.";
+ };
+ };
+ };
+in
+
+{
+
+ options = {
+ fileSystems = mkOption {
+ type = with lib.types; loaOf (submodule encryptedFSOptions);
+ };
+ swapDevices = mkOption {
+ type = with lib.types; listOf (submodule encryptedFSOptions);
+ };
+ };
+
+ config = mkIf anyEncrypted {
+ assertions = map (dev: {
+ assertion = dev.encrypted.label != null;
+ message = ''
+ The filesystem for ${dev.mountPoint} has encrypted.enable set to true, but no encrypted.label set
+ '';
+ }) encDevs;
+
+ boot.initrd = {
+ luks = {
+ devices =
+ map (dev: { name = dev.encrypted.label; device = dev.encrypted.blkDev; } ) keylessEncDevs;
+ forceLuksSupportInInitrd = true;
+ };
+ postMountCommands =
+ concatMapStrings (dev: "cryptsetup luksOpen --key-file ${dev.encrypted.keyFile} ${dev.encrypted.blkDev} ${dev.encrypted.label};\n") keyedEncDevs;
+ };
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/tasks/filesystems.nix b/nixpkgs/nixos/modules/tasks/filesystems.nix
new file mode 100644
index 00000000000..688c77cb22d
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems.nix
@@ -0,0 +1,326 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+with utils;
+
+let
+
+ addCheckDesc = desc: elemType: check: types.addCheck elemType check
+ // { description = "${elemType.description} (with check: ${desc})"; };
+ nonEmptyStr = addCheckDesc "non-empty" types.str
+ (x: x != "" && ! (all (c: c == " " || c == "\t") (stringToCharacters x)));
+
+ fileSystems' = toposort fsBefore (attrValues config.fileSystems);
+
+ fileSystems = if fileSystems' ? result
+ then # use topologically sorted fileSystems everywhere
+ fileSystems'.result
+ else # the assertion below will catch this,
+ # but we fall back to the original order
+ # anyway so that other modules could check
+ # their assertions too
+ (attrValues config.fileSystems);
+
+ prioOption = prio: optionalString (prio != null) " pri=${toString prio}";
+
+ specialFSTypes = [ "proc" "sysfs" "tmpfs" "ramfs" "devtmpfs" "devpts" ];
+
+ coreFileSystemOpts = { name, config, ... }: {
+
+ options = {
+
+ mountPoint = mkOption {
+ example = "/mnt/usb";
+ type = nonEmptyStr;
+ description = "Location of the mounted the file system.";
+ };
+
+ device = mkOption {
+ default = null;
+ example = "/dev/sda";
+ type = types.nullOr nonEmptyStr;
+ description = "Location of the device.";
+ };
+
+ fsType = mkOption {
+ default = "auto";
+ example = "ext3";
+ type = nonEmptyStr;
+ description = "Type of the file system.";
+ };
+
+ options = mkOption {
+ default = [ "defaults" ];
+ example = [ "data=journal" ];
+ description = "Options used to mount the file system.";
+ type = types.listOf nonEmptyStr;
+ };
+
+ };
+
+ config = {
+ mountPoint = mkDefault name;
+ device = mkIf (elem config.fsType specialFSTypes) (mkDefault config.fsType);
+ };
+
+ };
+
+ fileSystemOpts = { config, ... }: {
+
+ options = {
+
+ label = mkOption {
+ default = null;
+ example = "root-partition";
+ type = types.nullOr nonEmptyStr;
+ description = "Label of the device (if any).";
+ };
+
+ autoFormat = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If the device does not currently contain a filesystem (as
+ determined by <command>blkid</command>, then automatically
+ format it with the filesystem type specified in
+ <option>fsType</option>. Use with caution.
+ '';
+ };
+
+ formatOptions = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ If <option>autoFormat</option> option is set specifies
+ extra options passed to mkfs.
+ '';
+ };
+
+ autoResize = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If set, the filesystem is grown to its maximum size before
+ being mounted. (This is typically the size of the containing
+ partition.) This is currently only supported for ext2/3/4
+ filesystems that are mounted during early boot.
+ '';
+ };
+
+ noCheck = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Disable running fsck on this filesystem.";
+ };
+
+ };
+
+ config = let
+ defaultFormatOptions =
+ # -F needed to allow bare block device without partitions
+ if (builtins.substring 0 3 config.fsType) == "ext" then "-F"
+ # -q needed for non-interactive operations
+ else if config.fsType == "jfs" then "-q"
+ # (same here)
+ else if config.fsType == "reiserfs" then "-q"
+ else null;
+ in {
+ options = mkIf config.autoResize [ "x-nixos.autoresize" ];
+ formatOptions = mkIf (defaultFormatOptions != null) (mkDefault defaultFormatOptions);
+ };
+
+ };
+
+ # Makes sequence of `specialMount device mountPoint options fsType` commands.
+ # `systemMount` should be defined in the sourcing script.
+ makeSpecialMounts = mounts:
+ pkgs.writeText "mounts.sh" (concatMapStringsSep "\n" (mount: ''
+ specialMount "${mount.device}" "${mount.mountPoint}" "${concatStringsSep "," mount.options}" "${mount.fsType}"
+ '') mounts);
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ fileSystems = mkOption {
+ default = {};
+ example = literalExample ''
+ {
+ "/".device = "/dev/hda1";
+ "/data" = {
+ device = "/dev/hda2";
+ fsType = "ext3";
+ options = [ "data=journal" ];
+ };
+ "/bigdisk".label = "bigdisk";
+ }
+ '';
+ type = types.loaOf (types.submodule [coreFileSystemOpts fileSystemOpts]);
+ description = ''
+ The file systems to be mounted. It must include an entry for
+ the root directory (<literal>mountPoint = "/"</literal>). Each
+ entry in the list is an attribute set with the following fields:
+ <literal>mountPoint</literal>, <literal>device</literal>,
+ <literal>fsType</literal> (a file system type recognised by
+ <command>mount</command>; defaults to
+ <literal>"auto"</literal>), and <literal>options</literal>
+ (the mount options passed to <command>mount</command> using the
+ <option>-o</option> flag; defaults to <literal>[ "defaults" ]</literal>).
+
+ Instead of specifying <literal>device</literal>, you can also
+ specify a volume label (<literal>label</literal>) for file
+ systems that support it, such as ext2/ext3 (see <command>mke2fs
+ -L</command>).
+ '';
+ };
+
+ system.fsPackages = mkOption {
+ internal = true;
+ default = [ ];
+ description = "Packages supplying file system mounters and checkers.";
+ };
+
+ boot.supportedFilesystems = mkOption {
+ default = [ ];
+ example = [ "btrfs" ];
+ type = types.listOf types.str;
+ description = "Names of supported filesystem types.";
+ };
+
+ boot.specialFileSystems = mkOption {
+ default = {};
+ type = types.loaOf (types.submodule coreFileSystemOpts);
+ internal = true;
+ description = ''
+ Special filesystems that are mounted very early during boot.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ assertions = let
+ ls = sep: concatMapStringsSep sep (x: x.mountPoint);
+ notAutoResizable = fs: fs.autoResize && !(hasPrefix "ext" fs.fsType || fs.fsType == "f2fs");
+ in [
+ { assertion = ! (fileSystems' ? cycle);
+ message = "The ‘fileSystems’ option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}";
+ }
+ { assertion = ! (any notAutoResizable fileSystems);
+ message = let
+ fs = head (filter notAutoResizable fileSystems);
+ in
+ "Mountpoint '${fs.mountPoint}': 'autoResize = true' is not supported for 'fsType = \"${fs.fsType}\"':${if fs.fsType == "auto" then " fsType has to be explicitly set and" else ""} only the ext filesystems and f2fs support it.";
+ }
+ ];
+
+ # Export for use in other modules
+ system.build.fileSystems = fileSystems;
+ system.build.earlyMountScript = makeSpecialMounts (toposort fsBefore (attrValues config.boot.specialFileSystems)).result;
+
+ boot.supportedFilesystems = map (fs: fs.fsType) fileSystems;
+
+ # Add the mount helpers to the system path so that `mount' can find them.
+ system.fsPackages = [ pkgs.dosfstools ];
+
+ environment.systemPackages = with pkgs; [ fuse3 fuse ] ++ config.system.fsPackages;
+
+ environment.etc.fstab.text =
+ 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!
+ #
+ # To make changes, edit the fileSystems and swapDevices NixOS options
+ # in your /etc/nixos/configuration.nix file.
+
+ # Filesystems.
+ ${concatMapStrings (fs:
+ (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}’.")
+ + " " + escape fs.mountPoint
+ + " " + fs.fsType
+ + " " + builtins.concatStringsSep "," fs.options
+ + " 0"
+ + " " + (if skipCheck fs then "0" else
+ if fs.mountPoint == "/" then "1" else "2")
+ + "\n"
+ ) fileSystems}
+
+ # Swap devices.
+ ${flip concatMapStrings config.swapDevices (sw:
+ "${sw.realDevice} none swap${prioOption sw.priority}\n"
+ )}
+ '';
+
+ # Provide a target that pulls in all filesystems.
+ systemd.targets.fs =
+ { description = "All File Systems";
+ wants = [ "local-fs.target" "remote-fs.target" ];
+ };
+
+ # Emit systemd services to format requested filesystems.
+ systemd.services =
+ let
+
+ formatDevice = fs:
+ let
+ mountPoint' = "${escapeSystemdPath fs.mountPoint}.mount";
+ device' = escapeSystemdPath fs.device;
+ device'' = "${device'}.device";
+ in nameValuePair "mkfs-${device'}"
+ { description = "Initialisation of Filesystem ${fs.device}";
+ wantedBy = [ mountPoint' ];
+ before = [ mountPoint' "systemd-fsck@${device'}.service" ];
+ requires = [ device'' ];
+ after = [ device'' ];
+ path = [ pkgs.utillinux ] ++ config.system.fsPackages;
+ script =
+ ''
+ if ! [ -e "${fs.device}" ]; then exit 1; fi
+ # FIXME: this is scary. The test could be more robust.
+ type=$(blkid -p -s TYPE -o value "${fs.device}" || true)
+ if [ -z "$type" ]; then
+ echo "creating ${fs.fsType} filesystem on ${fs.device}..."
+ mkfs.${fs.fsType} ${fs.formatOptions} "${fs.device}"
+ fi
+ '';
+ unitConfig.RequiresMountsFor = [ "${dirOf fs.device}" ];
+ unitConfig.DefaultDependencies = false; # needed to prevent a cycle
+ serviceConfig.Type = "oneshot";
+ };
+
+ in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems));
+
+ # Sync mount options with systemd's src/core/mount-setup.c: mount_table.
+ boot.specialFileSystems = {
+ "/proc" = { fsType = "proc"; options = [ "nosuid" "noexec" "nodev" ]; };
+ "/run" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=755" "size=${config.boot.runSize}" ]; };
+ "/dev" = { fsType = "devtmpfs"; options = [ "nosuid" "strictatime" "mode=755" "size=${config.boot.devSize}" ]; };
+ "/dev/shm" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=1777" "size=${config.boot.devShmSize}" ]; };
+ "/dev/pts" = { fsType = "devpts"; options = [ "nosuid" "noexec" "mode=620" "ptmxmode=0666" "gid=${toString config.ids.gids.tty}" ]; };
+
+ # To hold secrets that shouldn't be written to disk (generally used for NixOps, harmless elsewhere)
+ "/run/keys" = { fsType = "ramfs"; options = [ "nosuid" "nodev" "mode=750" "gid=${toString config.ids.gids.keys}" ]; };
+ } // optionalAttrs (!config.boot.isContainer) {
+ # systemd-nspawn populates /sys by itself, and remounting it causes all
+ # kinds of weird issues (most noticeably, waiting for host disk device
+ # nodes).
+ "/sys" = { fsType = "sysfs"; options = [ "nosuid" "noexec" "nodev" ]; };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/bcachefs.nix b/nixpkgs/nixos/modules/tasks/filesystems/bcachefs.nix
new file mode 100644
index 00000000000..5fda24adb97
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/bcachefs.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+
+ 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 (elem "bcachefs" config.boot.supportedFilesystems) (mkMerge [
+ {
+ system.fsPackages = [ pkgs.bcachefs-tools ];
+
+ # use kernel package with bcachefs support until it's in mainline
+ boot.kernelPackages = pkgs.linuxPackages_testing_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 = ''
+ 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/nixpkgs/nixos/modules/tasks/filesystems/btrfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/btrfs.nix
new file mode 100644
index 00000000000..48be18c7102
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/btrfs.nix
@@ -0,0 +1,132 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+
+ inInitrd = any (fs: fs == "btrfs") config.boot.initrd.supportedFilesystems;
+ inSystem = any (fs: fs == "btrfs") config.boot.supportedFilesystems;
+
+ cfgScrub = config.services.btrfs.autoScrub;
+
+ enableAutoScrub = cfgScrub.enable;
+ enableBtrfs = inInitrd || inSystem || enableAutoScrub;
+
+in
+
+{
+ options = {
+ # One could also do regular btrfs balances, but that shouldn't be necessary
+ # during normal usage and as long as the filesystems aren't filled near capacity
+ services.btrfs.autoScrub = {
+ enable = mkEnableOption "regular btrfs scrub";
+
+ fileSystems = mkOption {
+ type = types.listOf types.path;
+ example = [ "/" ];
+ description = ''
+ List of paths to btrfs filesystems to regularily call <command>btrfs scrub</command> on.
+ Defaults to all mount points with btrfs filesystems.
+ If you mount a filesystem multiple times or additionally mount subvolumes,
+ you need to manually specify this list to avoid scrubbing multiple times.
+ '';
+ };
+
+ interval = mkOption {
+ default = "monthly";
+ type = types.str;
+ example = "weekly";
+ description = ''
+ Systemd calendar expression for when to scrub btrfs filesystems.
+ The recommended period is a month but could be less
+ (<citerefentry><refentrytitle>btrfs-scrub</refentrytitle>
+ <manvolnum>8</manvolnum></citerefentry>).
+ See
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>
+ for more information on the syntax.
+ '';
+ };
+
+ };
+ };
+
+ config = mkMerge [
+ (mkIf enableBtrfs {
+ system.fsPackages = [ pkgs.btrfs-progs ];
+
+ boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" "crc32c" ];
+
+ boot.initrd.extraUtilsCommands = mkIf inInitrd
+ ''
+ copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs
+ ln -sv btrfs $out/bin/btrfsck
+ ln -sv btrfsck $out/bin/fsck.btrfs
+ '';
+
+ boot.initrd.extraUtilsCommandsTest = mkIf inInitrd
+ ''
+ $out/bin/btrfs --version
+ '';
+
+ boot.initrd.postDeviceCommands = mkIf inInitrd
+ ''
+ btrfs device scan
+ '';
+ })
+
+ (mkIf enableAutoScrub {
+ assertions = [
+ {
+ assertion = cfgScrub.enable -> (cfgScrub.fileSystems != []);
+ message = ''
+ If 'services.btrfs.autoScrub' is enabled, you need to have at least one
+ btrfs file system mounted via 'fileSystems' or specify a list manually
+ in 'services.btrfs.autoScrub.fileSystems'.
+ '';
+ }
+ ];
+
+ # This will yield duplicated units if the user mounts a filesystem multiple times
+ # or additionally mounts subvolumes, but going the other way around via devices would
+ # yield duplicated units when a filesystem spans multiple devices.
+ # This way around seems like the more sensible default.
+ services.btrfs.autoScrub.fileSystems = mkDefault (mapAttrsToList (name: fs: fs.mountPoint)
+ (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems));
+
+ # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service
+ # template units due to problems enabling the parameterized units,
+ # so settled with many units and templating via nix for now.
+ # https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544
+ systemd.timers = let
+ scrubTimer = fs: let
+ fs' = utils.escapeSystemdPath fs;
+ in nameValuePair "btrfs-scrub-${fs'}" {
+ description = "regular btrfs scrub timer on ${fs}";
+
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = cfgScrub.interval;
+ AccuracySec = "1d";
+ Persistent = true;
+ };
+ };
+ in listToAttrs (map scrubTimer cfgScrub.fileSystems);
+
+ systemd.services = let
+ scrubService = fs: let
+ fs' = utils.escapeSystemdPath fs;
+ in nameValuePair "btrfs-scrub-${fs'}" {
+ description = "btrfs scrub on ${fs}";
+
+ serviceConfig = {
+ Type = "oneshot";
+ Nice = 19;
+ IOSchedulingClass = "idle";
+ ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}";
+ };
+ };
+ in listToAttrs (map scrubService cfgScrub.fileSystems);
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/cifs.nix b/nixpkgs/nixos/modules/tasks/filesystems/cifs.nix
new file mode 100644
index 00000000000..47ba0c03c56
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/cifs.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inInitrd = any (fs: fs == "cifs") config.boot.initrd.supportedFilesystems;
+
+in
+
+{
+ config = {
+
+ system.fsPackages = mkIf (any (fs: fs == "cifs") config.boot.supportedFilesystems) [ pkgs.cifs-utils ];
+
+ boot.initrd.availableKernelModules = mkIf inInitrd
+ [ "cifs" "nls_utf8" "hmac" "md4" "ecb" "des_generic" "sha256" ];
+
+ boot.initrd.extraUtilsCommands = mkIf inInitrd
+ ''
+ copy_bin_and_libs ${pkgs.cifs-utils}/sbin/mount.cifs
+ '';
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/ecryptfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/ecryptfs.nix
new file mode 100644
index 00000000000..12a407cabbf
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/ecryptfs.nix
@@ -0,0 +1,14 @@
+{ config, lib, pkgs, ... }:
+# TODO: make ecryptfs work in initramfs?
+
+with lib;
+
+{
+ config = mkIf (any (fs: fs == "ecryptfs") config.boot.supportedFilesystems) {
+ system.fsPackages = [ pkgs.ecryptfs ];
+ security.wrappers = {
+ "mount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private";
+ "umount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private";
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/exfat.nix b/nixpkgs/nixos/modules/tasks/filesystems/exfat.nix
new file mode 100644
index 00000000000..1527f993fdd
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/exfat.nix
@@ -0,0 +1,11 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = mkIf (any (fs: fs == "exfat") config.boot.supportedFilesystems) {
+
+ system.fsPackages = [ pkgs.exfat ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/ext.nix b/nixpkgs/nixos/modules/tasks/filesystems/ext.nix
new file mode 100644
index 00000000000..a14a3ac3854
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/ext.nix
@@ -0,0 +1,22 @@
+{ pkgs, ... }:
+
+{
+ config = {
+
+ system.fsPackages = [ pkgs.e2fsprogs ];
+
+ # As of kernel 4.3, there is no separate ext3 driver (they're also handled by ext4.ko)
+ boot.initrd.availableKernelModules = [ "ext2" "ext4" ];
+
+ boot.initrd.extraUtilsCommands =
+ ''
+ # Copy e2fsck and friends.
+ copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/e2fsck
+ copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/tune2fs
+ ln -sv e2fsck $out/bin/fsck.ext2
+ ln -sv e2fsck $out/bin/fsck.ext3
+ ln -sv e2fsck $out/bin/fsck.ext4
+ '';
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/f2fs.nix b/nixpkgs/nixos/modules/tasks/filesystems/f2fs.nix
new file mode 100644
index 00000000000..a305235979a
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/f2fs.nix
@@ -0,0 +1,25 @@
+{ config, pkgs, lib, ... }:
+
+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) {
+
+ system.fsPackages = [ pkgs.f2fs-tools ];
+
+ boot.initrd.availableKernelModules = mkIf inInitrd [ "f2fs" "crc32" ];
+
+ 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/nixpkgs/nixos/modules/tasks/filesystems/glusterfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/glusterfs.nix
new file mode 100644
index 00000000000..e8c7fa8efba
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/glusterfs.nix
@@ -0,0 +1,11 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = mkIf (any (fs: fs == "glusterfs") config.boot.supportedFilesystems) {
+
+ system.fsPackages = [ pkgs.glusterfs ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/jfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/jfs.nix
new file mode 100644
index 00000000000..fc3905c7dc2
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/jfs.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ inInitrd = any (fs: fs == "jfs") config.boot.initrd.supportedFilesystems;
+in
+{
+ config = mkIf (any (fs: fs == "jfs") config.boot.supportedFilesystems) {
+
+ system.fsPackages = [ pkgs.jfsutils ];
+
+ boot.initrd.kernelModules = mkIf inInitrd [ "jfs" ];
+
+ boot.initrd.extraUtilsCommands = mkIf inInitrd ''
+ copy_bin_and_libs ${pkgs.jfsutils}/sbin/fsck.jfs
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/nfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/nfs.nix
new file mode 100644
index 00000000000..e0e8bb1f03d
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/nfs.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inInitrd = any (fs: fs == "nfs") config.boot.initrd.supportedFilesystems;
+
+ nfsStateDir = "/var/lib/nfs";
+
+ rpcMountpoint = "${nfsStateDir}/rpc_pipefs";
+
+ idmapdConfFile = pkgs.writeText "idmapd.conf" ''
+ [General]
+ Pipefs-Directory = ${rpcMountpoint}
+ ${optionalString (config.networking.domain != null)
+ "Domain = ${config.networking.domain}"}
+
+ [Mapping]
+ Nobody-User = nobody
+ Nobody-Group = nogroup
+
+ [Translation]
+ Method = nsswitch
+ '';
+
+ nfsConfFile = pkgs.writeText "nfs.conf" cfg.extraConfig;
+
+ cfg = config.services.nfs;
+
+in
+
+{
+ ###### interface
+
+ options = {
+ services.nfs = {
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra nfs-utils configuration.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf (any (fs: fs == "nfs" || fs == "nfs4") config.boot.supportedFilesystems) {
+
+ services.rpcbind.enable = true;
+
+ system.fsPackages = [ pkgs.nfs-utils ];
+
+ boot.initrd.kernelModules = mkIf inInitrd [ "nfs" ];
+
+ systemd.packages = [ pkgs.nfs-utils ];
+
+ environment.etc = {
+ "idmapd.conf".source = idmapdConfFile;
+ "nfs.conf".source = nfsConfFile;
+ };
+
+ systemd.services.nfs-blkmap =
+ { restartTriggers = [ nfsConfFile ];
+ };
+
+ systemd.targets.nfs-client =
+ { wantedBy = [ "multi-user.target" "remote-fs.target" ];
+ };
+
+ systemd.services.nfs-idmapd =
+ { restartTriggers = [ idmapdConfFile ];
+ };
+
+ systemd.services.nfs-mountd =
+ { restartTriggers = [ nfsConfFile ];
+ enable = mkDefault false;
+ };
+
+ systemd.services.nfs-server =
+ { restartTriggers = [ nfsConfFile ];
+ enable = mkDefault false;
+ };
+
+ systemd.services.auth-rpcgss-module =
+ {
+ unitConfig.ConditionPathExists = [ "" "/etc/krb5.keytab" ];
+ };
+
+ systemd.services.rpc-gssd =
+ { restartTriggers = [ nfsConfFile ];
+ unitConfig.ConditionPathExists = [ "" "/etc/krb5.keytab" ];
+ };
+
+ systemd.services.rpc-statd =
+ { restartTriggers = [ nfsConfFile ];
+
+ preStart =
+ ''
+ mkdir -p /var/lib/nfs/{sm,sm.bak}
+ '';
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/ntfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/ntfs.nix
new file mode 100644
index 00000000000..c40d2a1a80b
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/ntfs.nix
@@ -0,0 +1,11 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = mkIf (any (fs: fs == "ntfs" || fs == "ntfs-3g") config.boot.supportedFilesystems) {
+
+ system.fsPackages = [ pkgs.ntfs3g ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/reiserfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/reiserfs.nix
new file mode 100644
index 00000000000..ab4c43e2ab8
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/reiserfs.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inInitrd = any (fs: fs == "reiserfs") config.boot.initrd.supportedFilesystems;
+
+in
+
+{
+ config = mkIf (any (fs: fs == "reiserfs") config.boot.supportedFilesystems) {
+
+ system.fsPackages = [ pkgs.reiserfsprogs ];
+
+ boot.initrd.kernelModules = mkIf inInitrd [ "reiserfs" ];
+
+ boot.initrd.extraUtilsCommands = mkIf inInitrd
+ ''
+ copy_bin_and_libs ${pkgs.reiserfsprogs}/sbin/reiserfsck
+ ln -s reiserfsck $out/bin/fsck.reiserfs
+ '';
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/unionfs-fuse.nix b/nixpkgs/nixos/modules/tasks/filesystems/unionfs-fuse.nix
new file mode 100644
index 00000000000..1dcc4c87e3c
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/unionfs-fuse.nix
@@ -0,0 +1,32 @@
+{ config, pkgs, lib, ... }:
+
+{
+ config = lib.mkMerge [
+
+ (lib.mkIf (lib.any (fs: fs == "unionfs-fuse") config.boot.initrd.supportedFilesystems) {
+ boot.initrd.kernelModules = [ "fuse" ];
+
+ boot.initrd.extraUtilsCommands = ''
+ copy_bin_and_libs ${pkgs.fuse}/sbin/mount.fuse
+ copy_bin_and_libs ${pkgs.unionfs-fuse}/bin/unionfs
+ substitute ${pkgs.unionfs-fuse}/sbin/mount.unionfs-fuse $out/bin/mount.unionfs-fuse \
+ --replace '${pkgs.bash}/bin/bash' /bin/sh \
+ --replace '${pkgs.fuse}/sbin' /bin \
+ --replace '${pkgs.unionfs-fuse}/bin' /bin
+ chmod +x $out/bin/mount.unionfs-fuse
+ '';
+
+ boot.initrd.postDeviceCommands = ''
+ # Hacky!!! fuse hard-codes the path to mount
+ mkdir -p /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${pkgs.utillinux.name}-bin/bin
+ ln -s $(which mount) /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${pkgs.utillinux.name}-bin/bin
+ ln -s $(which umount) /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${pkgs.utillinux.name}-bin/bin
+ '';
+ })
+
+ (lib.mkIf (lib.any (fs: fs == "unionfs-fuse") config.boot.supportedFilesystems) {
+ system.fsPackages = [ pkgs.unionfs-fuse ];
+ })
+
+ ];
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/vboxsf.nix b/nixpkgs/nixos/modules/tasks/filesystems/vboxsf.nix
new file mode 100644
index 00000000000..5497194f6a8
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/vboxsf.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inInitrd = any (fs: fs == "vboxsf") config.boot.initrd.supportedFilesystems;
+
+ package = pkgs.runCommand "mount.vboxsf" { preferLocalBuild = true; } ''
+ mkdir -p $out/bin
+ cp ${pkgs.linuxPackages.virtualboxGuestAdditions}/bin/mount.vboxsf $out/bin
+ '';
+in
+
+{
+ config = mkIf (any (fs: fs == "vboxsf") config.boot.supportedFilesystems) {
+
+ system.fsPackages = [ package ];
+
+ boot.initrd.kernelModules = mkIf inInitrd [ "vboxsf" ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/vfat.nix b/nixpkgs/nixos/modules/tasks/filesystems/vfat.nix
new file mode 100644
index 00000000000..958e27ae8a3
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/vfat.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inInitrd = any (fs: fs == "vfat") config.boot.initrd.supportedFilesystems;
+
+in
+
+{
+ config = mkIf (any (fs: fs == "vfat") config.boot.supportedFilesystems) {
+
+ system.fsPackages = [ pkgs.dosfstools ];
+
+ boot.initrd.kernelModules = mkIf inInitrd [ "vfat" "nls_cp437" "nls_iso8859-1" ];
+
+ boot.initrd.extraUtilsCommands = mkIf inInitrd
+ ''
+ copy_bin_and_libs ${pkgs.dosfstools}/sbin/dosfsck
+ ln -sv dosfsck $out/bin/fsck.vfat
+ '';
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/xfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/xfs.nix
new file mode 100644
index 00000000000..98038701ca5
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/xfs.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inInitrd = any (fs: fs == "xfs") config.boot.initrd.supportedFilesystems;
+
+in
+
+{
+ config = mkIf (any (fs: fs == "xfs") config.boot.supportedFilesystems) {
+
+ system.fsPackages = [ pkgs.xfsprogs.bin ];
+
+ boot.initrd.availableKernelModules = mkIf inInitrd [ "xfs" "crc32c" ];
+
+ boot.initrd.extraUtilsCommands = mkIf inInitrd
+ ''
+ copy_bin_and_libs ${pkgs.xfsprogs.bin}/bin/fsck.xfs
+ copy_bin_and_libs ${pkgs.xfsprogs.bin}/bin/xfs_repair
+ '';
+
+ # Trick just to set 'sh' after the extraUtils nuke-refs.
+ boot.initrd.extraUtilsCommandsTest = mkIf inInitrd
+ ''
+ sed -i -e 's,^#!.*,#!'$out/bin/sh, $out/bin/fsck.xfs
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix
new file mode 100644
index 00000000000..2ed8c5aa292
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix
@@ -0,0 +1,562 @@
+{ config, lib, pkgs, utils, ... }:
+#
+# TODO: zfs tunables
+
+with utils;
+with lib;
+
+let
+
+ cfgZfs = config.boot.zfs;
+ cfgSnapshots = config.services.zfs.autoSnapshot;
+ cfgSnapFlags = cfgSnapshots.flags;
+ cfgScrub = config.services.zfs.autoScrub;
+ cfgTrim = config.services.zfs.trim;
+
+ inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems;
+ inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems;
+
+ enableAutoSnapshots = cfgSnapshots.enable;
+ enableAutoScrub = cfgScrub.enable;
+ enableZfs = inInitrd || inSystem || enableAutoSnapshots || enableAutoScrub;
+
+ kernel = config.boot.kernelPackages;
+
+ packages = if config.boot.zfs.enableUnstable then {
+ zfs = kernel.zfsUnstable;
+ zfsUser = pkgs.zfsUnstable;
+ } else {
+ zfs = kernel.zfs;
+ zfsUser = pkgs.zfs;
+ };
+
+ autosnapPkg = pkgs.zfstools.override {
+ zfs = packages.zfsUser;
+ };
+
+ zfsAutoSnap = "${autosnapPkg}/bin/zfs-auto-snapshot";
+
+ datasetToPool = x: elemAt (splitString "/" x) 0;
+
+ fsToPool = fs: datasetToPool fs.device;
+
+ zfsFilesystems = filter (x: x.fsType == "zfs") config.system.build.fileSystems;
+
+ allPools = unique ((map fsToPool zfsFilesystems) ++ cfgZfs.extraPools);
+
+ rootPools = unique (map fsToPool (filter fsNeededForBoot zfsFilesystems));
+
+ dataPools = unique (filter (pool: !(elem pool rootPools)) allPools);
+
+ snapshotNames = [ "frequent" "hourly" "daily" "weekly" "monthly" ];
+
+ # When importing ZFS pools, there's one difficulty: These scripts may run
+ # before the backing devices (physical HDDs, etc.) of the pool have been
+ # scanned and initialized.
+ #
+ # An attempted import with all devices missing will just fail, and can be
+ # retried, but an import where e.g. two out of three disks in a three-way
+ # mirror are missing, will succeed. This is a problem: When the missing disks
+ # are later discovered, they won't be automatically set online, rendering the
+ # pool redundancy-less (and far slower) until such time as the system reboots.
+ #
+ # The solution is the below. poolReady checks the status of an un-imported
+ # pool, to see if *every* device is available -- in which case the pool will be
+ # in state ONLINE, as opposed to DEGRADED, FAULTED or MISSING.
+ #
+ # The import scripts then loop over this, waiting until the pool is ready or a
+ # sufficient amount of time has passed that we can assume it won't be. In the
+ # latter case it makes one last attempt at importing, allowing the system to
+ # (eventually) boot even with a degraded pool.
+ importLib = {zpoolCmd, awkCmd, cfgZfs}: ''
+ poolReady() {
+ pool="$1"
+ 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
+ echo "Pool $pool in state $state, waiting"
+ return 1
+ fi
+ }
+ poolImported() {
+ pool="$1"
+ "${zpoolCmd}" list "$pool" >/dev/null 2>/dev/null
+ }
+ poolImport() {
+ pool="$1"
+ "${zpoolCmd}" import -d "${cfgZfs.devNodes}" -N $ZFS_FORCE "$pool"
+ }
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+ boot.zfs = {
+ enableUnstable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Use the unstable zfs package. This might be an option, if the latest
+ kernel is not yet supported by a published release of ZFS. Enabling
+ this option will install a development version of ZFS on Linux. The
+ version will have already passed an extensive test suite, but it is
+ more likely to hit an undiscovered bug compared to running a released
+ version of ZFS on Linux.
+ '';
+ };
+
+ extraPools = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "tank" "data" ];
+ description = ''
+ Name or GUID of extra ZFS pools that you wish to import during boot.
+
+ Usually this is not necessary. Instead, you should set the mountpoint property
+ of ZFS filesystems to <literal>legacy</literal> and add the ZFS filesystems to
+ NixOS's <option>fileSystems</option> option, which makes NixOS automatically
+ import the associated pool.
+
+ However, in some cases (e.g. if you have many filesystems) it may be preferable
+ to exclusively use ZFS commands to manage filesystems. If so, since NixOS/systemd
+ will not be managing those filesystems, you will need to specify the ZFS pool here
+ so that NixOS automatically imports it on every boot.
+ '';
+ };
+
+ devNodes = mkOption {
+ type = types.path;
+ default = "/dev/disk/by-id";
+ example = "/dev/disk/by-id";
+ description = ''
+ Name of directory from which to import ZFS devices.
+
+ This should be a path under /dev containing stable names for all devices needed, as
+ import may fail if device nodes are renamed concurrently with a device failing.
+ '';
+ };
+
+ forceImportRoot = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Forcibly import the ZFS root pool(s) during early boot.
+
+ This is enabled by default for backwards compatibility purposes, but it is highly
+ recommended to disable this option, as it bypasses some of the safeguards ZFS uses
+ to protect your ZFS pools.
+
+ If you set this option to <literal>false</literal> and NixOS subsequently fails to
+ boot because it cannot import the root pool, you should boot with the
+ <literal>zfs_force=1</literal> option as a kernel parameter (e.g. by manually
+ editing the kernel params in grub during boot). You should only need to do this
+ once.
+ '';
+ };
+
+ forceImportAll = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Forcibly import all ZFS pool(s).
+
+ This is enabled by default for backwards compatibility purposes, but it is highly
+ recommended to disable this option, as it bypasses some of the safeguards ZFS uses
+ to protect your ZFS pools.
+
+ If you set this option to <literal>false</literal> and NixOS subsequently fails to
+ import your non-root ZFS pool(s), you should manually import each pool with
+ "zpool import -f &lt;pool-name&gt;", and then reboot. You should only need to do
+ this once.
+ '';
+ };
+
+ requestEncryptionCredentials = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Request encryption keys or passwords for all encrypted datasets on import.
+ For root pools the encryption key can be supplied via both an
+ interactive prompt (keylocation=prompt) and from a file
+ (keylocation=file://). Note that for data pools the encryption key can
+ be only loaded from a file and not via interactive prompt since the
+ import is processed in a background systemd service.
+ '';
+ };
+
+ };
+
+ services.zfs.autoSnapshot = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable the (OpenSolaris-compatible) ZFS auto-snapshotting service.
+ Note that you must set the <literal>com.sun:auto-snapshot</literal>
+ property to <literal>true</literal> on all datasets which you wish
+ to auto-snapshot.
+
+ You can override a child dataset to use, or not use auto-snapshotting
+ by setting its flag with the given interval:
+ <literal>zfs set com.sun:auto-snapshot:weekly=false DATASET</literal>
+ '';
+ };
+
+ flags = mkOption {
+ default = "-k -p";
+ example = "-k -p --utc";
+ type = types.str;
+ description = ''
+ Flags to pass to the zfs-auto-snapshot command.
+
+ Run <literal>zfs-auto-snapshot</literal> (without any arguments) to
+ see available flags.
+
+ If it's not too inconvenient for snapshots to have timestamps in UTC,
+ it is suggested that you append <literal>--utc</literal> to the list
+ of default options (see example).
+
+ Otherwise, snapshot names can cause name conflicts or apparent time
+ reversals due to daylight savings, timezone or other date/time changes.
+ '';
+ };
+
+ frequent = mkOption {
+ default = 4;
+ type = types.int;
+ description = ''
+ Number of frequent (15-minute) auto-snapshots that you wish to keep.
+ '';
+ };
+
+ hourly = mkOption {
+ default = 24;
+ type = types.int;
+ description = ''
+ Number of hourly auto-snapshots that you wish to keep.
+ '';
+ };
+
+ daily = mkOption {
+ default = 7;
+ type = types.int;
+ description = ''
+ Number of daily auto-snapshots that you wish to keep.
+ '';
+ };
+
+ weekly = mkOption {
+ default = 4;
+ type = types.int;
+ description = ''
+ Number of weekly auto-snapshots that you wish to keep.
+ '';
+ };
+
+ monthly = mkOption {
+ default = 12;
+ type = types.int;
+ description = ''
+ Number of monthly auto-snapshots that you wish to keep.
+ '';
+ };
+ };
+
+ services.zfs.trim = {
+ enable = mkEnableOption "Enables periodic TRIM on all ZFS pools.";
+
+ interval = mkOption {
+ default = "weekly";
+ type = types.str;
+ example = "daily";
+ description = ''
+ How often we run trim. For most desktop and server systems
+ a sufficient trimming frequency is once a week.
+
+ The format is described in
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ '';
+ };
+ };
+
+ services.zfs.autoScrub = {
+ enable = mkEnableOption "Enables periodic scrubbing of ZFS pools.";
+
+ interval = mkOption {
+ default = "Sun, 02:00";
+ type = types.str;
+ example = "daily";
+ description = ''
+ Systemd calendar expression when to scrub ZFS pools. See
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>.
+ '';
+ };
+
+ pools = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ example = [ "tank" ];
+ description = ''
+ List of ZFS pools to periodically scrub. If empty, all pools
+ will be scrubbed.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkMerge [
+ (mkIf enableZfs {
+ assertions = [
+ {
+ assertion = config.networking.hostId != null;
+ message = "ZFS requires networking.hostId to be set";
+ }
+ {
+ assertion = !cfgZfs.forceImportAll || cfgZfs.forceImportRoot;
+ message = "If you enable boot.zfs.forceImportAll, you must also enable boot.zfs.forceImportRoot";
+ }
+ ];
+
+ virtualisation.lxd.zfsSupport = true;
+
+ boot = {
+ kernelModules = [ "zfs" ];
+ extraModulePackages = with packages; [ zfs ];
+ };
+
+ boot.initrd = mkIf inInitrd {
+ kernelModules = [ "zfs" ] ++ optional (!cfgZfs.enableUnstable) "spl";
+ extraUtilsCommands =
+ ''
+ copy_bin_and_libs ${packages.zfsUser}/sbin/zfs
+ copy_bin_and_libs ${packages.zfsUser}/sbin/zdb
+ copy_bin_and_libs ${packages.zfsUser}/sbin/zpool
+ '';
+ extraUtilsCommandsTest = mkIf inInitrd
+ ''
+ $out/bin/zfs --help >/dev/null 2>&1
+ $out/bin/zpool --help >/dev/null 2>&1
+ '';
+ postDeviceCommands = concatStringsSep "\n" ([''
+ ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}"
+
+ for o in $(cat /proc/cmdline); do
+ case $o in
+ zfs_force|zfs_force=1)
+ ZFS_FORCE="-f"
+ ;;
+ esac
+ done
+ ''] ++ [(importLib {
+ # See comments at importLib definition.
+ zpoolCmd = "zpool";
+ awkCmd = "awk";
+ inherit cfgZfs;
+ })] ++ (map (pool: ''
+ echo -n "importing root ZFS pool \"${pool}\"..."
+ # Loop across the import until it succeeds, because the devices needed may not be discovered yet.
+ if ! poolImported "${pool}"; then
+ for trial in `seq 1 60`; do
+ poolReady "${pool}" > /dev/null && msg="$(poolImport "${pool}" 2>&1)" && break
+ sleep 1
+ echo -n .
+ done
+ echo
+ if [[ -n "$msg" ]]; then
+ echo "$msg";
+ fi
+ poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool.
+ fi
+ ${lib.optionalString cfgZfs.requestEncryptionCredentials ''
+ zfs load-key -a
+ ''}
+ '') rootPools));
+ };
+
+ boot.loader.grub = mkIf inInitrd {
+ zfsSupport = true;
+ };
+
+ environment.etc."zfs/zed.d".source = "${packages.zfsUser}/etc/zfs/zed.d/";
+
+ system.fsPackages = [ packages.zfsUser ]; # XXX: needed? zfs doesn't have (need) a fsck
+ environment.systemPackages = [ packages.zfsUser ]
+ ++ optional enableAutoSnapshots autosnapPkg; # so the user can run the command to see flags
+
+ services.udev.packages = [ packages.zfsUser ]; # to hook zvol naming, etc.
+ systemd.packages = [ packages.zfsUser ];
+
+ systemd.services = let
+ getPoolFilesystems = pool:
+ filter (x: x.fsType == "zfs" && (fsToPool x) == pool) config.system.build.fileSystems;
+
+ getPoolMounts = pool:
+ let
+ mountPoint = fs: escapeSystemdPath fs.mountPoint;
+ in
+ map (x: "${mountPoint x}.mount") (getPoolFilesystems pool);
+
+ createImportService = pool:
+ nameValuePair "zfs-import-${pool}" {
+ description = "Import ZFS pool \"${pool}\"";
+ requires = [ "systemd-udev-settle.service" ];
+ after = [ "systemd-udev-settle.service" "systemd-modules-load.service" ];
+ wantedBy = (getPoolMounts pool) ++ [ "local-fs.target" ];
+ before = (getPoolMounts pool) ++ [ "local-fs.target" ];
+ unitConfig = {
+ DefaultDependencies = "no";
+ };
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ script = (importLib {
+ # See comments at importLib definition.
+ zpoolCmd="${packages.zfsUser}/sbin/zpool";
+ awkCmd="${pkgs.gawk}/bin/awk";
+ inherit cfgZfs;
+ }) + ''
+ poolImported "${pool}" && exit
+ echo -n "importing ZFS pool \"${pool}\"..."
+ # Loop across the import until it succeeds, because the devices needed may not be discovered yet.
+ for trial in `seq 1 60`; do
+ poolReady "${pool}" && poolImport "${pool}" && break
+ sleep 1
+ done
+ poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool.
+ if poolImported "${pool}"; then
+ ${optionalString cfgZfs.requestEncryptionCredentials "\"${packages.zfsUser}/sbin/zfs\" load-key -r \"${pool}\""}
+ echo "Successfully imported ${pool}"
+ else
+ exit 1
+ fi
+ '';
+ };
+
+ # This forces a sync of any ZFS pools prior to poweroff, even if they're set
+ # to sync=disabled.
+ createSyncService = pool:
+ nameValuePair "zfs-sync-${pool}" {
+ description = "Sync ZFS pool \"${pool}\"";
+ wantedBy = [ "shutdown.target" ];
+ unitConfig = {
+ DefaultDependencies = false;
+ };
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ script = ''
+ ${packages.zfsUser}/sbin/zfs set nixos:shutdown-time="$(date)" "${pool}"
+ '';
+ };
+ createZfsService = serv:
+ nameValuePair serv {
+ after = [ "systemd-modules-load.service" ];
+ wantedBy = [ "zfs.target" ];
+ };
+
+ in listToAttrs (map createImportService dataPools ++
+ map createSyncService allPools ++
+ map createZfsService [ "zfs-mount" "zfs-share" "zfs-zed" ]);
+
+ systemd.targets.zfs-import =
+ let
+ services = map (pool: "zfs-import-${pool}.service") dataPools;
+ in
+ {
+ requires = services;
+ after = services;
+ wantedBy = [ "zfs.target" ];
+ };
+
+ systemd.targets.zfs.wantedBy = [ "multi-user.target" ];
+ })
+
+ (mkIf enableAutoSnapshots {
+ systemd.services = let
+ descr = name: if name == "frequent" then "15 mins"
+ else if name == "hourly" then "hour"
+ else if name == "daily" then "day"
+ else if name == "weekly" then "week"
+ else if name == "monthly" then "month"
+ else throw "unknown snapshot name";
+ numSnapshots = name: builtins.getAttr name cfgSnapshots;
+ in builtins.listToAttrs (map (snapName:
+ {
+ name = "zfs-snapshot-${snapName}";
+ value = {
+ description = "ZFS auto-snapshotting every ${descr snapName}";
+ after = [ "zfs-import.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${zfsAutoSnap} ${cfgSnapFlags} ${snapName} ${toString (numSnapshots snapName)}";
+ };
+ restartIfChanged = false;
+ };
+ }) snapshotNames);
+
+ systemd.timers = let
+ timer = name: if name == "frequent" then "*:0,15,30,45" else name;
+ in builtins.listToAttrs (map (snapName:
+ {
+ name = "zfs-snapshot-${snapName}";
+ value = {
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = timer snapName;
+ Persistent = "yes";
+ };
+ };
+ }) snapshotNames);
+ })
+
+ (mkIf enableAutoScrub {
+ systemd.services.zfs-scrub = {
+ description = "ZFS pools scrubbing";
+ after = [ "zfs-import.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ };
+ script = ''
+ ${packages.zfsUser}/bin/zpool scrub ${
+ if cfgScrub.pools != [] then
+ (concatStringsSep " " cfgScrub.pools)
+ else
+ "$(${packages.zfsUser}/bin/zpool list -H -o name)"
+ }
+ '';
+ };
+
+ 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";
+ };
+ };
+ })
+
+ (mkIf cfgTrim.enable {
+ systemd.services.zpool-trim = {
+ description = "ZFS pools trim";
+ after = [ "zfs-import.target" ];
+ path = [ packages.zfsUser ];
+ startAt = cfgTrim.interval;
+ script = ''
+ zpool list -H -o name | xargs -n1 zpool trim
+ '';
+ };
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/tasks/kbd.nix b/nixpkgs/nixos/modules/tasks/kbd.nix
new file mode 100644
index 00000000000..c6ba998b19e
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/kbd.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ makeColor = n: value: "COLOR_${toString n}=${value}";
+ makeColorCS =
+ let positions = [ "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F" ];
+ in n: value: "\\033]P${elemAt positions (n - 1)}${value}";
+ colors = concatImapStringsSep "\n" makeColor config.i18n.consoleColors;
+
+ isUnicode = hasSuffix "UTF-8" (toUpper config.i18n.defaultLocale);
+
+ optimizedKeymap = pkgs.runCommand "keymap" {
+ nativeBuildInputs = [ pkgs.buildPackages.kbd ];
+ LOADKEYS_KEYMAP_PATH = "${kbdEnv}/share/keymaps/**";
+ preferLocalBuild = true;
+ } ''
+ loadkeys -b ${optionalString isUnicode "-u"} "${config.i18n.consoleKeyMap}" > $out
+ '';
+
+ # Sadly, systemd-vconsole-setup doesn't support binary keymaps.
+ vconsoleConf = pkgs.writeText "vconsole.conf" ''
+ KEYMAP=${config.i18n.consoleKeyMap}
+ FONT=${config.i18n.consoleFont}
+ ${colors}
+ '';
+
+ kbdEnv = pkgs.buildEnv {
+ name = "kbd-env";
+ paths = [ pkgs.kbd ] ++ config.i18n.consolePackages;
+ pathsToLink = [ "/share/consolefonts" "/share/consoletrans" "/share/keymaps" "/share/unimaps" ];
+ };
+
+ setVconsole = !config.boot.isContainer;
+in
+
+{
+ ###### interface
+
+ options = {
+
+ # most options are defined in i18n.nix
+
+ # FIXME: still needed?
+ boot.extraTTYs = mkOption {
+ default = [];
+ type = types.listOf types.str;
+ example = ["tty8" "tty9"];
+ description = ''
+ Tty (virtual console) devices, in addition to the consoles on
+ which mingetty and syslogd run, that must be initialised.
+ Only useful if you have some program that you want to run on
+ some fixed console. For example, the NixOS installation CD
+ opens the manual in a web browser on console 7, so it sets
+ <option>boot.extraTTYs</option> to <literal>["tty7"]</literal>.
+ '';
+ };
+
+ boot.earlyVconsoleSetup = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable setting font as early as possible (in initrd).
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkMerge [
+ (mkIf (!setVconsole) {
+ systemd.services.systemd-vconsole-setup.enable = false;
+ })
+
+ (mkIf setVconsole (mkMerge [
+ { environment.systemPackages = [ pkgs.kbd ];
+
+ # Let systemd-vconsole-setup.service do the work of setting up the
+ # virtual consoles.
+ environment.etc."vconsole.conf".source = vconsoleConf;
+ # Provide kbd with additional packages.
+ environment.etc.kbd.source = "${kbdEnv}/share";
+
+ boot.initrd.preLVMCommands = mkBefore ''
+ kbd_mode ${if isUnicode then "-u" else "-a"} -C /dev/console
+ printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console
+ loadkmap < ${optimizedKeymap}
+
+ ${optionalString config.boot.earlyVconsoleSetup ''
+ setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
+ ''}
+
+ ${concatImapStringsSep "\n" (n: color: ''
+ printf "${makeColorCS n color}" >> /dev/console
+ '') config.i18n.consoleColors}
+ '';
+
+ systemd.services.systemd-vconsole-setup =
+ { before = [ "display-manager.service" ];
+ after = [ "systemd-udev-settle.service" ];
+ restartTriggers = [ vconsoleConf kbdEnv ];
+ };
+ }
+
+ (mkIf config.boot.earlyVconsoleSetup {
+ boot.initrd.extraUtilsCommands = ''
+ mkdir -p $out/share/consolefonts
+ ${if substring 0 1 config.i18n.consoleFont == "/" then ''
+ font="${config.i18n.consoleFont}"
+ '' else ''
+ font="$(echo ${kbdEnv}/share/consolefonts/${config.i18n.consoleFont}.*)"
+ ''}
+ if [[ $font == *.gz ]]; then
+ gzip -cd $font > $out/share/consolefonts/font.psf
+ else
+ cp -L $font $out/share/consolefonts/font.psf
+ fi
+ '';
+ })
+ ]))
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/tasks/lvm.nix b/nixpkgs/nixos/modules/tasks/lvm.nix
new file mode 100644
index 00000000000..d56a8a2f63a
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/lvm.nix
@@ -0,0 +1,17 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+ ###### implementation
+
+ config = mkIf (!config.boot.isContainer) {
+
+ environment.systemPackages = [ pkgs.lvm2 ];
+
+ services.udev.packages = [ pkgs.lvm2 ];
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix b/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix
new file mode 100644
index 00000000000..1726d05115e
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -0,0 +1,523 @@
+{ config, lib, pkgs, utils, ... }:
+
+with utils;
+with lib;
+
+let
+
+ cfg = config.networking;
+ interfaces = attrValues cfg.interfaces;
+
+ slaves = concatMap (i: i.interfaces) (attrValues cfg.bonds)
+ ++ concatMap (i: i.interfaces) (attrValues cfg.bridges)
+ ++ concatMap (i: i.interfaces) (attrValues cfg.vswitches)
+ ++ concatMap (i: [i.interface]) (attrValues cfg.macvlans)
+ ++ concatMap (i: [i.interface]) (attrValues cfg.vlans);
+
+ # We must escape interfaces due to the systemd interpretation
+ subsystemDevice = interface:
+ "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
+
+ interfaceIps = i:
+ i.ipv4.addresses
+ ++ optionals cfg.enableIPv6 i.ipv6.addresses;
+
+ destroyBond = i: ''
+ while true; do
+ UPDATED=1
+ SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}')
+ for I in $SLAVES; do
+ UPDATED=0
+ ip link set "$I" nomaster
+ done
+ [ "$UPDATED" -eq "1" ] && break
+ done
+ ip link set "${i}" down 2>/dev/null || true
+ ip link del "${i}" 2>/dev/null || true
+ '';
+
+ # warn that these attributes are deprecated (2017-2-2)
+ # Should be removed in the release after next
+ bondDeprecation = rec {
+ deprecated = [ "lacp_rate" "miimon" "mode" "xmit_hash_policy" ];
+ filterDeprecated = bond: (filterAttrs (attrName: attr:
+ elem attrName deprecated && attr != null) bond);
+ };
+
+ bondWarnings =
+ let oneBondWarnings = bondName: bond:
+ mapAttrsToList (bondText bondName) (bondDeprecation.filterDeprecated bond);
+ bondText = bondName: optName: _:
+ "${bondName}.${optName} is deprecated, use ${bondName}.driverOptions";
+ in {
+ warnings = flatten (mapAttrsToList oneBondWarnings cfg.bonds);
+ };
+
+ normalConfig = {
+
+ systemd.services =
+ let
+
+ deviceDependency = dev:
+ # Use systemd service if we manage device creation, else
+ # trust udev when not in a container
+ if (hasAttr dev (filterAttrs (k: v: v.virtual) cfg.interfaces)) ||
+ (hasAttr dev cfg.bridges) ||
+ (hasAttr dev cfg.bonds) ||
+ (hasAttr dev cfg.macvlans) ||
+ (hasAttr dev cfg.sits) ||
+ (hasAttr dev cfg.vlans) ||
+ (hasAttr dev cfg.vswitches)
+ then [ "${dev}-netdev.service" ]
+ else optional (dev != null && dev != "lo" && !config.boot.isContainer) (subsystemDevice dev);
+
+ hasDefaultGatewaySet = (cfg.defaultGateway != null && cfg.defaultGateway.address != "")
+ || (cfg.enableIPv6 && cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "");
+
+ networkLocalCommands = {
+ after = [ "network-setup.service" ];
+ bindsTo = [ "network-setup.service" ];
+ };
+
+ networkSetup =
+ { description = "Networking Setup";
+
+ after = [ "network-pre.target" "systemd-udevd.service" "systemd-sysctl.service" ];
+ before = [ "network.target" "shutdown.target" ];
+ wants = [ "network.target" ];
+ # 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";
+
+ unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+
+ path = [ pkgs.iproute ];
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+
+ unitConfig.DefaultDependencies = false;
+
+ script =
+ ''
+ ${optionalString config.networking.resolvconf.enable ''
+ # Set the static DNS configuration, if given.
+ ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF
+ ${optionalString (cfg.nameservers != [] && cfg.domain != null) ''
+ domain ${cfg.domain}
+ ''}
+ ${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)}
+ ${flip concatMapStrings cfg.nameservers (ns: ''
+ nameserver ${ns}
+ '')}
+ EOF
+ ''}
+
+ # Set the default gateway.
+ ${optionalString (cfg.defaultGateway != null && cfg.defaultGateway.address != "") ''
+ ${optionalString (cfg.defaultGateway.interface != null) ''
+ ip route replace ${cfg.defaultGateway.address} dev ${cfg.defaultGateway.interface} ${optionalString (cfg.defaultGateway.metric != null)
+ "metric ${toString cfg.defaultGateway.metric}"
+ } proto static
+ ''}
+ ip route replace default ${optionalString (cfg.defaultGateway.metric != null)
+ "metric ${toString cfg.defaultGateway.metric}"
+ } via "${cfg.defaultGateway.address}" ${
+ optionalString (cfg.defaultGatewayWindowSize != null)
+ "window ${toString cfg.defaultGatewayWindowSize}"} ${
+ optionalString (cfg.defaultGateway.interface != null)
+ "dev ${cfg.defaultGateway.interface}"} proto static
+ ''}
+ ${optionalString (cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "") ''
+ ${optionalString (cfg.defaultGateway6.interface != null) ''
+ ip -6 route replace ${cfg.defaultGateway6.address} dev ${cfg.defaultGateway6.interface} ${optionalString (cfg.defaultGateway6.metric != null)
+ "metric ${toString cfg.defaultGateway6.metric}"
+ } proto static
+ ''}
+ ip -6 route replace default ${optionalString (cfg.defaultGateway6.metric != null)
+ "metric ${toString cfg.defaultGateway6.metric}"
+ } via "${cfg.defaultGateway6.address}" ${
+ optionalString (cfg.defaultGatewayWindowSize != null)
+ "window ${toString cfg.defaultGatewayWindowSize}"} ${
+ optionalString (cfg.defaultGateway6.interface != null)
+ "dev ${cfg.defaultGateway6.interface}"} proto static
+ ''}
+ '';
+ };
+
+ # For each interface <foo>, create a job ‘network-addresses-<foo>.service"
+ # that performs static address configuration. It has a "wants"
+ # dependency on ‘<foo>.service’, which is supposed to create
+ # the interface and need not exist (i.e. for hardware
+ # interfaces). It has a binds-to dependency on the actual
+ # network device, so it only gets started after the interface
+ # has appeared, and it's stopped when the interface
+ # disappears.
+ configureAddrs = i:
+ let
+ ips = interfaceIps i;
+ in
+ nameValuePair "network-addresses-${i.name}"
+ { description = "Address configuration of ${i.name}";
+ wantedBy = [
+ "network-setup.service"
+ "network-link-${i.name}.service"
+ "network.target"
+ ];
+ # order before network-setup because the routes that are configured
+ # there may need ip addresses configured
+ before = [ "network-setup.service" ];
+ bindsTo = deviceDependency i.name;
+ after = [ "network-pre.target" ] ++ (deviceDependency i.name);
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ # Restart rather than stop+start this unit to prevent the
+ # network from dying during switch-to-configuration.
+ stopIfChanged = false;
+ path = [ pkgs.iproute ];
+ script =
+ ''
+ state="/run/nixos/network/addresses/${i.name}"
+ mkdir -p $(dirname "$state")
+
+ ${flip concatMapStrings ips (ip:
+ let
+ cidr = "${ip.address}/${toString ip.prefixLength}";
+ in
+ ''
+ echo "${cidr}" >> $state
+ echo -n "adding address ${cidr}... "
+ if out=$(ip addr add "${cidr}" dev "${i.name}" 2>&1); then
+ echo "done"
+ elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
+ echo "'ip addr add "${cidr}" dev "${i.name}"' failed: $out"
+ exit 1
+ fi
+ ''
+ )}
+
+ state="/run/nixos/network/routes/${i.name}"
+ mkdir -p $(dirname "$state")
+
+ ${flip concatMapStrings (i.ipv4.routes ++ i.ipv6.routes) (route:
+ let
+ cidr = "${route.address}/${toString route.prefixLength}";
+ via = optionalString (route.via != null) ''via "${route.via}"'';
+ options = concatStrings (mapAttrsToList (name: val: "${name} ${val} ") route.options);
+ in
+ ''
+ echo "${cidr}" >> $state
+ echo -n "adding route ${cidr}... "
+ if out=$(ip route add "${cidr}" ${options} ${via} dev "${i.name}" proto static 2>&1); then
+ echo "done"
+ elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
+ echo "'ip route add "${cidr}" ${options} ${via} dev "${i.name}"' failed: $out"
+ exit 1
+ fi
+ ''
+ )}
+ '';
+ preStop = ''
+ state="/run/nixos/network/routes/${i.name}"
+ while read cidr; do
+ echo -n "deleting route $cidr... "
+ ip route del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed"
+ done < "$state"
+ rm -f "$state"
+
+ state="/run/nixos/network/addresses/${i.name}"
+ while read cidr; do
+ echo -n "deleting address $cidr... "
+ ip addr del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed"
+ done < "$state"
+ rm -f "$state"
+ '';
+ };
+
+ createTunDevice = i: nameValuePair "${i.name}-netdev"
+ { description = "Virtual Network Interface ${i.name}";
+ bindsTo = [ "dev-net-tun.device" ];
+ after = [ "dev-net-tun.device" "network-pre.target" ];
+ wantedBy = [ "network-setup.service" (subsystemDevice i.name) ];
+ partOf = [ "network-setup.service" ];
+ before = [ "network-setup.service" ];
+ path = [ pkgs.iproute ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ script = ''
+ ip tuntap add dev "${i.name}" mode "${i.virtualType}" user "${i.virtualOwner}"
+ '';
+ postStop = ''
+ ip link del ${i.name} || true
+ '';
+ };
+
+ createBridgeDevice = n: v: nameValuePair "${n}-netdev"
+ (let
+ deps = concatLists (map deviceDependency v.interfaces);
+ in
+ { description = "Bridge Interface ${n}";
+ wantedBy = [ "network-setup.service" (subsystemDevice n) ];
+ bindsTo = deps ++ optional v.rstp "mstpd.service";
+ partOf = [ "network-setup.service" ] ++ optional v.rstp "mstpd.service";
+ after = [ "network-pre.target" ] ++ deps ++ optional v.rstp "mstpd.service"
+ ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces;
+ before = [ "network-setup.service" ];
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ path = [ pkgs.iproute ];
+ script = ''
+ # Remove Dead Interfaces
+ echo "Removing old bridge ${n}..."
+ ip link show "${n}" >/dev/null 2>&1 && ip link del "${n}"
+
+ echo "Adding bridge ${n}..."
+ ip link add name "${n}" type bridge
+
+ # Enslave child interfaces
+ ${flip concatMapStrings v.interfaces (i: ''
+ ip link set "${i}" master "${n}"
+ ip link set "${i}" up
+ '')}
+ # Save list of enslaved interfaces
+ echo "${flip concatMapStrings v.interfaces (i: ''
+ ${i}
+ '')}" > /run/${n}.interfaces
+
+ ${optionalString config.virtualisation.libvirtd.enable ''
+ # Enslave dynamically added interfaces which may be lost on nixos-rebuild
+ for uri in qemu:///system lxc:///; do
+ for dom in $(${pkgs.libvirt}/bin/virsh -c $uri list --name); do
+ ${pkgs.libvirt}/bin/virsh -c $uri dumpxml "$dom" | \
+ ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set ',target/@dev,' master ',source/@bridge,';')" | \
+ ${pkgs.bash}/bin/bash
+ done
+ done
+ ''}
+
+ # Enable stp on the interface
+ ${optionalString v.rstp ''
+ echo 2 >/sys/class/net/${n}/bridge/stp_state
+ ''}
+
+ ip link set "${n}" up
+ '';
+ postStop = ''
+ ip link set "${n}" down || true
+ ip link del "${n}" || true
+ rm -f /run/${n}.interfaces
+ '';
+ reload = ''
+ # Un-enslave child interfaces (old list of interfaces)
+ for interface in `cat /run/${n}.interfaces`; do
+ ip link set "$interface" nomaster up
+ done
+
+ # Enslave child interfaces (new list of interfaces)
+ ${flip concatMapStrings v.interfaces (i: ''
+ ip link set "${i}" master "${n}"
+ ip link set "${i}" up
+ '')}
+ # Save list of enslaved interfaces
+ echo "${flip concatMapStrings v.interfaces (i: ''
+ ${i}
+ '')}" > /run/${n}.interfaces
+
+ # (Un-)set stp on the bridge
+ echo ${if v.rstp then "2" else "0"} > /sys/class/net/${n}/bridge/stp_state
+ '';
+ reloadIfChanged = true;
+ });
+
+ createVswitchDevice = n: v: nameValuePair "${n}-netdev"
+ (let
+ deps = concatLists (map deviceDependency v.interfaces);
+ ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
+ in
+ { description = "Open vSwitch Interface ${n}";
+ wantedBy = [ "network-setup.service" "vswitchd.service" ] ++ deps;
+ bindsTo = [ "vswitchd.service" (subsystemDevice n) ] ++ deps;
+ partOf = [ "network-setup.service" "vswitchd.service" ];
+ after = [ "network-pre.target" "vswitchd.service" ] ++ deps;
+ before = [ "network-setup.service" ];
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ path = [ pkgs.iproute config.virtualisation.vswitch.package ];
+ script = ''
+ echo "Removing old Open vSwitch ${n}..."
+ ovs-vsctl --if-exists del-br ${n}
+
+ echo "Adding Open vSwitch ${n}..."
+ ovs-vsctl -- add-br ${n} ${concatMapStrings (i: " -- add-port ${n} ${i}") v.interfaces} \
+ ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
+ ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
+
+ echo "Adding OpenFlow rules for Open vSwitch ${n}..."
+ ovs-ofctl add-flows ${n} ${ofRules}
+ '';
+ postStop = ''
+ ip link set ${n} down || true
+ ovs-ofctl del-flows ${n} || true
+ ovs-vsctl --if-exists del-br ${n}
+ '';
+ });
+
+ createBondDevice = n: v: nameValuePair "${n}-netdev"
+ (let
+ deps = concatLists (map deviceDependency v.interfaces);
+ in
+ { description = "Bond Interface ${n}";
+ wantedBy = [ "network-setup.service" (subsystemDevice n) ];
+ bindsTo = deps;
+ partOf = [ "network-setup.service" ];
+ after = [ "network-pre.target" ] ++ deps
+ ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces;
+ before = [ "network-setup.service" ];
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ path = [ pkgs.iproute pkgs.gawk ];
+ script = ''
+ echo "Destroying old bond ${n}..."
+ ${destroyBond n}
+
+ echo "Creating new bond ${n}..."
+ ip link add name "${n}" type bond \
+ ${let opts = (mapAttrs (const toString)
+ (bondDeprecation.filterDeprecated v))
+ // v.driverOptions;
+ in concatStringsSep "\n"
+ (mapAttrsToList (set: val: " ${set} ${val} \\") opts)}
+
+ # !!! There must be a better way to wait for the interface
+ while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
+
+ # Bring up the bond and enslave the specified interfaces
+ ip link set "${n}" up
+ ${flip concatMapStrings v.interfaces (i: ''
+ ip link set "${i}" down
+ ip link set "${i}" master "${n}"
+ '')}
+ '';
+ postStop = destroyBond n;
+ });
+
+ createMacvlanDevice = n: v: nameValuePair "${n}-netdev"
+ (let
+ deps = deviceDependency v.interface;
+ in
+ { description = "Vlan Interface ${n}";
+ wantedBy = [ "network-setup.service" (subsystemDevice n) ];
+ bindsTo = deps;
+ partOf = [ "network-setup.service" ];
+ after = [ "network-pre.target" ] ++ deps;
+ before = [ "network-setup.service" ];
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ path = [ pkgs.iproute ];
+ script = ''
+ # Remove Dead Interfaces
+ ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+ ip link add link "${v.interface}" name "${n}" type macvlan \
+ ${optionalString (v.mode != null) "mode ${v.mode}"}
+ ip link set "${n}" up
+ '';
+ postStop = ''
+ ip link delete "${n}" || true
+ '';
+ });
+
+ createSitDevice = n: v: nameValuePair "${n}-netdev"
+ (let
+ deps = deviceDependency v.dev;
+ in
+ { description = "6-to-4 Tunnel Interface ${n}";
+ wantedBy = [ "network-setup.service" (subsystemDevice n) ];
+ bindsTo = deps;
+ partOf = [ "network-setup.service" ];
+ after = [ "network-pre.target" ] ++ deps;
+ before = [ "network-setup.service" ];
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ path = [ pkgs.iproute ];
+ script = ''
+ # Remove Dead Interfaces
+ ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+ ip link add name "${n}" type sit \
+ ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
+ ${optionalString (v.local != null) "local \"${v.local}\""} \
+ ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
+ ${optionalString (v.dev != null) "dev \"${v.dev}\""}
+ ip link set "${n}" up
+ '';
+ postStop = ''
+ ip link delete "${n}" || true
+ '';
+ });
+
+ createVlanDevice = n: v: nameValuePair "${n}-netdev"
+ (let
+ deps = deviceDependency v.interface;
+ in
+ { description = "Vlan Interface ${n}";
+ wantedBy = [ "network-setup.service" (subsystemDevice n) ];
+ bindsTo = deps;
+ partOf = [ "network-setup.service" ];
+ after = [ "network-pre.target" ] ++ deps;
+ before = [ "network-setup.service" ];
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ path = [ pkgs.iproute ];
+ script = ''
+ # Remove Dead Interfaces
+ ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+ ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
+
+ # We try to bring up the logical VLAN interface. If the master
+ # interface the logical interface is dependent upon is not up yet we will
+ # fail to immediately bring up the logical interface. The resulting logical
+ # interface will brought up later when the master interface is up.
+ ip link set "${n}" up || true
+ '';
+ postStop = ''
+ ip link delete "${n}" || true
+ '';
+ });
+
+ in listToAttrs (
+ map configureAddrs interfaces ++
+ map createTunDevice (filter (i: i.virtual) interfaces))
+ // mapAttrs' createBridgeDevice cfg.bridges
+ // mapAttrs' createVswitchDevice cfg.vswitches
+ // mapAttrs' createBondDevice cfg.bonds
+ // mapAttrs' createMacvlanDevice cfg.macvlans
+ // mapAttrs' createSitDevice cfg.sits
+ // mapAttrs' createVlanDevice cfg.vlans
+ // {
+ network-setup = networkSetup;
+ network-local-commands = networkLocalCommands;
+ };
+
+ services.udev.extraRules =
+ ''
+ KERNEL=="tun", TAG+="systemd"
+ '';
+
+
+ };
+
+in
+
+{
+ config = mkMerge [
+ bondWarnings
+ (mkIf (!cfg.useNetworkd) normalConfig)
+ { # Ensure slave interfaces are brought up
+ networking.interfaces = genAttrs slaves (i: {});
+ }
+ ];
+}
diff --git a/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix b/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix
new file mode 100644
index 00000000000..7c6604922cf
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -0,0 +1,243 @@
+{ config, lib, utils, ... }:
+
+with utils;
+with lib;
+
+let
+
+ cfg = config.networking;
+ interfaces = attrValues cfg.interfaces;
+
+ interfaceIps = i:
+ i.ipv4.addresses
+ ++ optionals cfg.enableIPv6 i.ipv6.addresses;
+
+ dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no";
+
+ slaves =
+ concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
+ ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
+ ++ map (sit: sit.dev) (attrValues cfg.sits)
+ ++ map (vlan: vlan.interface) (attrValues cfg.vlans);
+
+in
+
+{
+
+ config = mkIf cfg.useNetworkd {
+
+ assertions = [ {
+ assertion = cfg.defaultGatewayWindowSize == null;
+ message = "networking.defaultGatewayWindowSize is not supported by networkd.";
+ } {
+ assertion = cfg.vswitches == {};
+ message = "networking.vswichtes are not supported by networkd.";
+ } {
+ assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null;
+ message = "networking.defaultGateway.interface is not supported by networkd.";
+ } {
+ assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
+ message = "networking.defaultGateway6.interface is not supported by networkd.";
+ } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
+ assertion = !rstp;
+ message = "networking.bridges.${n}.rstp is not supported by networkd.";
+ });
+
+ networking.dhcpcd.enable = mkDefault false;
+
+ systemd.services.network-local-commands = {
+ after = [ "systemd-networkd.service" ];
+ bindsTo = [ "systemd-networkd.service" ];
+ };
+
+ systemd.network =
+ let
+ domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
+ genericNetwork = override:
+ let gateway = optional (cfg.defaultGateway != null) cfg.defaultGateway.address
+ ++ optional (cfg.defaultGateway6 != null) cfg.defaultGateway6.address;
+ in {
+ DHCP = override (dhcpStr cfg.useDHCP);
+ } // optionalAttrs (gateway != [ ]) {
+ routes = override [
+ {
+ routeConfig = {
+ Gateway = gateway;
+ GatewayOnLink = false;
+ };
+ }
+ ];
+ } // optionalAttrs (domains != [ ]) {
+ domains = override domains;
+ };
+ in mkMerge [ {
+ enable = true;
+ networks."99-main" = (genericNetwork mkDefault) // {
+ # We keep the "broken" behaviour of applying this to all interfaces.
+ # In general we want to get rid of this workaround but there hasn't
+ # been any work on that.
+ # See the following issues for details:
+ # - https://github.com/NixOS/nixpkgs/issues/18962
+ # - https://github.com/NixOS/nixpkgs/issues/61629
+ matchConfig = mkDefault { Name = "*"; };
+ };
+ }
+ (mkMerge (forEach interfaces (i: {
+ netdevs = mkIf i.virtual ({
+ "40-${i.name}" = {
+ netdevConfig = {
+ Name = i.name;
+ Kind = i.virtualType;
+ };
+ "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
+ User = i.virtualOwner;
+ };
+ };
+ });
+ networks."40-${i.name}" = mkMerge [ (genericNetwork mkDefault) {
+ name = mkDefault i.name;
+ DHCP = mkForce (dhcpStr
+ (if i.useDHCP != null then i.useDHCP else cfg.useDHCP && interfaceIps i == [ ]));
+ address = forEach (interfaceIps i)
+ (ip: "${ip.address}/${toString ip.prefixLength}");
+ networkConfig.IPv6PrivacyExtensions = "kernel";
+ } ];
+ })))
+ (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
+ netdevs."40-${name}" = {
+ netdevConfig = {
+ Name = name;
+ Kind = "bridge";
+ };
+ };
+ networks = listToAttrs (forEach bridge.interfaces (bi:
+ nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
+ DHCP = mkOverride 0 (dhcpStr false);
+ networkConfig.Bridge = name;
+ } ])));
+ })))
+ (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: {
+ netdevs."40-${name}" = {
+ netdevConfig = {
+ Name = name;
+ Kind = "bond";
+ };
+ bondConfig = let
+ # manual mapping as of 2017-02-03
+ # man 5 systemd.netdev [BOND]
+ # to https://www.kernel.org/doc/Documentation/networking/bonding.txt
+ # driver options.
+ driverOptionMapping = let
+ trans = f: optName: { valTransform = f; optNames = [optName]; };
+ simp = trans id;
+ ms = trans (v: v + "ms");
+ in {
+ Mode = simp "mode";
+ TransmitHashPolicy = simp "xmit_hash_policy";
+ LACPTransmitRate = simp "lacp_rate";
+ MIIMonitorSec = ms "miimon";
+ UpDelaySec = ms "updelay";
+ DownDelaySec = ms "downdelay";
+ LearnPacketIntervalSec = simp "lp_interval";
+ AdSelect = simp "ad_select";
+ FailOverMACPolicy = simp "fail_over_mac";
+ ARPValidate = simp "arp_validate";
+ # apparently in ms for this value?! Upstream bug?
+ ARPIntervalSec = simp "arp_interval";
+ ARPIPTargets = simp "arp_ip_target";
+ ARPAllTargets = simp "arp_all_targets";
+ PrimaryReselectPolicy = simp "primary_reselect";
+ ResendIGMP = simp "resend_igmp";
+ PacketsPerSlave = simp "packets_per_slave";
+ GratuitousARP = { valTransform = id;
+ optNames = [ "num_grat_arp" "num_unsol_na" ]; };
+ AllSlavesActive = simp "all_slaves_active";
+ MinLinks = simp "min_links";
+ };
+
+ do = bond.driverOptions;
+ assertNoUnknownOption = let
+ knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames)
+ driverOptionMapping);
+ # options that apparently don’t exist in the networkd config
+ unknownOptions = [ "primary" ];
+ assertTrace = bool: msg: if bool then true else builtins.trace msg false;
+ in assert all (driverOpt: assertTrace
+ (elem driverOpt (knownOptions ++ unknownOptions))
+ "The bond.driverOption `${driverOpt}` cannot be mapped to the list of known networkd bond options. Please add it to the mapping above the assert or to `unknownOptions` should it not exist in networkd.")
+ (mapAttrsToList (k: _: k) do); "";
+ # get those driverOptions that have been set
+ filterSystemdOptions = filterAttrs (sysDOpt: kOpts:
+ any (kOpt: do ? ${kOpt}) kOpts.optNames);
+ # build final set of systemd options to bond values
+ buildOptionSet = mapAttrs (_: kOpts: with kOpts;
+ # we simply take the first set kernel bond option
+ # (one option has multiple names, which is silly)
+ head (map (optN: valTransform (do.${optN}))
+ # only map those that exist
+ (filter (o: do ? ${o}) optNames)));
+ in seq assertNoUnknownOption
+ (buildOptionSet (filterSystemdOptions driverOptionMapping));
+
+ };
+
+ networks = listToAttrs (forEach bond.interfaces (bi:
+ nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
+ DHCP = mkOverride 0 (dhcpStr false);
+ networkConfig.Bond = name;
+ } ])));
+ })))
+ (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: {
+ netdevs."40-${name}" = {
+ netdevConfig = {
+ Name = name;
+ Kind = "macvlan";
+ };
+ macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
+ };
+ networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+ macvlan = [ name ];
+ } ]);
+ })))
+ (mkMerge (flip mapAttrsToList cfg.sits (name: sit: {
+ netdevs."40-${name}" = {
+ netdevConfig = {
+ Name = name;
+ Kind = "sit";
+ };
+ tunnelConfig =
+ (optionalAttrs (sit.remote != null) {
+ Remote = sit.remote;
+ }) // (optionalAttrs (sit.local != null) {
+ Local = sit.local;
+ }) // (optionalAttrs (sit.ttl != null) {
+ TTL = sit.ttl;
+ });
+ };
+ networks = mkIf (sit.dev != null) {
+ "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+ tunnel = [ name ];
+ } ]);
+ };
+ })))
+ (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
+ netdevs."40-${name}" = {
+ netdevConfig = {
+ Name = name;
+ Kind = "vlan";
+ };
+ vlanConfig.Id = vlan.id;
+ };
+ networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+ vlan = [ name ];
+ } ]);
+ })))
+ ];
+
+ # We need to prefill the slaved devices with networking options
+ # This forces the network interface creator to initialize slaves.
+ networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves);
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/tasks/network-interfaces.nix b/nixpkgs/nixos/modules/tasks/network-interfaces.nix
new file mode 100644
index 00000000000..5bf7b0d227f
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/network-interfaces.nix
@@ -0,0 +1,1183 @@
+{ config, options, lib, pkgs, utils, ... }:
+
+with lib;
+with utils;
+
+let
+
+ cfg = config.networking;
+ interfaces = attrValues cfg.interfaces;
+ hasVirtuals = any (i: i.virtual) interfaces;
+ hasSits = cfg.sits != { };
+ hasBonds = cfg.bonds != { };
+
+ slaves = concatMap (i: i.interfaces) (attrValues cfg.bonds)
+ ++ concatMap (i: i.interfaces) (attrValues cfg.bridges)
+ ++ concatMap (i: i.interfaces) (attrValues cfg.vswitches);
+
+ slaveIfs = map (i: cfg.interfaces.${i}) (filter (i: cfg.interfaces ? ${i}) slaves);
+
+ rstpBridges = flip filterAttrs cfg.bridges (_: { rstp, ... }: rstp);
+
+ needsMstpd = rstpBridges != { };
+
+ bridgeStp = optional needsMstpd (pkgs.writeTextFile {
+ name = "bridge-stp";
+ executable = true;
+ destination = "/bin/bridge-stp";
+ text = ''
+ #!${pkgs.runtimeShell} -e
+ export PATH="${pkgs.mstpd}/bin"
+
+ BRIDGES=(${concatStringsSep " " (attrNames rstpBridges)})
+ for BRIDGE in $BRIDGES; do
+ if [ "$BRIDGE" = "$1" ]; then
+ if [ "$2" = "start" ]; then
+ mstpctl addbridge "$BRIDGE"
+ exit 0
+ elif [ "$2" = "stop" ]; then
+ mstpctl delbridge "$BRIDGE"
+ exit 0
+ fi
+ exit 1
+ fi
+ done
+ exit 1
+ '';
+ });
+
+ # We must escape interfaces due to the systemd interpretation
+ subsystemDevice = interface:
+ "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
+
+ addrOpts = v:
+ assert v == 4 || v == 6;
+ { options = {
+ address = mkOption {
+ type = types.str;
+ description = ''
+ IPv${toString v} address of the interface. Leave empty to configure the
+ interface using DHCP.
+ '';
+ };
+
+ prefixLength = mkOption {
+ type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
+ description = ''
+ Subnet mask of the interface, specified as the number of
+ bits in the prefix (<literal>${if v == 4 then "24" else "64"}</literal>).
+ '';
+ };
+ };
+ };
+
+ routeOpts = v:
+ { options = {
+ address = mkOption {
+ type = types.str;
+ description = "IPv${toString v} address of the network.";
+ };
+
+ prefixLength = mkOption {
+ type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
+ description = ''
+ Subnet mask of the network, specified as the number of
+ bits in the prefix (<literal>${if v == 4 then "24" else "64"}</literal>).
+ '';
+ };
+
+ via = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "IPv${toString v} address of the next hop.";
+ };
+
+ options = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ example = { mtu = "1492"; window = "524288"; };
+ description = ''
+ Other route options. See the symbol <literal>OPTIONS</literal>
+ in the <literal>ip-route(8)</literal> manual page for the details.
+ '';
+ };
+
+ };
+ };
+
+ gatewayCoerce = address: { inherit address; };
+
+ gatewayOpts = { ... }: {
+
+ options = {
+
+ address = mkOption {
+ type = types.str;
+ description = "The default gateway address.";
+ };
+
+ interface = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "enp0s3";
+ description = "The default gateway interface.";
+ };
+
+ metric = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 42;
+ description = "The default gateway metric/preference.";
+ };
+
+ };
+
+ };
+
+ interfaceOpts = { name, ... }: {
+
+ options = {
+ name = mkOption {
+ example = "eth0";
+ type = types.str;
+ description = "Name of the interface.";
+ };
+
+ preferTempAddress = mkOption {
+ type = types.bool;
+ default = cfg.enableIPv6;
+ defaultText = literalExample "config.networking.enableIPv6";
+ description = ''
+ When using SLAAC prefer a temporary (IPv6) address over the EUI-64
+ address for originating connections. This is used to reduce tracking.
+ '';
+ };
+
+ useDHCP = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Whether this interface should be configured with dhcp.
+ Null implies the old behavior which depends on whether ip addresses
+ are specified or not.
+ '';
+ };
+
+ ipv4.addresses = mkOption {
+ default = [ ];
+ example = [
+ { address = "10.0.0.1"; prefixLength = 16; }
+ { address = "192.168.1.1"; prefixLength = 24; }
+ ];
+ type = with types; listOf (submodule (addrOpts 4));
+ description = ''
+ List of IPv4 addresses that will be statically assigned to the interface.
+ '';
+ };
+
+ ipv6.addresses = mkOption {
+ default = [ ];
+ example = [
+ { address = "fdfd:b3f0:482::1"; prefixLength = 48; }
+ { address = "2001:1470:fffd:2098::e006"; prefixLength = 64; }
+ ];
+ type = with types; listOf (submodule (addrOpts 6));
+ description = ''
+ List of IPv6 addresses that will be statically assigned to the interface.
+ '';
+ };
+
+ ipv4.routes = mkOption {
+ default = [];
+ example = [
+ { address = "10.0.0.0"; prefixLength = 16; }
+ { address = "192.168.2.0"; prefixLength = 24; via = "192.168.1.1"; }
+ ];
+ type = with types; listOf (submodule (routeOpts 4));
+ description = ''
+ List of extra IPv4 static routes that will be assigned to the interface.
+ '';
+ };
+
+ ipv6.routes = mkOption {
+ default = [];
+ example = [
+ { address = "fdfd:b3f0::"; prefixLength = 48; }
+ { address = "2001:1470:fffd:2098::"; prefixLength = 64; via = "fdfd:b3f0::1"; }
+ ];
+ type = with types; listOf (submodule (routeOpts 6));
+ description = ''
+ List of extra IPv6 static routes that will be assigned to the interface.
+ '';
+ };
+
+ macAddress = mkOption {
+ default = null;
+ example = "00:11:22:33:44:55";
+ type = types.nullOr (types.str);
+ description = ''
+ MAC address of the interface. Leave empty to use the default.
+ '';
+ };
+
+ mtu = mkOption {
+ default = null;
+ example = 9000;
+ type = types.nullOr types.int;
+ description = ''
+ MTU size for packets leaving the interface. Leave empty to use the default.
+ '';
+ };
+
+ virtual = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether this interface is virtual and should be created by tunctl.
+ This is mainly useful for creating bridges between a host and a virtual
+ network such as VPN or a virtual machine.
+ '';
+ };
+
+ virtualOwner = mkOption {
+ default = "root";
+ type = types.str;
+ description = ''
+ In case of a virtual device, the user who owns it.
+ '';
+ };
+
+ virtualType = mkOption {
+ default = if hasPrefix "tun" name then "tun" else "tap";
+ defaultText = literalExample ''if hasPrefix "tun" name then "tun" else "tap"'';
+ type = with types; enum [ "tun" "tap" ];
+ description = ''
+ The type of interface to create.
+ The default is TUN for an interface name starting
+ with "tun", otherwise TAP.
+ '';
+ };
+
+ proxyARP = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Turn on proxy_arp for this device (and proxy_ndp for ipv6).
+ This is mainly useful for creating pseudo-bridges between a real
+ interface and a virtual network such as VPN or a virtual machine for
+ interfaces that don't support real bridging (most wlan interfaces).
+ As ARP proxying acts slightly above the link-layer, below-ip traffic
+ isn't bridged, so things like DHCP won't work. The advantage above
+ using NAT lies in the fact that no IP addresses are shared, so all
+ hosts are reachable/routeable.
+
+ WARNING: turns on ip-routing, so if you have multiple interfaces, you
+ should think of the consequence and setup firewall rules to limit this.
+ '';
+ };
+
+ };
+
+ config = {
+ name = mkDefault name;
+ };
+
+ # Renamed or removed options
+ imports =
+ let
+ defined = x: x != "_mkMergedOptionModule";
+ in [
+ (mkRenamedOptionModule [ "ip4" ] [ "ipv4" "addresses"])
+ (mkRenamedOptionModule [ "ip6" ] [ "ipv6" "addresses"])
+ (mkRemovedOptionModule [ "subnetMask" ] ''
+ Supply a prefix length instead; use option
+ networking.interfaces.<name>.ipv{4,6}.addresses'')
+ (mkMergedOptionModule
+ [ [ "ipAddress" ] [ "prefixLength" ] ]
+ [ "ipv4" "addresses" ]
+ (cfg: with cfg;
+ optional (defined ipAddress && defined prefixLength)
+ { address = ipAddress; prefixLength = prefixLength; }))
+ (mkMergedOptionModule
+ [ [ "ipv6Address" ] [ "ipv6PrefixLength" ] ]
+ [ "ipv6" "addresses" ]
+ (cfg: with cfg;
+ optional (defined ipv6Address && defined ipv6PrefixLength)
+ { address = ipv6Address; prefixLength = ipv6PrefixLength; }))
+
+ ({ options.warnings = options.warnings; })
+ ];
+
+ };
+
+ hexChars = stringToCharacters "0123456789abcdef";
+
+ isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ networking.hostName = mkOption {
+ default = "nixos";
+ type = types.str;
+ description = ''
+ The name of the machine. Leave it empty if you want to obtain
+ it from a DHCP server (if using DHCP).
+ '';
+ };
+
+ networking.hostId = mkOption {
+ default = null;
+ example = "4e98920d";
+ type = types.nullOr types.str;
+ description = ''
+ The 32-bit host ID of the machine, formatted as 8 hexadecimal characters.
+
+ You should try to make this ID unique among your machines. You can
+ generate a random 32-bit ID using the following commands:
+
+ <literal>head -c 8 /etc/machine-id</literal>
+
+ (this derives it from the machine-id that systemd generates) or
+
+ <literal>head -c4 /dev/urandom | od -A none -t x4</literal>
+ '';
+ };
+
+ networking.enableIPv6 = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable support for IPv6.
+ '';
+ };
+
+ networking.defaultGateway = mkOption {
+ default = null;
+ example = {
+ address = "131.211.84.1";
+ interface = "enp3s0";
+ };
+ type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts));
+ description = ''
+ The default gateway. It can be left empty if it is auto-detected through DHCP.
+ It can be specified as a string or an option set along with a network interface.
+ '';
+ };
+
+ networking.defaultGateway6 = mkOption {
+ default = null;
+ example = {
+ address = "2001:4d0:1e04:895::1";
+ interface = "enp3s0";
+ };
+ type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts));
+ description = ''
+ The default ipv6 gateway. It can be left empty if it is auto-detected through DHCP.
+ It can be specified as a string or an option set along with a network interface.
+ '';
+ };
+
+ networking.defaultGatewayWindowSize = mkOption {
+ default = null;
+ example = 524288;
+ type = types.nullOr types.int;
+ description = ''
+ The window size of the default gateway. It limits maximal data bursts that TCP peers
+ are allowed to send to us.
+ '';
+ };
+
+ networking.nameservers = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ["130.161.158.4" "130.161.33.17"];
+ description = ''
+ The list of nameservers. It can be left empty if it is auto-detected through DHCP.
+ '';
+ };
+
+ networking.search = mkOption {
+ default = [];
+ example = [ "example.com" "local.domain" ];
+ type = types.listOf types.str;
+ description = ''
+ The list of search paths used when resolving domain names.
+ '';
+ };
+
+ networking.domain = mkOption {
+ default = null;
+ example = "home";
+ type = types.nullOr types.str;
+ description = ''
+ The domain. It can be left empty if it is auto-detected through DHCP.
+ '';
+ };
+
+ networking.useHostResolvConf = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ In containers, whether to use the
+ <filename>resolv.conf</filename> supplied by the host.
+ '';
+ };
+
+ networking.localCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example = "text=anything; echo You can put $text here.";
+ description = ''
+ Shell commands to be executed at the end of the
+ <literal>network-setup</literal> systemd service. Note that if
+ you are using DHCP to obtain the network configuration,
+ interfaces may not be fully configured yet.
+ '';
+ };
+
+ networking.interfaces = mkOption {
+ default = {};
+ example =
+ { eth0.ipv4.addresses = [ {
+ address = "131.211.84.78";
+ prefixLength = 25;
+ } ];
+ };
+ description = ''
+ The configuration for each network interface. If
+ <option>networking.useDHCP</option> is true, then every
+ interface not listed here will be configured using DHCP.
+ '';
+ type = with types; loaOf (submodule interfaceOpts);
+ };
+
+ networking.vswitches = mkOption {
+ default = { };
+ example =
+ { vs0.interfaces = [ "eth0" "eth1" ];
+ vs1.interfaces = [ "eth2" "wlan0" ];
+ };
+ description =
+ ''
+ This option allows you to define Open vSwitches that connect
+ physical networks together. The value of this option is an
+ attribute set. Each attribute specifies a vswitch, with the
+ attribute name specifying the name of the vswitch's network
+ interface.
+ '';
+
+ type = with types; attrsOf (submodule {
+
+ options = {
+
+ interfaces = mkOption {
+ example = [ "eth0" "eth1" ];
+ type = types.listOf types.str;
+ description =
+ "The physical network interfaces connected by the vSwitch.";
+ };
+
+ controllers = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "ptcp:6653:[::1]" ];
+ description = ''
+ Specify the controller targets. For the allowed options see <literal>man 8 ovs-vsctl</literal>.
+ '';
+ };
+
+ openFlowRules = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ actions=normal
+ '';
+ description = ''
+ OpenFlow rules to insert into the Open vSwitch. All <literal>openFlowRules</literal> are
+ loaded with <literal>ovs-ofctl</literal> within one atomic operation.
+ '';
+ };
+
+ extraOvsctlCmds = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ set-fail-mode <switch_name> secure
+ set Bridge <switch_name> stp_enable=true
+ '';
+ description = ''
+ Commands to manipulate the Open vSwitch database. Every line executed with <literal>ovs-vsctl</literal>.
+ All commands are bundled together with the operations for adding the interfaces
+ into one atomic operation.
+ '';
+ };
+
+ };
+
+ });
+
+ };
+
+ networking.bridges = mkOption {
+ default = { };
+ example =
+ { br0.interfaces = [ "eth0" "eth1" ];
+ br1.interfaces = [ "eth2" "wlan0" ];
+ };
+ description =
+ ''
+ This option allows you to define Ethernet bridge devices
+ that connect physical networks together. The value of this
+ option is an attribute set. Each attribute specifies a
+ bridge, with the attribute name specifying the name of the
+ bridge's network interface.
+ '';
+
+ type = with types; attrsOf (submodule {
+
+ options = {
+
+ interfaces = mkOption {
+ example = [ "eth0" "eth1" ];
+ type = types.listOf types.str;
+ description =
+ "The physical network interfaces connected by the bridge.";
+ };
+
+ rstp = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether the bridge interface should enable rstp.";
+ };
+
+ };
+
+ });
+
+ };
+
+ networking.bonds =
+ let
+ driverOptionsExample = {
+ miimon = "100";
+ mode = "active-backup";
+ };
+ in mkOption {
+ default = { };
+ example = literalExample {
+ bond0 = {
+ interfaces = [ "eth0" "wlan0" ];
+ driverOptions = driverOptionsExample;
+ };
+ anotherBond.interfaces = [ "enp4s0f0" "enp4s0f1" "enp5s0f0" "enp5s0f1" ];
+ };
+ description = ''
+ This option allows you to define bond devices that aggregate multiple,
+ underlying networking interfaces together. The value of this option is
+ an attribute set. Each attribute specifies a bond, with the attribute
+ name specifying the name of the bond's network interface
+ '';
+
+ type = with types; attrsOf (submodule {
+
+ options = {
+
+ interfaces = mkOption {
+ example = [ "enp4s0f0" "enp4s0f1" "wlan0" ];
+ type = types.listOf types.str;
+ description = "The interfaces to bond together";
+ };
+
+ driverOptions = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = literalExample driverOptionsExample;
+ description = ''
+ Options for the bonding driver.
+ Documentation can be found in
+ <link xlink:href="https://www.kernel.org/doc/Documentation/networking/bonding.txt" />
+ '';
+
+ };
+
+ lacp_rate = mkOption {
+ default = null;
+ example = "fast";
+ type = types.nullOr types.str;
+ description = ''
+ DEPRECATED, use `driverOptions`.
+ Option specifying the rate in which we'll ask our link partner
+ to transmit LACPDU packets in 802.3ad mode.
+ '';
+ };
+
+ miimon = mkOption {
+ default = null;
+ example = 100;
+ type = types.nullOr types.int;
+ description = ''
+ DEPRECATED, use `driverOptions`.
+ Miimon is the number of millisecond in between each round of polling
+ by the device driver for failed links. By default polling is not
+ enabled and the driver is trusted to properly detect and handle
+ failure scenarios.
+ '';
+ };
+
+ mode = mkOption {
+ default = null;
+ example = "active-backup";
+ type = types.nullOr types.str;
+ description = ''
+ DEPRECATED, use `driverOptions`.
+ The mode which the bond will be running. The default mode for
+ the bonding driver is balance-rr, optimizing for throughput.
+ More information about valid modes can be found at
+ https://www.kernel.org/doc/Documentation/networking/bonding.txt
+ '';
+ };
+
+ xmit_hash_policy = mkOption {
+ default = null;
+ example = "layer2+3";
+ type = types.nullOr types.str;
+ description = ''
+ DEPRECATED, use `driverOptions`.
+ Selects the transmit hash policy to use for slave selection in
+ balance-xor, 802.3ad, and tlb modes.
+ '';
+ };
+
+ };
+
+ });
+ };
+
+ networking.macvlans = mkOption {
+ default = { };
+ example = literalExample {
+ wan = {
+ interface = "enp2s0";
+ mode = "vepa";
+ };
+ };
+ description = ''
+ This option allows you to define macvlan interfaces which should
+ be automatically created.
+ '';
+ type = with types; attrsOf (submodule {
+ options = {
+
+ interface = mkOption {
+ example = "enp4s0";
+ type = types.str;
+ description = "The interface the macvlan will transmit packets through.";
+ };
+
+ mode = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ example = "vepa";
+ description = "The mode of the macvlan device.";
+ };
+
+ };
+
+ });
+ };
+
+ networking.sits = mkOption {
+ default = { };
+ example = literalExample {
+ hurricane = {
+ remote = "10.0.0.1";
+ local = "10.0.0.22";
+ ttl = 255;
+ };
+ msipv6 = {
+ remote = "192.168.0.1";
+ dev = "enp3s0";
+ ttl = 127;
+ };
+ };
+ description = ''
+ This option allows you to define 6-to-4 interfaces which should be automatically created.
+ '';
+ type = with types; attrsOf (submodule {
+ options = {
+
+ remote = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "10.0.0.1";
+ description = ''
+ The address of the remote endpoint to forward traffic over.
+ '';
+ };
+
+ local = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "10.0.0.22";
+ description = ''
+ The address of the local endpoint which the remote
+ side should send packets to.
+ '';
+ };
+
+ ttl = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 255;
+ description = ''
+ The time-to-live of the connection to the remote tunnel endpoint.
+ '';
+ };
+
+ dev = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "enp4s0f0";
+ description = ''
+ The underlying network device on which the tunnel resides.
+ '';
+ };
+
+ };
+
+ });
+ };
+
+ networking.vlans = mkOption {
+ default = { };
+ example = literalExample {
+ vlan0 = {
+ id = 3;
+ interface = "enp3s0";
+ };
+ vlan1 = {
+ id = 1;
+ interface = "wlan0";
+ };
+ };
+ description =
+ ''
+ This option allows you to define vlan devices that tag packets
+ on top of a physical interface. The value of this option is an
+ attribute set. Each attribute specifies a vlan, with the name
+ specifying the name of the vlan interface.
+ '';
+
+ type = with types; attrsOf (submodule {
+
+ options = {
+
+ id = mkOption {
+ example = 1;
+ type = types.int;
+ description = "The vlan identifier";
+ };
+
+ interface = mkOption {
+ example = "enp4s0";
+ type = types.str;
+ description = "The interface the vlan will transmit packets through.";
+ };
+
+ };
+
+ });
+
+ };
+
+ networking.wlanInterfaces = mkOption {
+ default = { };
+ example = literalExample {
+ wlan-station0 = {
+ device = "wlp6s0";
+ };
+ wlan-adhoc0 = {
+ type = "ibss";
+ device = "wlp6s0";
+ mac = "02:00:00:00:00:01";
+ };
+ wlan-p2p0 = {
+ device = "wlp6s0";
+ mac = "02:00:00:00:00:02";
+ };
+ wlan-ap0 = {
+ device = "wlp6s0";
+ mac = "02:00:00:00:00:03";
+ };
+ };
+ description =
+ ''
+ Creating multiple WLAN interfaces on top of one physical WLAN device (NIC).
+
+ The name of the WLAN interface corresponds to the name of the attribute.
+ A NIC is referenced by the persistent device name of the WLAN interface that
+ <literal>udev</literal> assigns to a NIC by default.
+ If a NIC supports multiple WLAN interfaces, then the one NIC can be used as
+ <literal>device</literal> for multiple WLAN interfaces.
+ If a NIC is used for creating WLAN interfaces, then the default WLAN interface
+ with a persistent device name form <literal>udev</literal> is not created.
+ A WLAN interface with the persistent name assigned from <literal>udev</literal>
+ would have to be created explicitly.
+ '';
+
+ type = with types; attrsOf (submodule {
+
+ options = {
+
+ device = mkOption {
+ type = types.str;
+ example = "wlp6s0";
+ description = "The name of the underlying hardware WLAN device as assigned by <literal>udev</literal>.";
+ };
+
+ type = mkOption {
+ type = types.enum [ "managed" "ibss" "monitor" "mesh" "wds" ];
+ default = "managed";
+ example = "ibss";
+ description = ''
+ The type of the WLAN interface.
+ The type has to be supported by the underlying hardware of the device.
+ '';
+ };
+
+ meshID = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "MeshID of interface with type <literal>mesh</literal>.";
+ };
+
+ flags = mkOption {
+ type = with types; nullOr (enum [ "none" "fcsfail" "control" "otherbss" "cook" "active" ]);
+ default = null;
+ example = "control";
+ description = ''
+ Flags for interface of type <literal>monitor</literal>.
+ '';
+ };
+
+ fourAddr = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether to enable <literal>4-address mode</literal> with type <literal>managed</literal>.";
+ };
+
+ mac = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "02:00:00:00:00:01";
+ description = ''
+ MAC address to use for the device. If <literal>null</literal>, then the MAC of the
+ underlying hardware WLAN device is used.
+
+ INFO: Locally administered MAC addresses are of the form:
+ <itemizedlist>
+ <listitem><para>x2:xx:xx:xx:xx:xx</para></listitem>
+ <listitem><para>x6:xx:xx:xx:xx:xx</para></listitem>
+ <listitem><para>xA:xx:xx:xx:xx:xx</para></listitem>
+ <listitem><para>xE:xx:xx:xx:xx:xx</para></listitem>
+ </itemizedlist>
+ '';
+ };
+
+ };
+
+ });
+
+ };
+
+ networking.useDHCP = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to use DHCP to obtain an IP address and other
+ configuration for all network interfaces that are not manually
+ configured.
+ '';
+ };
+
+ networking.useNetworkd = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether we should use networkd as the network configuration backend or
+ the legacy script based system. Note that this option is experimental,
+ enable at your own risk.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ warnings = concatMap (i: i.warnings) interfaces;
+
+ assertions =
+ (forEach interfaces (i: {
+ # With the linux kernel, interface name length is limited by IFNAMSIZ
+ # to 16 bytes, including the trailing null byte.
+ # See include/linux/if.h in the kernel sources
+ assertion = stringLength i.name < 16;
+ message = ''
+ The name of networking.interfaces."${i.name}" is too long, it needs to be less than 16 characters.
+ '';
+ })) ++ (forEach slaveIfs (i: {
+ assertion = i.ipv4.addresses == [ ] && i.ipv6.addresses == [ ];
+ message = ''
+ The networking.interfaces."${i.name}" must not have any defined ips when it is a slave.
+ '';
+ })) ++ (forEach interfaces (i: {
+ assertion = i.preferTempAddress -> cfg.enableIPv6;
+ message = ''
+ Temporary addresses are only needed when IPv6 is enabled.
+ '';
+ })) ++ [
+ {
+ assertion = cfg.hostId == null || (stringLength cfg.hostId == 8 && isHexString cfg.hostId);
+ message = "Invalid value given to the networking.hostId option.";
+ }
+ ];
+
+ boot.kernelModules = [ ]
+ ++ optional cfg.enableIPv6 "ipv6"
+ ++ optional hasVirtuals "tun"
+ ++ optional hasSits "sit"
+ ++ optional hasBonds "bonding";
+
+ boot.extraModprobeConfig =
+ # This setting is intentional as it prevents default bond devices
+ # from being created.
+ optionalString hasBonds "options bonding max_bonds=0";
+
+ boot.kernel.sysctl = {
+ "net.ipv6.conf.all.disable_ipv6" = mkDefault (!cfg.enableIPv6);
+ "net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6);
+ "net.ipv6.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
+ } // listToAttrs (flip concatMap (filter (i: i.proxyARP) interfaces)
+ (i: forEach [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true)))
+ // listToAttrs (forEach (filter (i: i.preferTempAddress) interfaces)
+ (i: nameValuePair "net.ipv6.conf.${i.name}.use_tempaddr" 2));
+
+ # Capabilities won't work unless we have at-least a 4.3 Linux
+ # kernel because we need the ambient capability
+ security.wrappers = if (versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.3") then {
+ ping = {
+ source = "${pkgs.iputils.out}/bin/ping";
+ capabilities = "cap_net_raw+p";
+ };
+ } else {
+ ping.source = "${pkgs.iputils.out}/bin/ping";
+ };
+
+ # Set the host and domain names in the activation script. Don't
+ # clear it if it's not configured in the NixOS configuration,
+ # since it may have been set by dhcpcd in the meantime.
+ system.activationScripts.hostname =
+ optionalString (cfg.hostName != "") ''
+ hostname "${cfg.hostName}"
+ '';
+ system.activationScripts.domain =
+ optionalString (cfg.domain != null) ''
+ domainname "${cfg.domain}"
+ '';
+
+ environment.etc.hostid = mkIf (cfg.hostId != null)
+ { source = pkgs.runCommand "gen-hostid" { preferLocalBuild = true; } ''
+ hi="${cfg.hostId}"
+ ${if pkgs.stdenv.isBigEndian then ''
+ echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > $out
+ '' else ''
+ echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > $out
+ ''}
+ '';
+ };
+
+ # static hostname configuration needed for hostnamectl and the
+ # org.freedesktop.hostname1 dbus service (both provided by systemd)
+ environment.etc.hostname = mkIf (cfg.hostName != "")
+ {
+ text = cfg.hostName + "\n";
+ };
+
+ environment.systemPackages =
+ [ pkgs.host
+ pkgs.iproute
+ pkgs.iputils
+ pkgs.nettools
+ ]
+ ++ optionals config.networking.wireless.enable [
+ pkgs.wirelesstools # FIXME: obsolete?
+ pkgs.iw
+ pkgs.rfkill
+ ]
+ ++ bridgeStp;
+
+ # The network-interfaces target is kept for backwards compatibility.
+ # New modules must NOT use it.
+ systemd.targets.network-interfaces =
+ { description = "All Network Interfaces (deprecated)";
+ wantedBy = [ "network.target" ];
+ before = [ "network.target" ];
+ after = [ "network-pre.target" ];
+ unitConfig.X-StopOnReconfiguration = true;
+ };
+
+ systemd.services = {
+ network-local-commands = {
+ description = "Extra networking commands.";
+ before = [ "network.target" ];
+ wantedBy = [ "network.target" ];
+ after = [ "network-pre.target" ];
+ unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+ path = [ pkgs.iproute ];
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ script = ''
+ # Run any user-specified commands.
+ ${cfg.localCommands}
+ '';
+ };
+ } // (listToAttrs (forEach interfaces (i:
+ let
+ deviceDependency = if (config.boot.isContainer || i.name == "lo")
+ then []
+ else [ (subsystemDevice i.name) ];
+ in
+ nameValuePair "network-link-${i.name}"
+ { description = "Link configuration of ${i.name}";
+ wantedBy = [ "network-interfaces.target" ];
+ before = [ "network-interfaces.target" ];
+ bindsTo = deviceDependency;
+ after = [ "network-pre.target" ] ++ deviceDependency;
+ path = [ pkgs.iproute ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ script =
+ ''
+ echo "Configuring link..."
+ '' + optionalString (i.macAddress != null) ''
+ echo "setting MAC address to ${i.macAddress}..."
+ ip link set "${i.name}" address "${i.macAddress}"
+ '' + optionalString (i.mtu != null) ''
+ echo "setting MTU to ${toString i.mtu}..."
+ ip link set "${i.name}" mtu "${toString i.mtu}"
+ '' + ''
+ echo -n "bringing up interface... "
+ ip link set "${i.name}" up && echo "done" || (echo "failed"; exit 1)
+ '';
+ })));
+
+ services.mstpd = mkIf needsMstpd { enable = true; };
+
+ virtualisation.vswitch = mkIf (cfg.vswitches != { }) { enable = true; };
+
+ services.udev.packages = [
+ (pkgs.writeTextFile rec {
+ name = "ipv6-privacy-extensions.rules";
+ destination = "/etc/udev/rules.d/98-${name}";
+ text = ''
+ # enable and prefer IPv6 privacy addresses by default
+ ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.%k.use_tempaddr=2"
+ '';
+ })
+ (pkgs.writeTextFile rec {
+ name = "ipv6-privacy-extensions.rules";
+ destination = "/etc/udev/rules.d/99-${name}";
+ text = concatMapStrings (i: ''
+ # enable IPv6 privacy addresses but prefer EUI-64 addresses for ${i.name}
+ ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${i.name}.use_tempaddr=1"
+ '') (filter (i: !i.preferTempAddress) interfaces);
+ })
+ ] ++ lib.optional (cfg.wlanInterfaces != {})
+ (pkgs.writeTextFile {
+ name = "99-zzz-40-wlanInterfaces.rules";
+ destination = "/etc/udev/rules.d/99-zzz-40-wlanInterfaces.rules";
+ text =
+ let
+ # Collect all interfaces that are defined for a device
+ # as device:interface key:value pairs.
+ wlanDeviceInterfaces =
+ let
+ allDevices = unique (mapAttrsToList (_: v: v.device) cfg.wlanInterfaces);
+ interfacesOfDevice = d: filterAttrs (_: v: v.device == d) cfg.wlanInterfaces;
+ in
+ genAttrs allDevices (d: interfacesOfDevice d);
+
+ # Convert device:interface key:value pairs into a list, and if it exists,
+ # place the interface which is named after the device at the beginning.
+ wlanListDeviceFirst = device: interfaces:
+ if hasAttr device interfaces
+ then mapAttrsToList (n: v: v//{_iName=n;}) (filterAttrs (n: _: n==device) interfaces) ++ mapAttrsToList (n: v: v//{_iName=n;}) (filterAttrs (n: _: n!=device) interfaces)
+ else mapAttrsToList (n: v: v // {_iName = n;}) interfaces;
+
+ # Udev script to execute for the default WLAN interface with the persistend udev name.
+ # The script creates the required, new WLAN interfaces interfaces and configures the
+ # existing, default interface.
+ curInterfaceScript = device: current: new: pkgs.writeScript "udev-run-script-wlan-interfaces-${device}.sh" ''
+ #!${pkgs.runtimeShell}
+ # Change the wireless phy device to a predictable name.
+ ${pkgs.iw}/bin/iw phy `${pkgs.coreutils}/bin/cat /sys/class/net/$INTERFACE/phy80211/name` set name ${device}
+
+ # Add new WLAN interfaces
+ ${flip concatMapStrings new (i: ''
+ ${pkgs.iw}/bin/iw phy ${device} interface add ${i._iName} type managed
+ '')}
+
+ # Configure the current interface
+ ${pkgs.iw}/bin/iw dev ${device} set type ${current.type}
+ ${optionalString (current.type == "mesh" && current.meshID!=null) "${pkgs.iw}/bin/iw dev ${device} set meshid ${current.meshID}"}
+ ${optionalString (current.type == "monitor" && current.flags!=null) "${pkgs.iw}/bin/iw dev ${device} set monitor ${current.flags}"}
+ ${optionalString (current.type == "managed" && current.fourAddr!=null) "${pkgs.iw}/bin/iw dev ${device} set 4addr ${if current.fourAddr then "on" else "off"}"}
+ ${optionalString (current.mac != null) "${pkgs.iproute}/bin/ip link set dev ${device} address ${current.mac}"}
+ '';
+
+ # Udev script to execute for a new WLAN interface. The script configures the new WLAN interface.
+ newInterfaceScript = device: new: pkgs.writeScript "udev-run-script-wlan-interfaces-${new._iName}.sh" ''
+ #!${pkgs.runtimeShell}
+ # Configure the new interface
+ ${pkgs.iw}/bin/iw dev ${new._iName} set type ${new.type}
+ ${optionalString (new.type == "mesh" && new.meshID!=null) "${pkgs.iw}/bin/iw dev ${device} set meshid ${new.meshID}"}
+ ${optionalString (new.type == "monitor" && new.flags!=null) "${pkgs.iw}/bin/iw dev ${device} set monitor ${new.flags}"}
+ ${optionalString (new.type == "managed" && new.fourAddr!=null) "${pkgs.iw}/bin/iw dev ${device} set 4addr ${if new.fourAddr then "on" else "off"}"}
+ ${optionalString (new.mac != null) "${pkgs.iproute}/bin/ip link set dev ${device} address ${new.mac}"}
+ '';
+
+ # Udev attributes for systemd to name the device and to create a .device target.
+ systemdAttrs = n: ''NAME:="${n}", ENV{INTERFACE}:="${n}", ENV{SYSTEMD_ALIAS}:="/sys/subsystem/net/devices/${n}", TAG+="systemd"'';
+ in
+ flip (concatMapStringsSep "\n") (attrNames wlanDeviceInterfaces) (device:
+ let
+ interfaces = wlanListDeviceFirst device wlanDeviceInterfaces.${device};
+ curInterface = elemAt interfaces 0;
+ newInterfaces = drop 1 interfaces;
+ in ''
+ # It is important to have that rule first as overwriting the NAME attribute also prevents the
+ # next rules from matching.
+ ${flip (concatMapStringsSep "\n") (wlanListDeviceFirst device wlanDeviceInterfaces.${device}) (interface:
+ ''ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", ENV{INTERFACE}=="${interface._iName}", ${systemdAttrs interface._iName}, RUN+="${newInterfaceScript device interface}"'')}
+
+ # Add the required, new WLAN interfaces to the default WLAN interface with the
+ # persistent, default name as assigned by udev.
+ ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}, RUN+="${curInterfaceScript device curInterface newInterfaces}"
+ # Generate the same systemd events for both 'add' and 'move' udev events.
+ ACTION=="move", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}
+ '');
+ });
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/tasks/powertop.nix b/nixpkgs/nixos/modules/tasks/powertop.nix
new file mode 100644
index 00000000000..609831506e1
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/powertop.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.powerManagement.powertop;
+in {
+ ###### interface
+
+ options.powerManagement.powertop.enable = mkEnableOption "powertop auto tuning on startup";
+
+ ###### implementation
+
+ config = mkIf (cfg.enable) {
+ systemd.services = {
+ powertop = {
+ wantedBy = [ "multi-user.target" ];
+ description = "Powertop tunings";
+ path = [ pkgs.kmod ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = "yes";
+ ExecStart = "${pkgs.powertop}/bin/powertop --auto-tune";
+ };
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/tasks/scsi-link-power-management.nix b/nixpkgs/nixos/modules/tasks/scsi-link-power-management.nix
new file mode 100644
index 00000000000..a9d987780ee
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/scsi-link-power-management.nix
@@ -0,0 +1,54 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.powerManagement.scsiLinkPolicy;
+
+ kernel = config.boot.kernelPackages.kernel;
+
+ allowedValues = [
+ "min_power"
+ "max_performance"
+ "medium_power"
+ "med_power_with_dipm"
+ ];
+
+in
+
+{
+ ###### interface
+
+ options = {
+
+ powerManagement.scsiLinkPolicy = mkOption {
+ default = null;
+ type = types.nullOr (types.enum allowedValues);
+ description = ''
+ SCSI link power management policy. The kernel default is
+ "max_performance".
+ </para><para>
+ "med_power_with_dipm" is supported by kernel versions
+ 4.15 and newer.
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf (cfg != null) {
+
+ assertions = singleton {
+ assertion = (cfg == "med_power_with_dipm") -> versionAtLeast kernel.version "4.15";
+ message = "med_power_with_dipm is not supported for kernels older than 4.15";
+ };
+
+ services.udev.extraRules = ''
+ SUBSYSTEM=="scsi_host", ACTION=="add", KERNEL=="host*", ATTR{link_power_management_policy}="${cfg}"
+ '';
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/tasks/swraid.nix b/nixpkgs/nixos/modules/tasks/swraid.nix
new file mode 100644
index 00000000000..8fa19194bed
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/swraid.nix
@@ -0,0 +1,17 @@
+{ pkgs, ... }:
+
+{
+
+ environment.systemPackages = [ pkgs.mdadm ];
+
+ services.udev.packages = [ pkgs.mdadm ];
+
+ systemd.packages = [ pkgs.mdadm ];
+
+ boot.initrd.availableKernelModules = [ "md_mod" "raid0" "raid1" "raid10" "raid456" ];
+
+ boot.initrd.extraUdevRulesCommands = ''
+ cp -v ${pkgs.mdadm}/lib/udev/rules.d/*.rules $out/
+ '';
+
+}
diff --git a/nixpkgs/nixos/modules/tasks/trackpoint.nix b/nixpkgs/nixos/modules/tasks/trackpoint.nix
new file mode 100644
index 00000000000..b154cf9f5f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/trackpoint.nix
@@ -0,0 +1,107 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ ###### interface
+
+ options = {
+
+ hardware.trackpoint = {
+
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable sensitivity and speed configuration for trackpoints.
+ '';
+ };
+
+ sensitivity = mkOption {
+ default = 128;
+ example = 255;
+ type = types.int;
+ description = ''
+ Configure the trackpoint sensitivity. By default, the kernel
+ configures 128.
+ '';
+ };
+
+ speed = mkOption {
+ default = 97;
+ example = 255;
+ type = types.int;
+ description = ''
+ Configure the trackpoint speed. By default, the kernel
+ configures 97.
+ '';
+ };
+
+ emulateWheel = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Enable scrolling while holding the middle mouse button.
+ '';
+ };
+
+ fakeButtons = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Switch to "bare" PS/2 mouse support in case Trackpoint buttons are not recognized
+ properly. This can happen for example on models like the L430, T450, T450s, on
+ which the Trackpoint buttons are actually a part of the Synaptics touchpad.
+ '';
+ };
+
+ device = mkOption {
+ default = "TPPS/2 IBM TrackPoint";
+ type = types.str;
+ description = ''
+ The device name of the trackpoint. You can check with xinput.
+ Some newer devices (example x1c6) use "TPPS/2 Elan TrackPoint".
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config =
+ let cfg = config.hardware.trackpoint; in
+ mkMerge [
+ (mkIf cfg.enable {
+ services.udev.extraRules =
+ ''
+ ACTION=="add|change", SUBSYSTEM=="input", ATTR{name}=="${cfg.device}", ATTR{device/speed}="${toString cfg.speed}", ATTR{device/sensitivity}="${toString cfg.sensitivity}"
+ '';
+
+ system.activationScripts.trackpoint =
+ ''
+ ${config.systemd.package}/bin/udevadm trigger --attr-match=name="${cfg.device}"
+ '';
+ })
+
+ (mkIf (cfg.emulateWheel) {
+ services.xserver.inputClassSections =
+ [''
+ Identifier "Trackpoint Wheel Emulation"
+ MatchProduct "${if cfg.fakeButtons then "PS/2 Generic Mouse" else "ETPS/2 Elantech TrackPoint|Elantech PS/2 TrackPoint|TPPS/2 IBM TrackPoint|DualPoint Stick|Synaptics Inc. Composite TouchPad / TrackPoint|ThinkPad USB Keyboard with TrackPoint|USB Trackpoint pointing device|Composite TouchPad / TrackPoint|${cfg.device}"}"
+ MatchDevicePath "/dev/input/event*"
+ Option "EmulateWheel" "true"
+ Option "EmulateWheelButton" "2"
+ Option "Emulate3Buttons" "false"
+ Option "XAxisMapping" "6 7"
+ Option "YAxisMapping" "4 5"
+ ''];
+ })
+
+ (mkIf cfg.fakeButtons {
+ boot.extraModprobeConfig = "options psmouse proto=bare";
+ })
+ ];
+}
diff --git a/nixpkgs/nixos/modules/tasks/tty-backgrounds-combine.sh b/nixpkgs/nixos/modules/tasks/tty-backgrounds-combine.sh
new file mode 100644
index 00000000000..55c3a1ebfa8
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/tty-backgrounds-combine.sh
@@ -0,0 +1,32 @@
+source $stdenv/setup
+
+ttys=($ttys)
+themes=($themes)
+
+mkdir -p $out
+
+defaultName=$(cd $default && ls | grep -v default)
+echo $defaultName
+ln -s $default/$defaultName $out/$defaultName
+ln -s $defaultName $out/default
+
+for ((n = 0; n < ${#ttys[*]}; n++)); do
+ tty=${ttys[$n]}
+ theme=${themes[$n]}
+
+ echo "TTY $tty -> $theme"
+
+ if [ "$theme" != default ]; then
+ themeName=$(cd $theme && ls | grep -v default)
+ ln -sfn $theme/$themeName $out/$themeName
+ else
+ themeName=default
+ fi
+
+ if test -e $out/$tty; then
+ echo "Multiple themes defined for the same TTY!"
+ exit 1
+ fi
+
+ ln -sfn $themeName $out/$tty
+done
diff --git a/nixpkgs/nixos/modules/testing/minimal-kernel.nix b/nixpkgs/nixos/modules/testing/minimal-kernel.nix
new file mode 100644
index 00000000000..7c2b9c05cf9
--- /dev/null
+++ b/nixpkgs/nixos/modules/testing/minimal-kernel.nix
@@ -0,0 +1,28 @@
+{ config, pkgs, lib, ... }:
+
+let
+ configfile = builtins.storePath (builtins.toFile "config" (lib.concatStringsSep "\n"
+ (map (builtins.getAttr "configLine") config.system.requiredKernelConfig))
+ );
+
+ origKernel = pkgs.buildLinux {
+ inherit (pkgs.linux) src version stdenv;
+ inherit configfile;
+ allowImportFromDerivation = true;
+ kernelPatches = [ pkgs.kernelPatches.cifs_timeout_2_6_38 ];
+ };
+
+ kernel = origKernel // (derivation (origKernel.drvAttrs // {
+ configurePhase = ''
+ runHook preConfigure
+ mkdir ../build
+ make $makeFlags "''${makeFlagsArray[@]}" mrproper
+ make $makeFlags "''${makeFlagsArray[@]}" KCONFIG_ALLCONFIG=${configfile} allnoconfig
+ runHook postConfigure
+ '';
+ }));
+
+ kernelPackages = pkgs.linuxPackagesFor kernel;
+in {
+ boot.kernelPackages = kernelPackages;
+}
diff --git a/nixpkgs/nixos/modules/testing/service-runner.nix b/nixpkgs/nixos/modules/testing/service-runner.nix
new file mode 100644
index 00000000000..17d5e337690
--- /dev/null
+++ b/nixpkgs/nixos/modules/testing/service-runner.nix
@@ -0,0 +1,115 @@
+{ lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ makeScript = name: service: pkgs.writeScript "${name}-runner"
+ ''
+ #! ${pkgs.perl}/bin/perl -w -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix}
+
+ use File::Slurp;
+
+ sub run {
+ my ($cmd) = @_;
+ my @args = split " ", $cmd;
+ my $prog;
+ if (substr($args[0], 0, 1) eq "@") {
+ $prog = substr($args[0], 1);
+ shift @args;
+ } else {
+ $prog = $args[0];
+ }
+ my $pid = fork;
+ if ($pid == 0) {
+ setpgrp; # don't receive SIGINT etc. from terminal
+ exec { $prog } @args;
+ die "failed to exec $prog\n";
+ } elsif (!defined $pid) {
+ die "failed to fork: $!\n";
+ }
+ return $pid;
+ };
+
+ sub run_wait {
+ my ($cmd) = @_;
+ my $pid = run $cmd;
+ die if waitpid($pid, 0) != $pid;
+ return $?;
+ };
+
+ # Set the environment. FIXME: escaping.
+ foreach my $key (keys %ENV) {
+ next if $key eq 'LOCALE_ARCHIVE';
+ delete $ENV{$key};
+ }
+ ${concatStrings (mapAttrsToList (n: v: ''
+ $ENV{'${n}'} = '${v}';
+ '') service.environment)}
+
+ # Run the ExecStartPre program. FIXME: this could be a list.
+ my $preStart = '${service.serviceConfig.ExecStartPre or ""}';
+ if ($preStart ne "") {
+ print STDERR "running ExecStartPre: $preStart\n";
+ my $res = run_wait $preStart;
+ die "$0: ExecStartPre failed with status $res\n" if $res;
+ };
+
+ # Run the ExecStart program.
+ my $cmd = '${service.serviceConfig.ExecStart}';
+ print STDERR "running ExecStart: $cmd\n";
+ my $mainPid = run $cmd;
+ $ENV{'MAINPID'} = $mainPid;
+
+ # Catch SIGINT, propagate to the main program.
+ sub intHandler {
+ print STDERR "got SIGINT, stopping service...\n";
+ kill 'INT', $mainPid;
+ };
+ $SIG{'INT'} = \&intHandler;
+ $SIG{'QUIT'} = \&intHandler;
+
+ # Run the ExecStartPost program.
+ my $postStart = '${service.serviceConfig.ExecStartPost or ""}';
+ if ($postStart ne "") {
+ print STDERR "running ExecStartPost: $postStart\n";
+ my $res = run_wait $postStart;
+ die "$0: ExecStartPost failed with status $res\n" if $res;
+ }
+
+ # Wait for the main program to exit.
+ die if waitpid($mainPid, 0) != $mainPid;
+ my $mainRes = $?;
+
+ # Run the ExecStopPost program.
+ my $postStop = '${service.serviceConfig.ExecStopPost or ""}';
+ if ($postStop ne "") {
+ print STDERR "running ExecStopPost: $postStop\n";
+ my $res = run_wait $postStop;
+ die "$0: ExecStopPost failed with status $res\n" if $res;
+ }
+
+ 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 {
+ type = with types; attrsOf (submodule opts);
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/testing/test-instrumentation.nix b/nixpkgs/nixos/modules/testing/test-instrumentation.nix
new file mode 100644
index 00000000000..1a11d9ce7c2
--- /dev/null
+++ b/nixpkgs/nixos/modules/testing/test-instrumentation.nix
@@ -0,0 +1,134 @@
+# This module allows the test driver to connect to the virtual machine
+# via a root shell attached to port 514.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+with import ../../lib/qemu-flags.nix { inherit pkgs; };
+
+{
+
+ # This option is a dummy that if used in conjunction with
+ # modules/virtualisation/qemu-vm.nix gets merged with the same option defined
+ # there and only is declared here because some modules use
+ # test-instrumentation.nix but not qemu-vm.nix.
+ #
+ # One particular example are the boot tests where we want instrumentation
+ # within the images but not other stuff like setting up 9p filesystems.
+ options.virtualisation.qemu.program = mkOption { type = types.path; };
+
+ config = {
+
+ systemd.services.backdoor =
+ { wantedBy = [ "multi-user.target" ];
+ requires = [ "dev-hvc0.device" "dev-${qemuSerialDevice}.device" ];
+ after = [ "dev-hvc0.device" "dev-${qemuSerialDevice}.device" ];
+ script =
+ ''
+ export USER=root
+ export HOME=/root
+ export DISPLAY=:0.0
+
+ source /etc/profile
+
+ # Don't use a pager when executing backdoor
+ # actions. Because we use a tty, commands like systemctl
+ # or nix-store get confused into thinking they're running
+ # interactively.
+ export PAGER=
+
+ cd /tmp
+ exec < /dev/hvc0 > /dev/hvc0
+ while ! exec 2> /dev/${qemuSerialDevice}; do sleep 0.1; done
+ echo "connecting to host..." >&2
+ stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
+ echo
+ PS1= exec /bin/sh
+ '';
+ serviceConfig.KillSignal = "SIGHUP";
+ };
+
+ # Prevent agetty from being instantiated on the serial device, since it
+ # interferes with the backdoor (writes to it will randomly fail
+ # with EIO). Likewise for hvc0.
+ systemd.services."serial-getty@${qemuSerialDevice}".enable = false;
+ systemd.services."serial-getty@hvc0".enable = false;
+
+ # Only use a serial console, no TTY.
+ virtualisation.qemu.consoles = [ qemuSerialDevice ];
+
+ boot.initrd.preDeviceCommands =
+ ''
+ echo 600 > /proc/sys/kernel/hung_task_timeout_secs
+ '';
+
+ boot.initrd.postDeviceCommands =
+ ''
+ # Using acpi_pm as a clock source causes the guest clock to
+ # slow down under high host load. This is usually a bad
+ # thing, but for VM tests it should provide a bit more
+ # determinism (e.g. if the VM runs at lower speed, then
+ # timeouts in the VM should also be delayed).
+ echo acpi_pm > /sys/devices/system/clocksource/clocksource0/current_clocksource
+ '';
+
+ boot.postBootCommands =
+ ''
+ # Panic on out-of-memory conditions rather than letting the
+ # OOM killer randomly get rid of processes, since this leads
+ # to failures that are hard to diagnose.
+ echo 2 > /proc/sys/vm/panic_on_oom
+
+ # Coverage data is written into /tmp/coverage-data.
+ mkdir -p /tmp/xchg/coverage-data
+ '';
+
+ # If the kernel has been built with coverage instrumentation, make
+ # it available under /proc/gcov.
+ boot.kernelModules = [ "gcov-proc" ];
+
+ # Panic if an error occurs in stage 1 (rather than waiting for
+ # user intervention).
+ boot.kernelParams =
+ [ "console=${qemuSerialDevice}" "panic=1" "boot.panic_on_fail" ];
+
+ # `xwininfo' is used by the test driver to query open windows.
+ environment.systemPackages = [ pkgs.xorg.xwininfo ];
+
+ # Log everything to the serial console.
+ services.journald.extraConfig =
+ ''
+ ForwardToConsole=yes
+ MaxLevelConsole=debug
+ '';
+
+ systemd.extraConfig = ''
+ # Don't clobber the console with duplicate systemd messages.
+ ShowStatus=no
+ # Allow very slow start
+ DefaultTimeoutStartSec=300
+ '';
+
+ boot.consoleLogLevel = 7;
+
+ # Prevent tests from accessing the Internet.
+ networking.defaultGateway = mkOverride 150 "";
+ networking.nameservers = mkOverride 150 [ ];
+
+ systemd.globalEnvironment.GCOV_PREFIX = "/tmp/xchg/coverage-data";
+
+ system.requiredKernelConfig = with config.lib.kernelConfig; [
+ (isYes "SERIAL_8250_CONSOLE")
+ (isYes "SERIAL_8250")
+ (isEnabled "VIRTIO_CONSOLE")
+ ];
+
+ networking.usePredictableInterfaceNames = false;
+
+ # Make it easy to log in as root when running the test interactively.
+ users.users.root.initialHashedPassword = mkOverride 150 "";
+
+ services.xserver.displayManager.job.logToJournal = true;
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/amazon-image.nix b/nixpkgs/nixos/modules/virtualisation/amazon-image.nix
new file mode 100644
index 00000000000..aadfc5add35
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/amazon-image.nix
@@ -0,0 +1,153 @@
+# Configuration for Amazon EC2 instances. (Note that this file is a
+# misnomer - it should be "amazon-config.nix" or so, not
+# "amazon-image.nix", since it's used not only to build images but
+# also to reconfigure instances. However, we can't rename it because
+# existing "configuration.nix" files on EC2 instances refer to it.)
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+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 ];
+
+ config = {
+
+ assertions = [
+ { assertion = cfg.hvm;
+ message = "Paravirtualized EC2 instances are no longer supported.";
+ }
+ { assertion = cfg.efi -> cfg.hvm;
+ message = "EC2 instances using EFI must be HVM instances.";
+ }
+ ];
+
+ boot.growPartition = cfg.hvm;
+
+ fileSystems."/" = {
+ device = "/dev/disk/by-label/nixos";
+ fsType = "ext4";
+ autoResize = true;
+ };
+
+ fileSystems."/boot" = mkIf cfg.efi {
+ device = "/dev/disk/by-label/ESP";
+ fsType = "vfat";
+ };
+
+ boot.extraModulePackages = [
+ config.boot.kernelPackages.ena
+ ];
+ boot.initrd.kernelModules = [ "xen-blkfront" "xen-netfront" ];
+ boot.initrd.availableKernelModules = [ "ixgbevf" "ena" "nvme" ];
+ boot.kernelParams = mkIf cfg.hvm [ "console=ttyS0" ];
+
+ # Prevent the nouveau kernel module from being loaded, as it
+ # interferes with the nvidia/nvidia-uvm modules needed for CUDA.
+ # Also blacklist xen_fbfront to prevent a 30 second delay during
+ # boot.
+ boot.blacklistedKernelModules = [ "nouveau" "xen_fbfront" ];
+
+ # Generate a GRUB menu. Amazon's pv-grub uses this to boot our kernel/initrd.
+ boot.loader.grub.version = if cfg.hvm then 2 else 1;
+ boot.loader.grub.device = if (cfg.hvm && !cfg.efi) then "/dev/xvda" else "nodev";
+ boot.loader.grub.extraPerEntryConfig = mkIf (!cfg.hvm) "root (hd0)";
+ boot.loader.grub.efiSupport = cfg.efi;
+ boot.loader.grub.efiInstallAsRemovable = cfg.efi;
+ boot.loader.timeout = 0;
+
+ boot.initrd.network.enable = true;
+
+ # 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.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
+ # by layering a unionfs-fuse mount on top of it so we have a lot more space for
+ # Nix operations.
+ boot.initrd.postMountCommands =
+ ''
+ ${metadataFetcher}
+
+ diskNr=0
+ diskForUnionfs=
+ for device in /dev/xvd[abcde]*; do
+ if [ "$device" = /dev/xvda -o "$device" = /dev/xvda1 ]; then continue; fi
+ fsType=$(blkid -o value -s TYPE "$device" || true)
+ if [ "$fsType" = swap ]; then
+ echo "activating swap device $device..."
+ swapon "$device" || true
+ elif [ "$fsType" = ext3 ]; then
+ mp="/disk$diskNr"
+ diskNr=$((diskNr + 1))
+ if mountFS "$device" "$mp" "" ext3; then
+ if [ -z "$diskForUnionfs" ]; then diskForUnionfs="$mp"; fi
+ fi
+ else
+ echo "skipping unknown device type $device"
+ fi
+ done
+
+ if [ -n "$diskForUnionfs" ]; then
+ mkdir -m 755 -p $targetRoot/$diskForUnionfs/root
+
+ mkdir -m 1777 -p $targetRoot/$diskForUnionfs/root/tmp $targetRoot/tmp
+ mount --bind $targetRoot/$diskForUnionfs/root/tmp $targetRoot/tmp
+
+ if [ "$(cat "$metaDir/ami-manifest-path")" != "(unknown)" ]; then
+ mkdir -m 755 -p $targetRoot/$diskForUnionfs/root/var $targetRoot/var
+ mount --bind $targetRoot/$diskForUnionfs/root/var $targetRoot/var
+
+ mkdir -p /unionfs-chroot/ro-nix
+ mount --rbind $targetRoot/nix /unionfs-chroot/ro-nix
+
+ mkdir -m 755 -p $targetRoot/$diskForUnionfs/root/nix
+ mkdir -p /unionfs-chroot/rw-nix
+ mount --rbind $targetRoot/$diskForUnionfs/root/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
+ fi
+ fi
+ '';
+
+ boot.initrd.extraUtilsCommands =
+ ''
+ # We need swapon in the initrd.
+ copy_bin_and_libs ${pkgs.utillinux}/sbin/swapon
+ '';
+
+ # 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";
+
+ # Force getting the hostname from EC2.
+ networking.hostName = mkDefault "";
+
+ # Always include cryptsetup so that Charon can use it.
+ 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/nixpkgs/nixos/modules/virtualisation/amazon-init.nix b/nixpkgs/nixos/modules/virtualisation/amazon-init.nix
new file mode 100644
index 00000000000..8032b2c6d7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/amazon-init.nix
@@ -0,0 +1,61 @@
+{ config, pkgs, ... }:
+
+let
+ script = ''
+ #!${pkgs.runtimeShell} -eu
+
+ echo "attempting to fetch configuration from EC2 user data..."
+
+ export HOME=/root
+ export PATH=${pkgs.lib.makeBinPath [ config.nix.package pkgs.systemd pkgs.gnugrep pkgs.gnused config.system.build.nixos-rebuild]}:$PATH
+ export NIX_PATH=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels
+
+ userData=/etc/ec2-metadata/user-data
+
+ if [ -s "$userData" ]; then
+ # If the user-data looks like it could be a nix expression,
+ # copy it over. Also, look for a magic three-hash comment and set
+ # that as the channel.
+ if sed '/^\(#\|SSH_HOST_.*\)/d' < "$userData" | grep -q '\S'; then
+ channels="$(grep '^###' "$userData" | sed 's|###\s*||')"
+ printf "%s" "$channels" | while read channel; do
+ echo "writing channel: $channel"
+ done
+
+ if [[ -n "$channels" ]]; then
+ printf "%s" "$channels" > /root/.nix-channels
+ nix-channel --update
+ fi
+
+ echo "setting configuration from EC2 user data"
+ cp "$userData" /etc/nixos/configuration.nix
+ else
+ echo "user data does not appear to be a Nix expression; ignoring"
+ exit
+ fi
+ else
+ echo "no user data is available"
+ exit
+ fi
+
+ nixos-rebuild switch
+ '';
+in {
+ systemd.services.amazon-init = {
+ inherit script;
+ description = "Reconfigure the system from EC2 userdata on startup";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "multi-user.target" ];
+ requires = [ "network-online.target" ];
+
+ restartIfChanged = false;
+ unitConfig.X-StopOnRemoval = false;
+
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/virtualisation/amazon-options.nix b/nixpkgs/nixos/modules/virtualisation/amazon-options.nix
new file mode 100644
index 00000000000..2e807131e93
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/amazon-options.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+{
+ options = {
+ ec2 = {
+ hvm = lib.mkOption {
+ default = lib.versionAtLeast config.system.stateVersion "17.03";
+ internal = true;
+ description = ''
+ Whether the EC2 instance is a HVM instance.
+ '';
+ };
+ efi = lib.mkOption {
+ default = pkgs.stdenv.hostPlatform.isAarch64;
+ internal = true;
+ description = ''
+ Whether the EC2 instance is using EFI.
+ '';
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/anbox.nix b/nixpkgs/nixos/modules/virtualisation/anbox.nix
new file mode 100644
index 00000000000..da5df358073
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/anbox.nix
@@ -0,0 +1,139 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.virtualisation.anbox;
+ kernelPackages = config.boot.kernelPackages;
+ addrOpts = v: addr: pref: name: {
+ address = mkOption {
+ default = addr;
+ type = types.str;
+ description = ''
+ IPv${toString v} ${name} address.
+ '';
+ };
+
+ prefixLength = mkOption {
+ default = pref;
+ type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
+ description = ''
+ Subnet mask of the ${name} address, specified as the number of
+ bits in the prefix (<literal>${if v == 4 then "24" else "64"}</literal>).
+ '';
+ };
+ };
+
+in
+
+{
+
+ options.virtualisation.anbox = {
+
+ enable = mkEnableOption "Anbox";
+
+ image = mkOption {
+ default = pkgs.anbox.image;
+ example = literalExample "pkgs.anbox.image";
+ type = types.package;
+ description = ''
+ Base android image for Anbox.
+ '';
+ };
+
+ extraInit = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra shell commands to be run inside the container image during init.
+ '';
+ };
+
+ ipv4 = {
+ container = addrOpts 4 "192.168.250.2" 24 "Container";
+ gateway = addrOpts 4 "192.168.250.1" 24 "Host";
+
+ dns = mkOption {
+ default = "1.1.1.1";
+ type = types.str;
+ description = ''
+ Container DNS server.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = singleton {
+ assertion = versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.18";
+ message = "Anbox needs user namespace support to work properly";
+ };
+
+ environment.systemPackages = with pkgs; [ anbox ];
+
+ boot.kernelModules = [ "ashmem_linux" "binder_linux" ];
+ boot.extraModulePackages = [ kernelPackages.anbox ];
+
+ services.udev.extraRules = ''
+ KERNEL=="ashmem", NAME="%k", MODE="0666"
+ KERNEL=="binder*", NAME="%k", MODE="0666"
+ '';
+
+ virtualisation.lxc.enable = true;
+ networking.bridges.anbox0.interfaces = [];
+ networking.interfaces.anbox0.ipv4.addresses = [ cfg.ipv4.gateway ];
+
+ networking.nat = {
+ enable = true;
+ internalInterfaces = [ "anbox0" ];
+ };
+
+ systemd.services.anbox-container-manager = let
+ anboxloc = "/var/lib/anbox";
+ in {
+ description = "Anbox Container Management Daemon";
+
+ environment.XDG_RUNTIME_DIR="${anboxloc}";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "systemd-udev-settle.service" ];
+ preStart = let
+ initsh = pkgs.writeText "nixos-init" (''
+ #!/system/bin/sh
+ setprop nixos.version ${config.system.nixos.version}
+
+ # we don't have radio
+ setprop ro.radio.noril yes
+ stop ril-daemon
+
+ # speed up boot
+ setprop debug.sf.nobootanimation 1
+ '' + cfg.extraInit);
+ initshloc = "${anboxloc}/rootfs-overlay/system/etc/init.goldfish.sh";
+ in ''
+ mkdir -p ${anboxloc}
+ mkdir -p $(dirname ${initshloc})
+ [ -f ${initshloc} ] && rm ${initshloc}
+ cp ${initsh} ${initshloc}
+ chown 100000:100000 ${initshloc}
+ chmod +x ${initshloc}
+ '';
+
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.anbox}/bin/anbox container-manager \
+ --data-path=${anboxloc} \
+ --android-image=${cfg.image} \
+ --container-network-address=${cfg.ipv4.container.address} \
+ --container-network-gateway=${cfg.ipv4.gateway.address} \
+ --container-network-dns-servers=${cfg.ipv4.dns} \
+ --use-rootfs-overlay \
+ --privileged
+ '';
+ };
+ };
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-agent-entropy.patch b/nixpkgs/nixos/modules/virtualisation/azure-agent-entropy.patch
new file mode 100644
index 00000000000..2a7ad08a4af
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/azure-agent-entropy.patch
@@ -0,0 +1,17 @@
+--- a/waagent 2016-03-12 09:58:15.728088851 +0200
++++ a/waagent 2016-03-12 09:58:43.572680025 +0200
+@@ -6173,10 +6173,10 @@
+ Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in mac]))
+
+ # Consume Entropy in ACPI table provided by Hyper-V
+- try:
+- SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
+- except:
+- pass
++ #try:
++ # SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
++ #except:
++ # pass
+
+ Log("Probing for Azure environment.")
+ self.Endpoint = self.DoDhcpWork()
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-agent.nix b/nixpkgs/nixos/modules/virtualisation/azure-agent.nix
new file mode 100644
index 00000000000..036b1036f92
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/azure-agent.nix
@@ -0,0 +1,198 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.virtualisation.azure.agent;
+
+ waagent = with pkgs; stdenv.mkDerivation rec {
+ name = "waagent-2.0";
+ src = pkgs.fetchFromGitHub {
+ owner = "Azure";
+ repo = "WALinuxAgent";
+ rev = "1b3a8407a95344d9d12a2a377f64140975f1e8e4";
+ sha256 = "10byzvmpgrmr4d5mdn2kq04aapqb3sgr1admk13wjmy5cd6bwd2x";
+ };
+
+ patches = [ ./azure-agent-entropy.patch ];
+
+ buildInputs = [ makeWrapper python pythonPackages.wrapPython ];
+ runtimeDeps = [ findutils gnugrep gawk coreutils openssl openssh
+ nettools # for hostname
+ procps # for pidof
+ shadow # for useradd, usermod
+ utillinux # for (u)mount, fdisk, sfdisk, mkswap
+ parted
+ ];
+ pythonPath = [ pythonPackages.pyasn1 ];
+
+ configurePhase = false;
+ buildPhase = false;
+
+ installPhase = ''
+ substituteInPlace config/99-azure-product-uuid.rules \
+ --replace /bin/chmod "${coreutils}/bin/chmod"
+ mkdir -p $out/lib/udev/rules.d
+ cp config/*.rules $out/lib/udev/rules.d
+
+ mkdir -p $out/bin
+ cp waagent $out/bin/
+ chmod +x $out/bin/waagent
+
+ wrapProgram "$out/bin/waagent" \
+ --prefix PYTHONPATH : $PYTHONPATH \
+ --prefix PATH : "${makeBinPath runtimeDeps}"
+ '';
+ };
+
+ provisionedHook = pkgs.writeScript "provisioned-hook" ''
+ #!${pkgs.runtimeShell}
+ ${config.systemd.package}/bin/systemctl start provisioned.target
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options.virtualisation.azure.agent = {
+ enable = mkOption {
+ default = false;
+ description = "Whether to enable the Windows Azure Linux Agent.";
+ };
+ verboseLogging = mkOption {
+ default = false;
+ description = "Whether to enable verbose logging.";
+ };
+ mountResourceDisk = mkOption {
+ default = true;
+ description = "Whether the agent should format (ext4) and mount the resource disk to /mnt/resource.";
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ assertions = [ {
+ assertion = pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64;
+ message = "Azure not currently supported on ${pkgs.stdenv.hostPlatform.system}";
+ } {
+ assertion = config.networking.networkmanager.enable == false;
+ message = "Windows Azure Linux Agent is not compatible with NetworkManager";
+ } ];
+
+ boot.initrd.kernelModules = [ "ata_piix" ];
+ networking.firewall.allowedUDPPorts = [ 68 ];
+
+
+ environment.etc."waagent.conf".text = ''
+ #
+ # Windows Azure Linux Agent Configuration
+ #
+
+ Role.StateConsumer=${provisionedHook}
+
+ # Enable instance creation
+ Provisioning.Enabled=y
+
+ # Password authentication for root account will be unavailable.
+ Provisioning.DeleteRootPassword=n
+
+ # Generate fresh host key pair.
+ Provisioning.RegenerateSshHostKeyPair=n
+
+ # Supported values are "rsa", "dsa" and "ecdsa".
+ Provisioning.SshHostKeyPairType=ed25519
+
+ # Monitor host name changes and publish changes via DHCP requests.
+ Provisioning.MonitorHostName=y
+
+ # Decode CustomData from Base64.
+ Provisioning.DecodeCustomData=n
+
+ # Execute CustomData after provisioning.
+ Provisioning.ExecuteCustomData=n
+
+ # Format if unformatted. If 'n', resource disk will not be mounted.
+ ResourceDisk.Format=${if cfg.mountResourceDisk then "y" else "n"}
+
+ # File system on the resource disk
+ # Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
+ ResourceDisk.Filesystem=ext4
+
+ # Mount point for the resource disk
+ ResourceDisk.MountPoint=/mnt/resource
+
+ # Respond to load balancer probes if requested by Windows Azure.
+ LBProbeResponder=y
+
+ # Enable logging to serial console (y|n)
+ # When stdout is not enough...
+ # 'y' if not set
+ Logs.Console=y
+
+ # Enable verbose logging (y|n)
+ Logs.Verbose=${if cfg.verboseLogging then "y" else "n"}
+
+ # Root device timeout in seconds.
+ OS.RootDeviceScsiTimeout=300
+ '';
+
+ services.udev.packages = [ waagent ];
+
+ networking.dhcpcd.persistent = true;
+
+ services.logrotate = {
+ enable = true;
+ config = ''
+ /var/log/waagent.log {
+ compress
+ monthly
+ rotate 6
+ notifempty
+ missingok
+ }
+ '';
+ };
+
+ systemd.targets.provisioned = {
+ description = "Services Requiring Azure VM provisioning to have finished";
+ };
+
+ systemd.services.consume-hypervisor-entropy =
+ { description = "Consume entropy in ACPI table provided by Hyper-V";
+
+ wantedBy = [ "sshd.service" "waagent.service" ];
+ before = [ "sshd.service" "waagent.service" ];
+
+ path = [ pkgs.coreutils ];
+ script =
+ ''
+ echo "Fetching entropy..."
+ cat /sys/firmware/acpi/tables/OEM0 > /dev/random
+ '';
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ serviceConfig.StandardError = "journal+console";
+ serviceConfig.StandardOutput = "journal+console";
+ };
+
+ systemd.services.waagent = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target" "sshd.service" ];
+ wants = [ "network-online.target" ];
+
+ path = [ pkgs.e2fsprogs pkgs.bash ];
+ description = "Windows Azure Agent Service";
+ unitConfig.ConditionPathExists = "/etc/waagent.conf";
+ serviceConfig = {
+ ExecStart = "${waagent}/bin/waagent -daemon";
+ Type = "simple";
+ };
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-bootstrap-blobs.nix b/nixpkgs/nixos/modules/virtualisation/azure-bootstrap-blobs.nix
new file mode 100644
index 00000000000..281be9a1231
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/azure-bootstrap-blobs.nix
@@ -0,0 +1,3 @@
+{
+ "16.03" = "https://nixos.blob.core.windows.net/images/nixos-image-16.03.847.8688c17-x86_64-linux.vhd";
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-common.nix b/nixpkgs/nixos/modules/virtualisation/azure-common.nix
new file mode 100644
index 00000000000..03239991b95
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/azure-common.nix
@@ -0,0 +1,64 @@
+{ lib, pkgs, ... }:
+
+with lib;
+{
+ imports = [ ../profiles/headless.nix ];
+
+ require = [ ./azure-agent.nix ];
+ virtualisation.azure.agent.enable = true;
+
+ boot.kernelParams = [ "console=ttyS0" "earlyprintk=ttyS0" "rootdelay=300" "panic=1" "boot.panic_on_fail" ];
+ boot.initrd.kernelModules = [ "hv_vmbus" "hv_netvsc" "hv_utils" "hv_storvsc" ];
+
+ # Generate a GRUB menu.
+ boot.loader.grub.device = "/dev/sda";
+ boot.loader.grub.version = 2;
+ 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;
+
+ fileSystems."/".device = "/dev/disk/by-label/nixos";
+
+ # Allow root logins only using the SSH key that the user specified
+ # at instance creation time, ping client connections to avoid timeouts
+ services.openssh.enable = true;
+ services.openssh.permitRootLogin = "prohibit-password";
+ services.openssh.extraConfig = ''
+ ClientAliveInterval 180
+ '';
+
+ # Force getting the hostname from Azure
+ networking.hostName = mkDefault "";
+
+ # Always include cryptsetup so that NixOps can use it.
+ # sg_scan is needed to finalize disk removal on older kernels
+ environment.systemPackages = [ pkgs.cryptsetup pkgs.sg3_utils ];
+
+ networking.usePredictableInterfaceNames = false;
+
+ services.udev.extraRules = ''
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:0", ATTR{removable}=="0", SYMLINK+="disk/by-lun/0",
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:1", ATTR{removable}=="0", SYMLINK+="disk/by-lun/1",
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:2", ATTR{removable}=="0", SYMLINK+="disk/by-lun/2"
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:3", ATTR{removable}=="0", SYMLINK+="disk/by-lun/3"
+
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:4", ATTR{removable}=="0", SYMLINK+="disk/by-lun/4"
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:5", ATTR{removable}=="0", SYMLINK+="disk/by-lun/5"
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:6", ATTR{removable}=="0", SYMLINK+="disk/by-lun/6"
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:7", ATTR{removable}=="0", SYMLINK+="disk/by-lun/7"
+
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:8", ATTR{removable}=="0", SYMLINK+="disk/by-lun/8"
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:9", ATTR{removable}=="0", SYMLINK+="disk/by-lun/9"
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:10", ATTR{removable}=="0", SYMLINK+="disk/by-lun/10"
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:11", ATTR{removable}=="0", SYMLINK+="disk/by-lun/11"
+
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:12", ATTR{removable}=="0", SYMLINK+="disk/by-lun/12"
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:13", ATTR{removable}=="0", SYMLINK+="disk/by-lun/13"
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:14", ATTR{removable}=="0", SYMLINK+="disk/by-lun/14"
+ ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:15", ATTR{removable}=="0", SYMLINK+="disk/by-lun/15"
+
+ '';
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-config-user.nix b/nixpkgs/nixos/modules/virtualisation/azure-config-user.nix
new file mode 100644
index 00000000000..267ba50ae02
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/azure-config-user.nix
@@ -0,0 +1,12 @@
+{ modulesPath, ... }:
+
+{
+ # To build the configuration or use nix-env, you need to run
+ # either nixos-rebuild --upgrade or nix-channel --update
+ # to fetch the nixos channel.
+
+ # This configures everything but bootstrap services,
+ # which only need to be run once and have already finished
+ # if you are able to see this comment.
+ imports = [ "${modulesPath}/virtualisation/azure-common.nix" ];
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-config.nix b/nixpkgs/nixos/modules/virtualisation/azure-config.nix
new file mode 100644
index 00000000000..780bd1b78dc
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/azure-config.nix
@@ -0,0 +1,5 @@
+{ modulesPath, ... }:
+
+{
+ imports = [ "${modulesPath}/virtualisation/azure-image.nix" ];
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-image.nix b/nixpkgs/nixos/modules/virtualisation/azure-image.nix
new file mode 100644
index 00000000000..e91dd72ff5d
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/azure-image.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ diskSize = 2048;
+in
+{
+ system.build.azureImage = import ../../lib/make-disk-image.nix {
+ name = "azure-image";
+ postVM = ''
+ ${pkgs.vmTools.qemu}/bin/qemu-img convert -f raw -o subformat=fixed,force_size -O vpc $diskImage $out/disk.vhd
+ '';
+ configFile = ./azure-config-user.nix;
+ format = "raw";
+ inherit diskSize;
+ inherit config lib pkgs;
+ };
+
+ imports = [ ./azure-common.nix ];
+
+ # Azure metadata is available as a CD-ROM drive.
+ fileSystems."/metadata".device = "/dev/sr0";
+
+ systemd.services.fetch-ssh-keys =
+ { description = "Fetch host keys and authorized_keys for root user";
+
+ wantedBy = [ "sshd.service" "waagent.service" ];
+ before = [ "sshd.service" "waagent.service" ];
+
+ path = [ pkgs.coreutils ];
+ script =
+ ''
+ eval "$(cat /metadata/CustomData.bin)"
+ if ! [ -z "$ssh_host_ecdsa_key" ]; then
+ echo "downloaded ssh_host_ecdsa_key"
+ echo "$ssh_host_ecdsa_key" > /etc/ssh/ssh_host_ed25519_key
+ chmod 600 /etc/ssh/ssh_host_ed25519_key
+ fi
+
+ if ! [ -z "$ssh_host_ecdsa_key_pub" ]; then
+ echo "downloaded ssh_host_ecdsa_key_pub"
+ echo "$ssh_host_ecdsa_key_pub" > /etc/ssh/ssh_host_ed25519_key.pub
+ chmod 644 /etc/ssh/ssh_host_ed25519_key.pub
+ fi
+
+ if ! [ -z "$ssh_root_auth_key" ]; then
+ echo "downloaded ssh_root_auth_key"
+ mkdir -m 0700 -p /root/.ssh
+ echo "$ssh_root_auth_key" > /root/.ssh/authorized_keys
+ chmod 600 /root/.ssh/authorized_keys
+ fi
+ '';
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ serviceConfig.StandardError = "journal+console";
+ serviceConfig.StandardOutput = "journal+console";
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-images.nix b/nixpkgs/nixos/modules/virtualisation/azure-images.nix
new file mode 100644
index 00000000000..22c82fc14f6
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/azure-images.nix
@@ -0,0 +1,5 @@
+let self = {
+ "16.09" = "https://nixos.blob.core.windows.net/images/nixos-image-16.09.1694.019dcc3-x86_64-linux.vhd";
+
+ latest = self."16.09";
+}; in self
diff --git a/nixpkgs/nixos/modules/virtualisation/brightbox-config.nix b/nixpkgs/nixos/modules/virtualisation/brightbox-config.nix
new file mode 100644
index 00000000000..0a018e4cd69
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/brightbox-config.nix
@@ -0,0 +1,5 @@
+{ modulesPath, ... }:
+
+{
+ imports = [ "${modulesPath}/virtualisation/brightbox-image.nix" ];
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/brightbox-image.nix b/nixpkgs/nixos/modules/virtualisation/brightbox-image.nix
new file mode 100644
index 00000000000..d0efbcc808a
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/brightbox-image.nix
@@ -0,0 +1,166 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ diskSize = "20G";
+in
+{
+ imports = [ ../profiles/headless.nix ../profiles/qemu-guest.nix ];
+
+ system.build.brightboxImage =
+ pkgs.vmTools.runInLinuxVM (
+ pkgs.runCommand "brightbox-image"
+ { preVM =
+ ''
+ mkdir $out
+ diskImage=$out/$diskImageBase
+ truncate $diskImage --size ${diskSize}
+ mv closure xchg/
+ '';
+
+ postVM =
+ ''
+ PATH=$PATH:${lib.makeBinPath [ pkgs.gnutar pkgs.gzip ]}
+ pushd $out
+ ${pkgs.qemu_kvm}/bin/qemu-img convert -c -O qcow2 $diskImageBase nixos.qcow2
+ rm $diskImageBase
+ popd
+ '';
+ diskImageBase = "nixos-image-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.raw";
+ buildInputs = [ pkgs.utillinux pkgs.perl ];
+ exportReferencesGraph =
+ [ "closure" config.system.build.toplevel ];
+ }
+ ''
+ # Create partition table
+ ${pkgs.parted}/sbin/parted --script /dev/vda mklabel msdos
+ ${pkgs.parted}/sbin/parted --script /dev/vda mkpart primary ext4 1 ${diskSize}
+ ${pkgs.parted}/sbin/parted --script /dev/vda print
+ . /sys/class/block/vda1/uevent
+ mknod /dev/vda1 b $MAJOR $MINOR
+
+ # Create an empty filesystem and mount it.
+ ${pkgs.e2fsprogs}/sbin/mkfs.ext4 -L nixos /dev/vda1
+ ${pkgs.e2fsprogs}/sbin/tune2fs -c 0 -i 0 /dev/vda1
+
+ mkdir /mnt
+ mount /dev/vda1 /mnt
+
+ # The initrd expects these directories to exist.
+ mkdir /mnt/dev /mnt/proc /mnt/sys
+
+ mount --bind /proc /mnt/proc
+ mount --bind /dev /mnt/dev
+ mount --bind /sys /mnt/sys
+
+ # Copy all paths in the closure to the filesystem.
+ storePaths=$(perl ${pkgs.pathsFromGraph} /tmp/xchg/closure)
+
+ mkdir -p /mnt/nix/store
+ echo "copying everything (will take a while)..."
+ cp -prd $storePaths /mnt/nix/store/
+
+ # Register the paths in the Nix database.
+ printRegistration=1 perl ${pkgs.pathsFromGraph} /tmp/xchg/closure | \
+ chroot /mnt ${config.nix.package.out}/bin/nix-store --load-db --option build-users-group ""
+
+ # Create the system profile to allow nixos-rebuild to work.
+ chroot /mnt ${config.nix.package.out}/bin/nix-env \
+ -p /nix/var/nix/profiles/system --set ${config.system.build.toplevel} \
+ --option build-users-group ""
+
+ # `nixos-rebuild' requires an /etc/NIXOS.
+ mkdir -p /mnt/etc
+ touch /mnt/etc/NIXOS
+
+ # `switch-to-configuration' requires a /bin/sh
+ mkdir -p /mnt/bin
+ ln -s ${config.system.build.binsh}/bin/sh /mnt/bin/sh
+
+ # Install a configuration.nix.
+ mkdir -p /mnt/etc/nixos /mnt/boot/grub
+ cp ${./brightbox-config.nix} /mnt/etc/nixos/configuration.nix
+
+ # Generate the GRUB menu.
+ ln -s vda /dev/sda
+ chroot /mnt ${config.system.build.toplevel}/bin/switch-to-configuration boot
+
+ umount /mnt/proc /mnt/dev /mnt/sys
+ umount /mnt
+ ''
+ );
+
+ fileSystems."/".label = "nixos";
+
+ # Generate a GRUB menu. Amazon's pv-grub uses this to boot our kernel/initrd.
+ boot.loader.grub.device = "/dev/vda";
+ 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";
+
+ # Force getting the hostname from Google Compute.
+ networking.hostName = mkDefault "";
+
+ # Always include cryptsetup so that NixOps can use it.
+ environment.systemPackages = [ pkgs.cryptsetup ];
+
+ systemd.services.fetch-ec2-data =
+ { description = "Fetch EC2 Data";
+
+ wantedBy = [ "multi-user.target" "sshd.service" ];
+ before = [ "sshd.service" ];
+ wants = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+
+ path = [ pkgs.wget pkgs.iproute ];
+
+ script =
+ ''
+ wget="wget -q --retry-connrefused -O -"
+
+ ${optionalString (config.networking.hostName == "") ''
+ echo "setting host name..."
+ ${pkgs.nettools}/bin/hostname $($wget http://169.254.169.254/latest/meta-data/hostname)
+ ''}
+
+ # Don't download the SSH key if it has already been injected
+ # into the image (a Nova feature).
+ if ! [ -e /root/.ssh/authorized_keys ]; then
+ echo "obtaining SSH key..."
+ mkdir -m 0700 -p /root/.ssh
+ $wget http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key > /root/key.pub
+ if [ $? -eq 0 -a -e /root/key.pub ]; then
+ if ! grep -q -f /root/key.pub /root/.ssh/authorized_keys; then
+ cat /root/key.pub >> /root/.ssh/authorized_keys
+ echo "new key added to authorized_keys"
+ fi
+ chmod 600 /root/.ssh/authorized_keys
+ rm -f /root/key.pub
+ fi
+ fi
+
+ # Extract the intended SSH host key for this machine from
+ # the supplied user data, if available. Otherwise sshd will
+ # generate one normally.
+ $wget http://169.254.169.254/2011-01-01/user-data > /root/user-data || true
+ key="$(sed 's/|/\n/g; s/SSH_HOST_DSA_KEY://; t; d' /root/user-data)"
+ key_pub="$(sed 's/SSH_HOST_DSA_KEY_PUB://; t; d' /root/user-data)"
+ if [ -n "$key" -a -n "$key_pub" -a ! -e /etc/ssh/ssh_host_dsa_key ]; then
+ mkdir -m 0755 -p /etc/ssh
+ (umask 077; echo "$key" > /etc/ssh/ssh_host_dsa_key)
+ echo "$key_pub" > /etc/ssh/ssh_host_dsa_key.pub
+ fi
+ '';
+
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/cloudstack-config.nix b/nixpkgs/nixos/modules/virtualisation/cloudstack-config.nix
new file mode 100644
index 00000000000..78afebdc5dd
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/cloudstack-config.nix
@@ -0,0 +1,40 @@
+{ lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [
+ ../profiles/qemu-guest.nix
+ ];
+
+ config = {
+ fileSystems."/" = {
+ device = "/dev/disk/by-label/nixos";
+ autoResize = true;
+ };
+
+ boot.growPartition = true;
+ boot.kernelParams = [ "console=tty0" ];
+ boot.loader.grub.device = "/dev/vda";
+ boot.loader.timeout = 0;
+
+ # Allow root logins
+ services.openssh = {
+ enable = true;
+ permitRootLogin = "prohibit-password";
+ };
+
+ # Cloud-init configuration.
+ services.cloud-init.enable = true;
+ # Wget is needed for setting password. This is of little use as
+ # root password login is disabled above.
+ environment.systemPackages = [ pkgs.wget ];
+ # Only enable CloudStack datasource for faster boot speed.
+ environment.etc."cloud/cloud.cfg.d/99_cloudstack.cfg".text = ''
+ datasource:
+ CloudStack: {}
+ None: {}
+ datasource_list: ["CloudStack"]
+ '';
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/container-config.nix b/nixpkgs/nixos/modules/virtualisation/container-config.nix
new file mode 100644
index 00000000000..604fb8a7593
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/container-config.nix
@@ -0,0 +1,29 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ config = mkIf config.boot.isContainer {
+
+ # Disable some features that are not useful in a container.
+ services.udisks2.enable = mkDefault false;
+ powerManagement.enable = mkDefault false;
+
+ networking.useHostResolvConf = mkDefault true;
+
+ # Containers should be light-weight, so start sshd on demand.
+ services.openssh.startWhenNeeded = mkDefault true;
+
+ # Shut up warnings about not having a boot loader.
+ system.build.installBootLoader = "${pkgs.coreutils}/bin/true";
+
+ # Not supported in systemd-nspawn containers.
+ security.audit.enable = false;
+
+ # Use the host's nix-daemon.
+ environment.variables.NIX_REMOTE = "daemon";
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/containers.nix b/nixpkgs/nixos/modules/virtualisation/containers.nix
new file mode 100644
index 00000000000..9c9f8fc0c21
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/containers.nix
@@ -0,0 +1,828 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ # The container's init script, a small wrapper around the regular
+ # NixOS stage-2 init script.
+ containerInit = (cfg:
+ let
+ renderExtraVeth = (name: cfg:
+ ''
+ echo "Bringing ${name} up"
+ ip link set dev ${name} up
+ ${optionalString (cfg.localAddress != null) ''
+ echo "Setting ip for ${name}"
+ ip addr add ${cfg.localAddress} dev ${name}
+ ''}
+ ${optionalString (cfg.localAddress6 != null) ''
+ echo "Setting ip6 for ${name}"
+ ip -6 addr add ${cfg.localAddress6} dev ${name}
+ ''}
+ ${optionalString (cfg.hostAddress != null) ''
+ echo "Setting route to host for ${name}"
+ ip route add ${cfg.hostAddress} dev ${name}
+ ''}
+ ${optionalString (cfg.hostAddress6 != null) ''
+ echo "Setting route6 to host for ${name}"
+ ip -6 route add ${cfg.hostAddress6} dev ${name}
+ ''}
+ ''
+ );
+ in
+ pkgs.writeScript "container-init"
+ ''
+ #! ${pkgs.runtimeShell} -e
+
+ # Initialise the container side of the veth pair.
+ if [ -n "$HOST_ADDRESS" ] || [ -n "$HOST_ADDRESS6" ] ||
+ [ -n "$LOCAL_ADDRESS" ] || [ -n "$LOCAL_ADDRESS6" ] ||
+ [ -n "$HOST_BRIDGE" ]; then
+ ip link set host0 name eth0
+ ip link set dev eth0 up
+
+ if [ -n "$LOCAL_ADDRESS" ]; then
+ ip addr add $LOCAL_ADDRESS dev eth0
+ fi
+ if [ -n "$LOCAL_ADDRESS6" ]; then
+ ip -6 addr add $LOCAL_ADDRESS6 dev eth0
+ fi
+ if [ -n "$HOST_ADDRESS" ]; then
+ ip route add $HOST_ADDRESS dev eth0
+ ip route add default via $HOST_ADDRESS
+ fi
+ if [ -n "$HOST_ADDRESS6" ]; then
+ ip -6 route add $HOST_ADDRESS6 dev eth0
+ ip -6 route add default via $HOST_ADDRESS6
+ fi
+
+ ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)}
+ fi
+
+ # Start the regular stage 1 script.
+ exec "$1"
+ ''
+ );
+
+ nspawnExtraVethArgs = (name: cfg: "--network-veth-extra=${name}");
+
+ startScript = cfg:
+ ''
+ mkdir -p -m 0755 "$root/etc" "$root/var/lib"
+ mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/containers
+ if ! [ -e "$root/etc/os-release" ]; then
+ touch "$root/etc/os-release"
+ fi
+
+ if ! [ -e "$root/etc/machine-id" ]; then
+ touch "$root/etc/machine-id"
+ fi
+
+ mkdir -p -m 0755 \
+ "/nix/var/nix/profiles/per-container/$INSTANCE" \
+ "/nix/var/nix/gcroots/per-container/$INSTANCE"
+
+ 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" ] ||
+ [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then
+ extraFlags+=" --network-veth"
+ 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)}"
+
+ for iface in $INTERFACES; do
+ extraFlags+=" --network-interface=$iface"
+ done
+
+ for iface in $MACVLANS; do
+ extraFlags+=" --network-macvlan=$iface"
+ done
+
+ # If the host is 64-bit and the container is 32-bit, add a
+ # --personality flag.
+ ${optionalString (config.nixpkgs.localSystem.system == "x86_64-linux") ''
+ if [ "$(< ''${SYSTEM_PATH:-/nix/var/nix/profiles/per-container/$INSTANCE/system}/system)" = i686-linux ]; then
+ extraFlags+=" --personality=x86"
+ fi
+ ''}
+
+ # Run systemd-nspawn without startup notification (we'll
+ # wait for the container systemd to signal readiness).
+ exec ${config.systemd.package}/bin/systemd-nspawn \
+ --keep-unit \
+ -M "$INSTANCE" -D "$root" $extraFlags \
+ $EXTRA_NSPAWN_FLAGS \
+ --notify-ready=yes \
+ --bind-ro=/nix/store \
+ --bind-ro=/nix/var/nix/db \
+ --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" \
+ ${optionalString (!cfg.ephemeral) "--link-journal=try-guest"} \
+ --setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \
+ --setenv HOST_BRIDGE="$HOST_BRIDGE" \
+ --setenv HOST_ADDRESS="$HOST_ADDRESS" \
+ --setenv LOCAL_ADDRESS="$LOCAL_ADDRESS" \
+ --setenv HOST_ADDRESS6="$HOST_ADDRESS6" \
+ --setenv LOCAL_ADDRESS6="$LOCAL_ADDRESS6" \
+ --setenv HOST_PORT="$HOST_PORT" \
+ --setenv PATH="$PATH" \
+ ${optionalString cfg.ephemeral "--ephemeral"} \
+ ${if cfg.additionalCapabilities != null && cfg.additionalCapabilities != [] then
+ ''--capability="${concatStringsSep " " cfg.additionalCapabilities}"'' else ""
+ } \
+ ${if cfg.tmpfs != null && cfg.tmpfs != [] then
+ ''--tmpfs=${concatStringsSep " --tmpfs=" cfg.tmpfs}'' else ""
+ } \
+ ${containerInit cfg} "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/init"
+ '';
+
+ preStartScript = cfg:
+ ''
+ # Clean up existing machined registration and interfaces.
+ machinectl terminate "$INSTANCE" 2> /dev/null || true
+
+ if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] ||
+ [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then
+ ip link del dev "ve-$INSTANCE" 2> /dev/null || true
+ ip link del dev "vb-$INSTANCE" 2> /dev/null || true
+ fi
+
+ ${concatStringsSep "\n" (
+ mapAttrsToList (name: cfg:
+ ''ip link del dev ${name} 2> /dev/null || true ''
+ ) cfg.extraVeths
+ )}
+ '';
+
+ postStartScript = (cfg:
+ let
+ ipcall = cfg: ipcmd: variable: attribute:
+ if cfg.${attribute} == null then
+ ''
+ if [ -n "${variable}" ]; then
+ ${ipcmd} add ${variable} dev $ifaceHost
+ fi
+ ''
+ else
+ ''${ipcmd} add ${cfg.${attribute}} dev $ifaceHost'';
+ renderExtraVeth = name: cfg:
+ if cfg.hostBridge != null then
+ ''
+ # Add ${name} to bridge ${cfg.hostBridge}
+ ip link set dev ${name} master ${cfg.hostBridge} up
+ ''
+ 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}
+ ''}
+ ${optionalString (cfg.hostAddress6 != null) ''
+ ip -6 addr add ${cfg.hostAddress6} dev ${name}
+ ''}
+ ${optionalString (cfg.localAddress != null) ''
+ ip route add ${cfg.localAddress} dev ${name}
+ ''}
+ ${optionalString (cfg.localAddress6 != null) ''
+ ip -6 route add ${cfg.localAddress6} dev ${name}
+ ''}
+ '';
+ in
+ ''
+ if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] ||
+ [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then
+ if [ -z "$HOST_BRIDGE" ]; then
+ ifaceHost=ve-$INSTANCE
+ ip link set dev $ifaceHost up
+
+ ${ipcall cfg "ip addr" "$HOST_ADDRESS" "hostAddress"}
+ ${ipcall cfg "ip -6 addr" "$HOST_ADDRESS6" "hostAddress6"}
+ ${ipcall cfg "ip route" "$LOCAL_ADDRESS" "localAddress"}
+ ${ipcall cfg "ip -6 route" "$LOCAL_ADDRESS6" "localAddress6"}
+ fi
+ ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)}
+ fi
+
+ # Get the leader PID so that we can signal it in
+ # preStop. We can't use machinectl there because D-Bus
+ # might be shutting down. FIXME: in systemd 219 we can
+ # just signal systemd-nspawn to do a clean shutdown.
+ machinectl show "$INSTANCE" | sed 's/Leader=\(.*\)/\1/;t;d' > "/run/containers/$INSTANCE.pid"
+ ''
+ );
+
+ serviceDirectives = cfg: {
+ ExecReload = pkgs.writeScript "reload-container"
+ ''
+ #! ${pkgs.runtimeShell} -e
+ ${pkgs.nixos-container}/bin/nixos-container run "$INSTANCE" -- \
+ bash --login -c "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/bin/switch-to-configuration test"
+ '';
+
+ SyslogIdentifier = "container %i";
+
+ EnvironmentFile = "-/etc/containers/%i.conf";
+
+ Type = "notify";
+
+ RuntimeDirectory = lib.optional cfg.ephemeral "containers/%i";
+
+ # Note that on reboot, systemd-nspawn returns 133, so this
+ # unit will be restarted. On poweroff, it returns 0, so the
+ # unit won't be restarted.
+ RestartForceExitStatus = "133";
+ SuccessExitStatus = "133";
+
+ # Some containers take long to start
+ # especially when you automatically start many at once
+ TimeoutStartSec = cfg.timeoutStartSec;
+
+ Restart = "on-failure";
+
+ Slice = "machine.slice";
+ 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
+ # (at least if we want remaining processes to be killed
+ # after the timeout). So send an ignored signal.
+ KillMode = "mixed";
+ KillSignal = "WINCH";
+
+ DevicePolicy = "closed";
+ DeviceAllow = map (d: "${d.node} ${d.modifier}") cfg.allowedDevices;
+ };
+
+
+ system = config.nixpkgs.localSystem.system;
+
+ bindMountOpts = { name, ... }: {
+
+ options = {
+ mountPoint = mkOption {
+ example = "/mnt/usb";
+ type = types.str;
+ description = "Mount point on the container file system.";
+ };
+ hostPath = mkOption {
+ default = null;
+ example = "/home/alice";
+ type = types.nullOr types.str;
+ description = "Location of the host path to be mounted.";
+ };
+ isReadOnly = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Determine whether the mounted path will be accessed in read-only mode.";
+ };
+ };
+
+ config = {
+ mountPoint = mkDefault name;
+ };
+
+ };
+
+ allowedDeviceOpts = { ... }: {
+ options = {
+ node = mkOption {
+ example = "/dev/net/tun";
+ type = types.str;
+ description = "Path to device node";
+ };
+ modifier = mkOption {
+ example = "rw";
+ type = types.str;
+ description = ''
+ Device node access modifier. Takes a combination
+ <literal>r</literal> (read), <literal>w</literal> (write), and
+ <literal>m</literal> (mknod). See the
+ <literal>systemd.resource-control(5)</literal> man page for more
+ information.'';
+ };
+ };
+ };
+
+
+ mkBindFlag = d:
+ let flagPrefix = if d.isReadOnly then " --bind-ro=" else " --bind=";
+ mountstr = if d.hostPath != null then "${d.hostPath}:${d.mountPoint}" else "${d.mountPoint}";
+ in flagPrefix + mountstr ;
+
+ mkBindFlags = bs: concatMapStrings mkBindFlag (lib.attrValues bs);
+
+ networkOptions = {
+ hostBridge = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "br0";
+ description = ''
+ Put the host-side of the veth-pair into the named bridge.
+ Only one of hostAddress* or hostBridge can be given.
+ '';
+ };
+
+ forwardPorts = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ protocol = mkOption {
+ type = types.str;
+ default = "tcp";
+ description = "The protocol specifier for port forwarding between host and container";
+ };
+ hostPort = mkOption {
+ type = types.int;
+ description = "Source port of the external interface on host";
+ };
+ containerPort = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Target port of container";
+ };
+ };
+ });
+ default = [];
+ example = [ { protocol = "tcp"; hostPort = 8080; containerPort = 80; } ];
+ description = ''
+ 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.
+ '';
+ };
+
+
+ hostAddress = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "10.231.136.1";
+ description = ''
+ The IPv4 address assigned to the host interface.
+ (Not used when hostBridge is set.)
+ '';
+ };
+
+ hostAddress6 = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fc00::1";
+ description = ''
+ The IPv6 address assigned to the host interface.
+ (Not used when hostBridge is set.)
+ '';
+ };
+
+ localAddress = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "10.231.136.2";
+ description = ''
+ The IPv4 address assigned to the interface in the container.
+ If a hostBridge is used, this should be given with netmask to access
+ the whole network. Otherwise the default netmask is /32 and routing is
+ set up from localAddress to hostAddress and back.
+ '';
+ };
+
+ localAddress6 = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fc00::2";
+ description = ''
+ The IPv6 address assigned to the interface in the container.
+ If a hostBridge is used, this should be given with netmask to access
+ the whole network. Otherwise the default netmask is /128 and routing is
+ set up from localAddress6 to hostAddress6 and back.
+ '';
+ };
+
+ };
+
+ dummyConfig =
+ {
+ extraVeths = {};
+ additionalCapabilities = [];
+ ephemeral = false;
+ timeoutStartSec = "15s";
+ allowedDevices = [];
+ hostAddress = null;
+ hostAddress6 = null;
+ localAddress = null;
+ localAddress6 = null;
+ tmpfs = null;
+ };
+
+in
+
+{
+ options = {
+
+ boot.isContainer = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether this NixOS machine is a lightweight container running
+ in another NixOS system.
+ '';
+ };
+
+ boot.enableContainers = mkOption {
+ type = types.bool;
+ default = !config.boot.isContainer;
+ description = ''
+ Whether to enable support for NixOS containers.
+ '';
+ };
+
+ containers = mkOption {
+ type = types.attrsOf (types.submodule (
+ { config, options, name, ... }:
+ {
+ options = {
+
+ config = mkOption {
+ description = ''
+ A specification of the desired configuration of this
+ container, as a NixOS module.
+ '';
+ type = lib.mkOptionType {
+ name = "Toplevel NixOS config";
+ merge = loc: defs: (import ../../lib/eval-config.nix {
+ inherit system;
+ modules =
+ let
+ extraConfig = {
+ _file = "module at ${__curPos.file}:${toString __curPos.line}";
+ config = {
+ boot.isContainer = true;
+ networking.hostName = mkDefault name;
+ networking.useDHCP = false;
+ assertions = [
+ {
+ assertion = config.privateNetwork -> stringLength name < 12;
+ message = ''
+ Container name `${name}` is too long: When `privateNetwork` is enabled, container names can
+ not be longer than 11 characters, because the container's interface name is derived from it.
+ This might be fixed in the future. See https://github.com/NixOS/nixpkgs/issues/38509
+ '';
+ }
+ ];
+ };
+ };
+ in [ extraConfig ] ++ (map (x: x.value) defs);
+ prefix = [ "containers" name ];
+ }).config;
+ };
+ };
+
+ path = mkOption {
+ type = types.path;
+ example = "/nix/var/nix/profiles/containers/webserver";
+ description = ''
+ As an alternative to specifying
+ <option>config</option>, you can specify the path to
+ the evaluated NixOS system configuration, typically a
+ symlink to a system profile.
+ '';
+ };
+
+ additionalCapabilities = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "CAP_NET_ADMIN" "CAP_MKNOD" ];
+ description = ''
+ Grant additional capabilities to the container. See the
+ capabilities(7) and systemd-nspawn(1) man pages for more
+ information.
+ '';
+ };
+
+ ephemeral = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Runs container in ephemeral mode with the empty root filesystem at boot.
+ This way container will be bootstrapped from scratch on each boot
+ and will be cleaned up on shutdown leaving no traces behind.
+ Useful for completely stateless, reproducible containers.
+
+ Note that this option might require to do some adjustments to the container configuration,
+ e.g. you might want to set
+ <varname>systemd.network.networks.$interface.dhcpConfig.ClientIdentifier</varname> to "mac"
+ if you use <varname>macvlans</varname> option.
+ This way dhcp client identifier will be stable between the container restarts.
+
+ Note that the container journal will not be linked to the host if this option is enabled.
+ '';
+ };
+
+ enableTun = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allows the container to create and setup tunnel interfaces
+ by granting the <literal>NET_ADMIN</literal> capability and
+ enabling access to <literal>/dev/net/tun</literal>.
+ '';
+ };
+
+ privateNetwork = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to give the container its own private virtual
+ Ethernet interface. The interface is called
+ <literal>eth0</literal>, and is hooked up to the interface
+ <literal>ve-<replaceable>container-name</replaceable></literal>
+ on the host. If this option is not set, then the
+ container shares the network interfaces of the host,
+ and can bind to any port on any interface.
+ '';
+ };
+
+ interfaces = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "eth1" "eth2" ];
+ description = ''
+ The list of interfaces to be moved into the container.
+ '';
+ };
+
+ macvlans = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "eth1" "eth2" ];
+ description = ''
+ The list of host interfaces from which macvlans will be
+ created. For each interface specified, a macvlan interface
+ will be created and moved to the container.
+ '';
+ };
+
+ extraVeths = mkOption {
+ type = with types; attrsOf (submodule { options = networkOptions; });
+ default = {};
+ description = ''
+ Extra veth-pairs to be created for the container
+ '';
+ };
+
+ autoStart = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether the container is automatically started at boot-time.
+ '';
+ };
+
+ timeoutStartSec = mkOption {
+ type = types.str;
+ default = "1min";
+ description = ''
+ Time for the container to start. In case of a timeout,
+ the container processes get killed.
+ See <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>
+ for more information about the format.
+ '';
+ };
+
+ bindMounts = mkOption {
+ type = with types; loaOf (submodule bindMountOpts);
+ default = {};
+ example = { "/home" = { hostPath = "/home/alice";
+ isReadOnly = false; };
+ };
+
+ description =
+ ''
+ An extra list of directories that is bound to the container.
+ '';
+ };
+
+ allowedDevices = mkOption {
+ type = with types; listOf (submodule allowedDeviceOpts);
+ default = [];
+ example = [ { node = "/dev/net/tun"; modifier = "rw"; } ];
+ description = ''
+ A list of device nodes to which the containers has access to.
+ '';
+ };
+
+ tmpfs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "/var" ];
+ description = ''
+ Mounts a set of tmpfs file systems into the container.
+ Multiple paths can be specified.
+ Valid items must conform to the --tmpfs argument
+ of systemd-nspawn. See systemd-nspawn(1) for details.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "--drop-capability=CAP_SYS_CHROOT" ];
+ description = ''
+ Extra flags passed to the systemd-nspawn command.
+ See systemd-nspawn(1) for details.
+ '';
+ };
+
+ } // networkOptions;
+
+ config = mkMerge
+ [
+ (mkIf options.config.isDefined {
+ path = config.config.system.build.toplevel;
+ })
+ ];
+ }));
+
+ default = {};
+ example = literalExample
+ ''
+ { webserver =
+ { path = "/nix/var/nix/profiles/webserver";
+ };
+ database =
+ { config =
+ { config, pkgs, ... }:
+ { services.postgresql.enable = true;
+ services.postgresql.package = pkgs.postgresql_9_6;
+
+ system.stateVersion = "17.03";
+ };
+ };
+ }
+ '';
+ description = ''
+ A set of NixOS system configurations to be run as lightweight
+ containers. Each container appears as a service
+ <literal>container-<replaceable>name</replaceable></literal>
+ on the host system, allowing it to be started and stopped via
+ <command>systemctl</command>.
+ '';
+ };
+
+ };
+
+
+ config = mkIf (config.boot.enableContainers) (let
+
+ unit = {
+ description = "Container '%i'";
+
+ unitConfig.RequiresMountsFor = "/var/lib/containers/%i";
+
+ path = [ pkgs.iproute ];
+
+ environment = {
+ root = "/var/lib/containers/%i";
+ INSTANCE = "%i";
+ };
+
+ preStart = preStartScript dummyConfig;
+
+ script = startScript dummyConfig;
+
+ postStart = postStartScript dummyConfig;
+
+ preStop =
+ ''
+ pid="$(cat /run/containers/$INSTANCE.pid)"
+ if [ -n "$pid" ]; then
+ kill -RTMIN+4 "$pid"
+ fi
+ rm -f "/run/containers/$INSTANCE.pid"
+ '';
+
+ restartIfChanged = false;
+
+ 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; }]
+ # declarative containers
+ ++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (let
+ containerConfig = cfg // (
+ if cfg.enableTun then
+ {
+ allowedDevices = cfg.allowedDevices
+ ++ [ { node = "/dev/net/tun"; modifier = "rw"; } ];
+ additionalCapabilities = cfg.additionalCapabilities
+ ++ [ "CAP_NET_ADMIN" ];
+ }
+ else {});
+ in
+ recursiveUpdate unit {
+ preStart = preStartScript containerConfig;
+ script = startScript containerConfig;
+ postStart = postStartScript containerConfig;
+ serviceConfig = serviceDirectives containerConfig;
+ unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "/var/lib/containers/%i";
+ environment.root = if containerConfig.ephemeral then "/run/containers/%i" else "/var/lib/containers/%i";
+ } // (
+ if containerConfig.autoStart then
+ {
+ wantedBy = [ "machines.target" ];
+ wants = [ "network.target" ];
+ after = [ "network.target" ];
+ restartTriggers = [
+ containerConfig.path
+ config.environment.etc."containers/${name}.conf".source
+ ];
+ restartIfChanged = true;
+ }
+ else {})
+ )) config.containers)
+ ));
+
+ # Generate a configuration file in /etc/containers for each
+ # 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);
+ in mapAttrs' (name: cfg: nameValuePair "containers/${name}.conf"
+ { text =
+ ''
+ SYSTEM_PATH=${cfg.path}
+ ${optionalString cfg.privateNetwork ''
+ PRIVATE_NETWORK=1
+ ${optionalString (cfg.hostBridge != null) ''
+ HOST_BRIDGE=${cfg.hostBridge}
+ ''}
+ ${optionalString (length cfg.forwardPorts > 0) ''
+ HOST_PORT=${concatStringsSep "," (map mkPortStr cfg.forwardPorts)}
+ ''}
+ ${optionalString (cfg.hostAddress != null) ''
+ HOST_ADDRESS=${cfg.hostAddress}
+ ''}
+ ${optionalString (cfg.hostAddress6 != null) ''
+ HOST_ADDRESS6=${cfg.hostAddress6}
+ ''}
+ ${optionalString (cfg.localAddress != null) ''
+ LOCAL_ADDRESS=${cfg.localAddress}
+ ''}
+ ${optionalString (cfg.localAddress6 != null) ''
+ LOCAL_ADDRESS6=${cfg.localAddress6}
+ ''}
+ ''}
+ INTERFACES="${toString cfg.interfaces}"
+ MACVLANS="${toString cfg.macvlans}"
+ ${optionalString cfg.autoStart ''
+ AUTO_START=1
+ ''}
+ EXTRA_NSPAWN_FLAGS="${mkBindFlags cfg.bindMounts +
+ optionalString (cfg.extraFlags != [])
+ (" " + concatStringsSep " " cfg.extraFlags)}"
+ '';
+ }) config.containers;
+
+ # Generate /etc/hosts entries for the containers.
+ networking.extraHosts = concatStrings (mapAttrsToList (name: cfg: optionalString (cfg.localAddress != null)
+ ''
+ ${head (splitString "/" cfg.localAddress)} ${name}.containers
+ '') config.containers);
+
+ networking.dhcpcd.denyInterfaces = [ "ve-*" "vb-*" ];
+
+ services.udev.extraRules = optionalString config.networking.networkmanager.enable ''
+ # Don't manage interfaces created by nixos-container.
+ ENV{INTERFACE}=="v[eb]-*", ENV{NM_UNMANAGED}="1"
+ '';
+
+ environment.systemPackages = [ pkgs.nixos-container ];
+ });
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/cri-o.nix b/nixpkgs/nixos/modules/virtualisation/cri-o.nix
new file mode 100644
index 00000000000..14a435f6c8b
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/cri-o.nix
@@ -0,0 +1,106 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.virtualisation.cri-o;
+in
+{
+ options.virtualisation.cri-o = {
+ enable = mkEnableOption "Container Runtime Interface for OCI (CRI-O)";
+
+ storageDriver = mkOption {
+ type = types.enum ["btrfs" "overlay" "vfs"];
+ default = "overlay";
+ description = "Storage driver to be used";
+ };
+
+ logLevel = mkOption {
+ type = types.enum ["trace" "debug" "info" "warn" "error" "fatal"];
+ default = "info";
+ description = "Log level to be used";
+ };
+
+ pauseImage = mkOption {
+ type = types.str;
+ default = "k8s.gcr.io/pause:3.1";
+ description = "Pause image for pod sandboxes to be used";
+ };
+
+ pauseCommand = mkOption {
+ type = types.str;
+ default = "/pause";
+ description = "Pause command to be executed";
+ };
+
+ registries = mkOption {
+ type = types.listOf types.str;
+ default = [ "docker.io" "quay.io" ];
+ description = "Registries to be configured for unqualified image pull";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs;
+ [ cri-o cri-tools conmon cni-plugins iptables runc utillinux ];
+ environment.etc."crictl.yaml".text = ''
+ runtime-endpoint: unix:///var/run/crio/crio.sock
+ '';
+ environment.etc."crio/crio.conf".text = ''
+ [crio]
+ storage_driver = "${cfg.storageDriver}"
+
+ [crio.image]
+ pause_image = "${cfg.pauseImage}"
+ pause_command = "${cfg.pauseCommand}"
+ registries = [
+ ${concatMapStringsSep ", " (x: "\"" + x + "\"") cfg.registries}
+ ]
+
+ [crio.runtime]
+ conmon = "${pkgs.conmon}/bin/conmon"
+ log_level = "${cfg.logLevel}"
+ manage_network_ns_lifecycle = true
+ '';
+ environment.etc."containers/policy.json".text = ''
+ {"default": [{"type": "insecureAcceptAnything"}]}
+ '';
+ environment.etc."cni/net.d/20-cri-o-bridge.conf".text = ''
+ {
+ "cniVersion": "0.3.1",
+ "name": "crio-bridge",
+ "type": "bridge",
+ "bridge": "cni0",
+ "isGateway": true,
+ "ipMasq": true,
+ "ipam": {
+ "type": "host-local",
+ "subnet": "10.88.0.0/16",
+ "routes": [
+ { "dst": "0.0.0.0/0" }
+ ]
+ }
+ }
+ '';
+
+ systemd.services.crio = {
+ description = "Container Runtime Interface for OCI (CRI-O)";
+ documentation = [ "https://github.com/cri-o/cri-o" ];
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = [ pkgs.utillinux pkgs.runc pkgs.iptables ];
+ serviceConfig = {
+ Type = "notify";
+ ExecStart = "${pkgs.cri-o}/bin/crio";
+ ExecReload = "/bin/kill -s HUP $MAINPID";
+ TasksMax = "infinity";
+ LimitNOFILE = "1048576";
+ LimitNPROC = "1048576";
+ LimitCORE = "infinity";
+ OOMScoreAdjust = "-999";
+ TimeoutStartSec = "0";
+ Restart = "on-abnormal";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/docker-containers.nix b/nixpkgs/nixos/modules/virtualisation/docker-containers.nix
new file mode 100644
index 00000000000..59b0943f591
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/docker-containers.nix
@@ -0,0 +1,230 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.docker-containers;
+
+ dockerContainer =
+ { ... }: {
+
+ options = {
+
+ image = mkOption {
+ type = types.str;
+ description = "Docker image to run.";
+ example = "library/hello-world";
+ };
+
+ cmd = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = "Commandline arguments to pass to the image's entrypoint.";
+ example = literalExample ''
+ ["--port=9000"]
+ '';
+ };
+
+ entrypoint = mkOption {
+ type = with types; nullOr str;
+ description = "Overwrite the default entrypoint of the image.";
+ default = null;
+ example = "/bin/my-app";
+ };
+
+ environment = mkOption {
+ type = with types; attrsOf str;
+ default = {};
+ description = "Environment variables to set for this container.";
+ example = literalExample ''
+ {
+ DATABASE_HOST = "db.example.com";
+ DATABASE_PORT = "3306";
+ }
+ '';
+ };
+
+ log-driver = mkOption {
+ type = types.str;
+ default = "none";
+ description = ''
+ Logging driver for the container. The default of
+ <literal>"none"</literal> means that the container's logs will be
+ handled as part of the systemd unit. Setting this to
+ <literal>"journald"</literal> will result in duplicate logging, but
+ the container's logs will be visible to the <command>docker
+ logs</command> command.
+
+ For more details and a full list of logging drivers, refer to the
+ <link xlink:href="https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver">
+ Docker engine documentation</link>
+ '';
+ };
+
+ ports = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = ''
+ Network ports to publish from the container to the outer host.
+
+ Valid formats:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>&lt;ip&gt;:&lt;hostPort&gt;:&lt;containerPort&gt;</literal>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>&lt;ip&gt;::&lt;containerPort&gt;</literal>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>&lt;hostPort&gt;:&lt;containerPort&gt;</literal>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>&lt;containerPort&gt;</literal>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ Both <literal>hostPort</literal> and
+ <literal>containerPort</literal> can be specified as a range of
+ ports. When specifying ranges for both, the number of container
+ ports in the range must match the number of host ports in the
+ range. Example: <literal>1234-1236:1234-1236/tcp</literal>
+
+ When specifying a range for <literal>hostPort</literal> only, the
+ <literal>containerPort</literal> must <emphasis>not</emphasis> be a
+ range. In this case, the container port is published somewhere
+ within the specified <literal>hostPort</literal> range. Example:
+ <literal>1234-1236:1234/tcp</literal>
+
+ Refer to the
+ <link xlink:href="https://docs.docker.com/engine/reference/run/#expose-incoming-ports">
+ Docker engine documentation</link> for full details.
+ '';
+ example = literalExample ''
+ [
+ "8080:9000"
+ ]
+ '';
+ };
+
+ user = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Override the username or UID (and optionally groupname or GID) used
+ in the container.
+ '';
+ example = "nobody:nogroup";
+ };
+
+ volumes = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = ''
+ List of volumes to attach to this container.
+
+ Note that this is a list of <literal>"src:dst"</literal> strings to
+ allow for <literal>src</literal> to refer to
+ <literal>/nix/store</literal> paths, which would difficult with an
+ attribute set. There are also a variety of mount options available
+ as a third field; please refer to the
+ <link xlink:href="https://docs.docker.com/engine/reference/run/#volume-shared-filesystems">
+ docker engine documentation</link> for details.
+ '';
+ example = literalExample ''
+ [
+ "volume_name:/path/inside/container"
+ "/path/on/host:/path/inside/container"
+ ]
+ '';
+ };
+
+ workdir = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "Override the default working directory for the container.";
+ example = "/var/lib/hello_world";
+ };
+
+ extraDockerOptions = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = "Extra options for <command>docker run</command>.";
+ example = literalExample ''
+ ["--network=host"]
+ '';
+ };
+ };
+ };
+
+ mkService = name: container: {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "docker.service" "docker.socket" ];
+ requires = [ "docker.service" "docker.socket" ];
+ serviceConfig = {
+ ExecStart = concatStringsSep " \\\n " ([
+ "${pkgs.docker}/bin/docker run"
+ "--rm"
+ "--name=%n"
+ "--log-driver=${container.log-driver}"
+ ] ++ optional (container.entrypoint != null)
+ "--entrypoint=${escapeShellArg container.entrypoint}"
+ ++ (mapAttrsToList (k: v: "-e ${escapeShellArg k}=${escapeShellArg v}") container.environment)
+ ++ map (p: "-p ${escapeShellArg p}") container.ports
+ ++ optional (container.user != null) "-u ${escapeShellArg container.user}"
+ ++ map (v: "-v ${escapeShellArg v}") container.volumes
+ ++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
+ ++ map escapeShellArg container.extraDockerOptions
+ ++ [container.image]
+ ++ map escapeShellArg container.cmd
+ );
+ ExecStartPre = "-${pkgs.docker}/bin/docker rm -f %n";
+ ExecStop = "${pkgs.docker}/bin/docker stop %n";
+ ExecStopPost = "-${pkgs.docker}/bin/docker rm -f %n";
+
+ ### There is no generalized way of supporting `reload` for docker
+ ### containers. Some containers may respond well to SIGHUP sent to their
+ ### init process, but it is not guaranteed; some apps have other reload
+ ### mechanisms, some don't have a reload signal at all, and some docker
+ ### images just have broken signal handling. The best compromise in this
+ ### case is probably to leave ExecReload undefined, so `systemctl reload`
+ ### will at least result in an error instead of potentially undefined
+ ### behaviour.
+ ###
+ ### Advanced users can still override this part of the unit to implement
+ ### a custom reload handler, since the result of all this is a normal
+ ### systemd service from the perspective of the NixOS module system.
+ ###
+ # ExecReload = ...;
+ ###
+
+ TimeoutStartSec = 0;
+ TimeoutStopSec = 120;
+ Restart = "always";
+ };
+ };
+
+in {
+
+ options.docker-containers = mkOption {
+ default = {};
+ type = types.attrsOf (types.submodule dockerContainer);
+ description = "Docker containers to run as systemd services.";
+ };
+
+ config = mkIf (cfg != {}) {
+
+ systemd.services = mapAttrs' (n: v: nameValuePair "docker-${n}" (mkService n v)) cfg;
+
+ virtualisation.docker.enable = true;
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/docker-image.nix b/nixpkgs/nixos/modules/virtualisation/docker-image.nix
new file mode 100644
index 00000000000..baac3a35a78
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/docker-image.nix
@@ -0,0 +1,57 @@
+{ ... }:
+
+{
+ imports = [
+ ../profiles/docker-container.nix # FIXME, shouldn't include something from profiles/
+ ];
+
+ boot.postBootCommands =
+ ''
+ # Set virtualisation to docker
+ echo "docker" > /run/systemd/container
+ '';
+
+ # Iptables do not work in Docker.
+ networking.firewall.enable = false;
+
+ # 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/nixpkgs/nixos/modules/virtualisation/docker-preloader.nix b/nixpkgs/nixos/modules/virtualisation/docker-preloader.nix
new file mode 100644
index 00000000000..6ab83058dee
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/virtualisation/docker.nix b/nixpkgs/nixos/modules/virtualisation/docker.nix
new file mode 100644
index 00000000000..7d196a46276
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/docker.nix
@@ -0,0 +1,223 @@
+# Systemd services for docker.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.virtualisation.docker;
+ proxy_env = config.networking.proxy.envVars;
+
+in
+
+{
+ ###### interface
+
+ options.virtualisation.docker = {
+ enable =
+ mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ This option enables docker, a daemon that manages
+ linux containers. Users in the "docker" group can interact with
+ the daemon (e.g. to start or stop containers) using the
+ <command>docker</command> command line tool.
+ '';
+ };
+
+ listenOptions =
+ mkOption {
+ type = types.listOf types.str;
+ default = ["/run/docker.sock"];
+ description =
+ ''
+ A list of unix and tcp docker should listen to. The format follows
+ ListenStream as described in systemd.socket(5).
+ '';
+ };
+
+ enableOnBoot =
+ mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ When enabled dockerd is started on boot. This is required for
+ containers which are created with the
+ <literal>--restart=always</literal> flag to work. If this option is
+ disabled, docker might be started on demand by socket activation.
+ '';
+ };
+
+ enableNvidia =
+ mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable nvidia-docker wrapper, supporting NVIDIA GPUs inside docker containers.
+ '';
+ };
+
+ liveRestore =
+ mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ ''
+ Allow dockerd to be restarted without affecting running container.
+ This option is incompatible with docker swarm.
+ '';
+ };
+
+ storageDriver =
+ mkOption {
+ type = types.nullOr (types.enum ["aufs" "btrfs" "devicemapper" "overlay" "overlay2" "zfs"]);
+ default = null;
+ description =
+ ''
+ This option determines which Docker storage driver to use. By default
+ it let's docker automatically choose preferred storage driver.
+ '';
+ };
+
+ logDriver =
+ mkOption {
+ type = types.enum ["none" "json-file" "syslog" "journald" "gelf" "fluentd" "awslogs" "splunk" "etwlogs" "gcplogs"];
+ default = "journald";
+ description =
+ ''
+ This option determines which Docker log driver to use.
+ '';
+ };
+
+ extraOptions =
+ mkOption {
+ type = types.separatedString " ";
+ default = "";
+ description =
+ ''
+ The extra command-line options to pass to
+ <command>docker</command> daemon.
+ '';
+ };
+
+ autoPrune = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to periodically prune Docker resources. If enabled, a
+ systemd timer will run <literal>docker system prune -f</literal>
+ as specified by the <literal>dates</literal> option.
+ '';
+ };
+
+ flags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "--all" ];
+ description = ''
+ Any additional flags passed to <command>docker system prune</command>.
+ '';
+ };
+
+ dates = mkOption {
+ default = "weekly";
+ type = types.str;
+ description = ''
+ Specification (in the format described by
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>) of the time at
+ which the prune will occur.
+ '';
+ };
+ };
+
+ package = mkOption {
+ default = pkgs.docker;
+ type = types.package;
+ example = pkgs.docker-edge;
+ description = ''
+ Docker package to be used in the module.
+ '';
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable (mkMerge [{
+ environment.systemPackages = [ cfg.package ]
+ ++ optional cfg.enableNvidia pkgs.nvidia-docker;
+ users.groups.docker.gid = config.ids.gids.docker;
+ systemd.packages = [ cfg.package ];
+
+ systemd.services.docker = {
+ wantedBy = optional cfg.enableOnBoot "multi-user.target";
+ environment = proxy_env;
+ serviceConfig = {
+ ExecStart = [
+ ""
+ ''
+ ${cfg.package}/bin/dockerd \
+ --group=docker \
+ --host=fd:// \
+ --log-driver=${cfg.logDriver} \
+ ${optionalString (cfg.storageDriver != null) "--storage-driver=${cfg.storageDriver}"} \
+ ${optionalString cfg.liveRestore "--live-restore" } \
+ ${optionalString cfg.enableNvidia "--add-runtime nvidia=${pkgs.nvidia-docker}/bin/nvidia-container-runtime" } \
+ ${cfg.extraOptions}
+ ''];
+ ExecReload=[
+ ""
+ "${pkgs.procps}/bin/kill -s HUP $MAINPID"
+ ];
+ };
+
+ path = [ pkgs.kmod ] ++ optional (cfg.storageDriver == "zfs") pkgs.zfs
+ ++ optional cfg.enableNvidia pkgs.nvidia-docker;
+ };
+
+ systemd.sockets.docker = {
+ description = "Docker Socket for the API";
+ wantedBy = [ "sockets.target" ];
+ socketConfig = {
+ ListenStream = cfg.listenOptions;
+ SocketMode = "0660";
+ SocketUser = "root";
+ SocketGroup = "docker";
+ };
+ };
+
+ systemd.services.docker-prune = {
+ description = "Prune docker resources";
+
+ restartIfChanged = false;
+ unitConfig.X-StopOnRemoval = false;
+
+ serviceConfig.Type = "oneshot";
+
+ script = ''
+ ${cfg.package}/bin/docker system prune -f ${toString cfg.autoPrune.flags}
+ '';
+
+ startAt = optional cfg.autoPrune.enable cfg.autoPrune.dates;
+ };
+
+ assertions = [
+ { assertion = cfg.enableNvidia -> config.hardware.opengl.driSupport32Bit or false;
+ message = "Option enableNvidia requires 32bit support libraries";
+ }];
+ }
+ (mkIf cfg.enableNvidia {
+ environment.etc."nvidia-container-runtime/config.toml".source = "${pkgs.nvidia-docker}/etc/config.toml";
+ })
+ ]);
+
+ imports = [
+ (mkRemovedOptionModule ["virtualisation" "docker" "socketActivation"] "This option was removed in favor of starting docker at boot")
+ ];
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/ec2-amis.nix b/nixpkgs/nixos/modules/virtualisation/ec2-amis.nix
new file mode 100644
index 00000000000..f640bb21b13
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/ec2-amis.nix
@@ -0,0 +1,295 @@
+let self = {
+ "14.04".ap-northeast-1.hvm-ebs = "ami-71c6f470";
+ "14.04".ap-northeast-1.pv-ebs = "ami-4dcbf84c";
+ "14.04".ap-northeast-1.pv-s3 = "ami-8fc4f68e";
+ "14.04".ap-southeast-1.hvm-ebs = "ami-da280888";
+ "14.04".ap-southeast-1.pv-ebs = "ami-7a9dbc28";
+ "14.04".ap-southeast-1.pv-s3 = "ami-c4290996";
+ "14.04".ap-southeast-2.hvm-ebs = "ami-ab523e91";
+ "14.04".ap-southeast-2.pv-ebs = "ami-6769055d";
+ "14.04".ap-southeast-2.pv-s3 = "ami-15533f2f";
+ "14.04".eu-central-1.hvm-ebs = "ami-ba0234a7";
+ "14.04".eu-west-1.hvm-ebs = "ami-96cb63e1";
+ "14.04".eu-west-1.pv-ebs = "ami-b48c25c3";
+ "14.04".eu-west-1.pv-s3 = "ami-06cd6571";
+ "14.04".sa-east-1.hvm-ebs = "ami-01b90e1c";
+ "14.04".sa-east-1.pv-ebs = "ami-69e35474";
+ "14.04".sa-east-1.pv-s3 = "ami-61b90e7c";
+ "14.04".us-east-1.hvm-ebs = "ami-58ba3a30";
+ "14.04".us-east-1.pv-ebs = "ami-9e0583f6";
+ "14.04".us-east-1.pv-s3 = "ami-9cbe3ef4";
+ "14.04".us-west-1.hvm-ebs = "ami-0bc3d74e";
+ "14.04".us-west-1.pv-ebs = "ami-8b1703ce";
+ "14.04".us-west-1.pv-s3 = "ami-27ccd862";
+ "14.04".us-west-2.hvm-ebs = "ami-3bf1bf0b";
+ "14.04".us-west-2.pv-ebs = "ami-259bd515";
+ "14.04".us-west-2.pv-s3 = "ami-07094037";
+
+ "14.12".ap-northeast-1.hvm-ebs = "ami-24435f25";
+ "14.12".ap-northeast-1.pv-ebs = "ami-b0425eb1";
+ "14.12".ap-northeast-1.pv-s3 = "ami-fed3c6ff";
+ "14.12".ap-southeast-1.hvm-ebs = "ami-6c765d3e";
+ "14.12".ap-southeast-1.pv-ebs = "ami-6a765d38";
+ "14.12".ap-southeast-1.pv-s3 = "ami-d1bf9183";
+ "14.12".ap-southeast-2.hvm-ebs = "ami-af86f395";
+ "14.12".ap-southeast-2.pv-ebs = "ami-b386f389";
+ "14.12".ap-southeast-2.pv-s3 = "ami-69c5ae53";
+ "14.12".eu-central-1.hvm-ebs = "ami-4a497a57";
+ "14.12".eu-central-1.pv-ebs = "ami-4c497a51";
+ "14.12".eu-central-1.pv-s3 = "ami-60f2c27d";
+ "14.12".eu-west-1.hvm-ebs = "ami-d126a5a6";
+ "14.12".eu-west-1.pv-ebs = "ami-0126a576";
+ "14.12".eu-west-1.pv-s3 = "ami-deda5fa9";
+ "14.12".sa-east-1.hvm-ebs = "ami-2d239e30";
+ "14.12".sa-east-1.pv-ebs = "ami-35239e28";
+ "14.12".sa-east-1.pv-s3 = "ami-81e3519c";
+ "14.12".us-east-1.hvm-ebs = "ami-0c463a64";
+ "14.12".us-east-1.pv-ebs = "ami-ac473bc4";
+ "14.12".us-east-1.pv-s3 = "ami-00e18a68";
+ "14.12".us-west-1.hvm-ebs = "ami-ca534a8f";
+ "14.12".us-west-1.pv-ebs = "ami-3e534a7b";
+ "14.12".us-west-1.pv-s3 = "ami-2905196c";
+ "14.12".us-west-2.hvm-ebs = "ami-fb9dc3cb";
+ "14.12".us-west-2.pv-ebs = "ami-899dc3b9";
+ "14.12".us-west-2.pv-s3 = "ami-cb7f2dfb";
+
+ "15.09".ap-northeast-1.hvm-ebs = "ami-58cac236";
+ "15.09".ap-northeast-1.hvm-s3 = "ami-39c8c057";
+ "15.09".ap-northeast-1.pv-ebs = "ami-5ac9c134";
+ "15.09".ap-northeast-1.pv-s3 = "ami-03cec66d";
+ "15.09".ap-southeast-1.hvm-ebs = "ami-2fc2094c";
+ "15.09".ap-southeast-1.hvm-s3 = "ami-9ec308fd";
+ "15.09".ap-southeast-1.pv-ebs = "ami-95c00bf6";
+ "15.09".ap-southeast-1.pv-s3 = "ami-bfc00bdc";
+ "15.09".ap-southeast-2.hvm-ebs = "ami-996c4cfa";
+ "15.09".ap-southeast-2.hvm-s3 = "ami-3f6e4e5c";
+ "15.09".ap-southeast-2.pv-ebs = "ami-066d4d65";
+ "15.09".ap-southeast-2.pv-s3 = "ami-cc6e4eaf";
+ "15.09".eu-central-1.hvm-ebs = "ami-3f8c6b50";
+ "15.09".eu-central-1.hvm-s3 = "ami-5b836434";
+ "15.09".eu-central-1.pv-ebs = "ami-118c6b7e";
+ "15.09".eu-central-1.pv-s3 = "ami-2c977043";
+ "15.09".eu-west-1.hvm-ebs = "ami-9cf04aef";
+ "15.09".eu-west-1.hvm-s3 = "ami-2bea5058";
+ "15.09".eu-west-1.pv-ebs = "ami-c9e852ba";
+ "15.09".eu-west-1.pv-s3 = "ami-c6f64cb5";
+ "15.09".sa-east-1.hvm-ebs = "ami-6e52df02";
+ "15.09".sa-east-1.hvm-s3 = "ami-1852df74";
+ "15.09".sa-east-1.pv-ebs = "ami-4368e52f";
+ "15.09".sa-east-1.pv-s3 = "ami-f15ad79d";
+ "15.09".us-east-1.hvm-ebs = "ami-84a6a0ee";
+ "15.09".us-east-1.hvm-s3 = "ami-06a7a16c";
+ "15.09".us-east-1.pv-ebs = "ami-a4a1a7ce";
+ "15.09".us-east-1.pv-s3 = "ami-5ba8ae31";
+ "15.09".us-west-1.hvm-ebs = "ami-22c8bb42";
+ "15.09".us-west-1.hvm-s3 = "ami-a2ccbfc2";
+ "15.09".us-west-1.pv-ebs = "ami-10cebd70";
+ "15.09".us-west-1.pv-s3 = "ami-fa30429a";
+ "15.09".us-west-2.hvm-ebs = "ami-ce57b9ae";
+ "15.09".us-west-2.hvm-s3 = "ami-2956b849";
+ "15.09".us-west-2.pv-ebs = "ami-005fb160";
+ "15.09".us-west-2.pv-s3 = "ami-cd55bbad";
+
+ "16.03".ap-northeast-1.hvm-ebs = "ami-40619d21";
+ "16.03".ap-northeast-1.hvm-s3 = "ami-ce629eaf";
+ "16.03".ap-northeast-1.pv-ebs = "ami-ef639f8e";
+ "16.03".ap-northeast-1.pv-s3 = "ami-a1609cc0";
+ "16.03".ap-northeast-2.hvm-ebs = "ami-deca00b0";
+ "16.03".ap-northeast-2.hvm-s3 = "ami-a3b77dcd";
+ "16.03".ap-northeast-2.pv-ebs = "ami-7bcb0115";
+ "16.03".ap-northeast-2.pv-s3 = "ami-a2b77dcc";
+ "16.03".ap-south-1.hvm-ebs = "ami-0dff9562";
+ "16.03".ap-south-1.hvm-s3 = "ami-13f69c7c";
+ "16.03".ap-south-1.pv-ebs = "ami-0ef39961";
+ "16.03".ap-south-1.pv-s3 = "ami-e0c8a28f";
+ "16.03".ap-southeast-1.hvm-ebs = "ami-5e964a3d";
+ "16.03".ap-southeast-1.hvm-s3 = "ami-4d964a2e";
+ "16.03".ap-southeast-1.pv-ebs = "ami-ec9b478f";
+ "16.03".ap-southeast-1.pv-s3 = "ami-999b47fa";
+ "16.03".ap-southeast-2.hvm-ebs = "ami-9f7359fc";
+ "16.03".ap-southeast-2.hvm-s3 = "ami-987359fb";
+ "16.03".ap-southeast-2.pv-ebs = "ami-a2705ac1";
+ "16.03".ap-southeast-2.pv-s3 = "ami-a3705ac0";
+ "16.03".eu-central-1.hvm-ebs = "ami-17a45178";
+ "16.03".eu-central-1.hvm-s3 = "ami-f9a55096";
+ "16.03".eu-central-1.pv-ebs = "ami-c8a550a7";
+ "16.03".eu-central-1.pv-s3 = "ami-6ea45101";
+ "16.03".eu-west-1.hvm-ebs = "ami-b5b3d5c6";
+ "16.03".eu-west-1.hvm-s3 = "ami-c986e0ba";
+ "16.03".eu-west-1.pv-ebs = "ami-b083e5c3";
+ "16.03".eu-west-1.pv-s3 = "ami-3c83e54f";
+ "16.03".sa-east-1.hvm-ebs = "ami-f6eb7f9a";
+ "16.03".sa-east-1.hvm-s3 = "ami-93e773ff";
+ "16.03".sa-east-1.pv-ebs = "ami-cbb82ca7";
+ "16.03".sa-east-1.pv-s3 = "ami-abb82cc7";
+ "16.03".us-east-1.hvm-ebs = "ami-c123a3d6";
+ "16.03".us-east-1.hvm-s3 = "ami-bc25a5ab";
+ "16.03".us-east-1.pv-ebs = "ami-bd25a5aa";
+ "16.03".us-east-1.pv-s3 = "ami-a325a5b4";
+ "16.03".us-west-1.hvm-ebs = "ami-748bcd14";
+ "16.03".us-west-1.hvm-s3 = "ami-a68dcbc6";
+ "16.03".us-west-1.pv-ebs = "ami-048acc64";
+ "16.03".us-west-1.pv-s3 = "ami-208dcb40";
+ "16.03".us-west-2.hvm-ebs = "ami-8263a0e2";
+ "16.03".us-west-2.hvm-s3 = "ami-925c9ff2";
+ "16.03".us-west-2.pv-ebs = "ami-5e61a23e";
+ "16.03".us-west-2.pv-s3 = "ami-734c8f13";
+
+ # 16.09.1508.3909827
+ "16.09".ap-northeast-1.hvm-ebs = "ami-68453b0f";
+ "16.09".ap-northeast-1.hvm-s3 = "ami-f9bec09e";
+ "16.09".ap-northeast-1.pv-ebs = "ami-254a3442";
+ "16.09".ap-northeast-1.pv-s3 = "ami-ef473988";
+ "16.09".ap-northeast-2.hvm-ebs = "ami-18ae7f76";
+ "16.09".ap-northeast-2.hvm-s3 = "ami-9eac7df0";
+ "16.09".ap-northeast-2.pv-ebs = "ami-57aa7b39";
+ "16.09".ap-northeast-2.pv-s3 = "ami-5cae7f32";
+ "16.09".ap-south-1.hvm-ebs = "ami-b3f98fdc";
+ "16.09".ap-south-1.hvm-s3 = "ami-98e690f7";
+ "16.09".ap-south-1.pv-ebs = "ami-aef98fc1";
+ "16.09".ap-south-1.pv-s3 = "ami-caf88ea5";
+ "16.09".ap-southeast-1.hvm-ebs = "ami-80fb51e3";
+ "16.09".ap-southeast-1.hvm-s3 = "ami-2df3594e";
+ "16.09".ap-southeast-1.pv-ebs = "ami-37f05a54";
+ "16.09".ap-southeast-1.pv-s3 = "ami-27f35944";
+ "16.09".ap-southeast-2.hvm-ebs = "ami-57ece834";
+ "16.09".ap-southeast-2.hvm-s3 = "ami-87f4f0e4";
+ "16.09".ap-southeast-2.pv-ebs = "ami-d8ede9bb";
+ "16.09".ap-southeast-2.pv-s3 = "ami-a6ebefc5";
+ "16.09".ca-central-1.hvm-ebs = "ami-9f863bfb";
+ "16.09".ca-central-1.hvm-s3 = "ami-ea85388e";
+ "16.09".ca-central-1.pv-ebs = "ami-ce8a37aa";
+ "16.09".ca-central-1.pv-s3 = "ami-448a3720";
+ "16.09".eu-central-1.hvm-ebs = "ami-1b884774";
+ "16.09".eu-central-1.hvm-s3 = "ami-b08c43df";
+ "16.09".eu-central-1.pv-ebs = "ami-888946e7";
+ "16.09".eu-central-1.pv-s3 = "ami-06874869";
+ "16.09".eu-west-1.hvm-ebs = "ami-1ed3e76d";
+ "16.09".eu-west-1.hvm-s3 = "ami-73d1e500";
+ "16.09".eu-west-1.pv-ebs = "ami-44c0f437";
+ "16.09".eu-west-1.pv-s3 = "ami-f3d8ec80";
+ "16.09".eu-west-2.hvm-ebs = "ami-2c9c9648";
+ "16.09".eu-west-2.hvm-s3 = "ami-6b9e940f";
+ "16.09".eu-west-2.pv-ebs = "ami-f1999395";
+ "16.09".eu-west-2.pv-s3 = "ami-bb9f95df";
+ "16.09".sa-east-1.hvm-ebs = "ami-a11882cd";
+ "16.09".sa-east-1.hvm-s3 = "ami-7726bc1b";
+ "16.09".sa-east-1.pv-ebs = "ami-9725bffb";
+ "16.09".sa-east-1.pv-s3 = "ami-b027bddc";
+ "16.09".us-east-1.hvm-ebs = "ami-854ca593";
+ "16.09".us-east-1.hvm-s3 = "ami-2241a834";
+ "16.09".us-east-1.pv-ebs = "ami-a441a8b2";
+ "16.09".us-east-1.pv-s3 = "ami-e841a8fe";
+ "16.09".us-east-2.hvm-ebs = "ami-3f41645a";
+ "16.09".us-east-2.hvm-s3 = "ami-804065e5";
+ "16.09".us-east-2.pv-ebs = "ami-f1466394";
+ "16.09".us-east-2.pv-s3 = "ami-05426760";
+ "16.09".us-west-1.hvm-ebs = "ami-c2efbca2";
+ "16.09".us-west-1.hvm-s3 = "ami-d71042b7";
+ "16.09".us-west-1.pv-ebs = "ami-04e8bb64";
+ "16.09".us-west-1.pv-s3 = "ami-31e9ba51";
+ "16.09".us-west-2.hvm-ebs = "ami-6449f504";
+ "16.09".us-west-2.hvm-s3 = "ami-344af654";
+ "16.09".us-west-2.pv-ebs = "ami-6d4af60d";
+ "16.09".us-west-2.pv-s3 = "ami-de48f4be";
+
+ # 17.03.885.6024dd4067
+ "17.03".ap-northeast-1.hvm-ebs = "ami-dbd0f7bc";
+ "17.03".ap-northeast-1.hvm-s3 = "ami-7cdff81b";
+ "17.03".ap-northeast-2.hvm-ebs = "ami-c59a48ab";
+ "17.03".ap-northeast-2.hvm-s3 = "ami-0b944665";
+ "17.03".ap-south-1.hvm-ebs = "ami-4f413220";
+ "17.03".ap-south-1.hvm-s3 = "ami-864033e9";
+ "17.03".ap-southeast-1.hvm-ebs = "ami-e08c3383";
+ "17.03".ap-southeast-1.hvm-s3 = "ami-c28f30a1";
+ "17.03".ap-southeast-2.hvm-ebs = "ami-fca9a69f";
+ "17.03".ap-southeast-2.hvm-s3 = "ami-3daaa55e";
+ "17.03".ca-central-1.hvm-ebs = "ami-9b00bdff";
+ "17.03".ca-central-1.hvm-s3 = "ami-e800bd8c";
+ "17.03".eu-central-1.hvm-ebs = "ami-5450803b";
+ "17.03".eu-central-1.hvm-s3 = "ami-6e2efe01";
+ "17.03".eu-west-1.hvm-ebs = "ami-10754c76";
+ "17.03".eu-west-1.hvm-s3 = "ami-11734a77";
+ "17.03".eu-west-2.hvm-ebs = "ami-ff1d099b";
+ "17.03".eu-west-2.hvm-s3 = "ami-fe1d099a";
+ "17.03".sa-east-1.hvm-ebs = "ami-d95d3eb5";
+ "17.03".sa-east-1.hvm-s3 = "ami-fca2c190";
+ "17.03".us-east-1.hvm-ebs = "ami-0940c61f";
+ "17.03".us-east-1.hvm-s3 = "ami-674fc971";
+ "17.03".us-east-2.hvm-ebs = "ami-afc2e6ca";
+ "17.03".us-east-2.hvm-s3 = "ami-a1cde9c4";
+ "17.03".us-west-1.hvm-ebs = "ami-587b2138";
+ "17.03".us-west-1.hvm-s3 = "ami-70411b10";
+ "17.03".us-west-2.hvm-ebs = "ami-a93daac9";
+ "17.03".us-west-2.hvm-s3 = "ami-5139ae31";
+
+ # 17.09.2681.59661f21be6
+ "17.09".eu-west-1.hvm-ebs = "ami-a30192da";
+ "17.09".eu-west-2.hvm-ebs = "ami-295a414d";
+ "17.09".eu-west-3.hvm-ebs = "ami-8c0eb9f1";
+ "17.09".eu-central-1.hvm-ebs = "ami-266cfe49";
+ "17.09".us-east-1.hvm-ebs = "ami-40bee63a";
+ "17.09".us-east-2.hvm-ebs = "ami-9d84aff8";
+ "17.09".us-west-1.hvm-ebs = "ami-d14142b1";
+ "17.09".us-west-2.hvm-ebs = "ami-3eb40346";
+ "17.09".ca-central-1.hvm-ebs = "ami-ca8207ae";
+ "17.09".ap-southeast-1.hvm-ebs = "ami-84bccff8";
+ "17.09".ap-southeast-2.hvm-ebs = "ami-0dc5386f";
+ "17.09".ap-northeast-1.hvm-ebs = "ami-89b921ef";
+ "17.09".ap-northeast-2.hvm-ebs = "ami-179b3b79";
+ "17.09".sa-east-1.hvm-ebs = "ami-4762202b";
+ "17.09".ap-south-1.hvm-ebs = "ami-4e376021";
+
+ # 18.03.132946.1caae7247b8
+ "18.03".eu-west-1.hvm-ebs = "ami-065c46ec";
+ "18.03".eu-west-2.hvm-ebs = "ami-64f31903";
+ "18.03".eu-west-3.hvm-ebs = "ami-5a8d3d27";
+ "18.03".eu-central-1.hvm-ebs = "ami-09faf9e2";
+ "18.03".us-east-1.hvm-ebs = "ami-8b3538f4";
+ "18.03".us-east-2.hvm-ebs = "ami-150b3170";
+ "18.03".us-west-1.hvm-ebs = "ami-ce06ebad";
+ "18.03".us-west-2.hvm-ebs = "ami-586c3520";
+ "18.03".ca-central-1.hvm-ebs = "ami-aca72ac8";
+ "18.03".ap-southeast-1.hvm-ebs = "ami-aa0b4d40";
+ "18.03".ap-southeast-2.hvm-ebs = "ami-d0f254b2";
+ "18.03".ap-northeast-1.hvm-ebs = "ami-456511a8";
+ "18.03".ap-northeast-2.hvm-ebs = "ami-3366d15d";
+ "18.03".sa-east-1.hvm-ebs = "ami-163e1f7a";
+ "18.03".ap-south-1.hvm-ebs = "ami-6a390b05";
+
+ # 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";
+
+ # 19.03.172286.8ea36d73256
+ "19.03".eu-west-1.hvm-ebs = "ami-0fe40176548ff0940";
+ "19.03".eu-west-2.hvm-ebs = "ami-03a40fd3a02fe95ba";
+ "19.03".eu-west-3.hvm-ebs = "ami-0436f9da0f20a638e";
+ "19.03".eu-central-1.hvm-ebs = "ami-0022b8ea9efde5de4";
+ "19.03".us-east-1.hvm-ebs = "ami-0efc58fb70ae9a217";
+ "19.03".us-east-2.hvm-ebs = "ami-0abf711b1b34da1af";
+ "19.03".us-west-1.hvm-ebs = "ami-07d126e8838c40ec5";
+ "19.03".us-west-2.hvm-ebs = "ami-03f8a737546e47fb0";
+ "19.03".ca-central-1.hvm-ebs = "ami-03f9fd0ef2e035ede";
+ "19.03".ap-southeast-1.hvm-ebs = "ami-0cff66114c652c262";
+ "19.03".ap-southeast-2.hvm-ebs = "ami-054c73a7f8d773ea9";
+ "19.03".ap-northeast-1.hvm-ebs = "ami-00db62688900456a4";
+ "19.03".ap-northeast-2.hvm-ebs = "ami-0485cdd1a5fdd2117";
+ "19.03".sa-east-1.hvm-ebs = "ami-0c6a43c6e0ad1f4e2";
+ "19.03".ap-south-1.hvm-ebs = "ami-0303deb1b5890f878";
+
+ latest = self."19.03";
+}; in self
diff --git a/nixpkgs/nixos/modules/virtualisation/ec2-data.nix b/nixpkgs/nixos/modules/virtualisation/ec2-data.nix
new file mode 100644
index 00000000000..82451787e8a
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/ec2-data.nix
@@ -0,0 +1,87 @@
+# This module defines a systemd service that sets the SSH host key and
+# authorized client key and host name of virtual machines running on
+# Amazon EC2, Eucalyptus and OpenStack Compute (Nova).
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+
+ systemd.services.apply-ec2-data =
+ { description = "Apply EC2 Data";
+
+ wantedBy = [ "multi-user.target" "sshd.service" ];
+ before = [ "sshd.service" ];
+
+ path = [ pkgs.iproute ];
+
+ script =
+ ''
+ ${optionalString (config.networking.hostName == "") ''
+ echo "setting host name..."
+ if [ -s /etc/ec2-metadata/hostname ]; then
+ ${pkgs.nettools}/bin/hostname $(cat /etc/ec2-metadata/hostname)
+ fi
+ ''}
+
+ if ! [ -e /root/.ssh/authorized_keys ]; then
+ echo "obtaining SSH key..."
+ mkdir -m 0700 -p /root/.ssh
+ if [ -s /etc/ec2-metadata/public-keys-0-openssh-key ]; then
+ cat /etc/ec2-metadata/public-keys-0-openssh-key >> /root/.ssh/authorized_keys
+ echo "new key added to authorized_keys"
+ chmod 600 /root/.ssh/authorized_keys
+ fi
+ fi
+
+ # Extract the intended SSH host key for this machine from
+ # the supplied user data, if available. Otherwise sshd will
+ # generate one normally.
+ userData=/etc/ec2-metadata/user-data
+
+ mkdir -m 0755 -p /etc/ssh
+
+ if [ -s "$userData" ]; then
+ key="$(sed 's/|/\n/g; s/SSH_HOST_DSA_KEY://; t; d' $userData)"
+ key_pub="$(sed 's/SSH_HOST_DSA_KEY_PUB://; t; d' $userData)"
+ if [ -n "$key" -a -n "$key_pub" -a ! -e /etc/ssh/ssh_host_dsa_key ]; then
+ (umask 077; echo "$key" > /etc/ssh/ssh_host_dsa_key)
+ echo "$key_pub" > /etc/ssh/ssh_host_dsa_key.pub
+ fi
+
+ key="$(sed 's/|/\n/g; s/SSH_HOST_ED25519_KEY://; t; d' $userData)"
+ key_pub="$(sed 's/SSH_HOST_ED25519_KEY_PUB://; t; d' $userData)"
+ if [ -n "$key" -a -n "$key_pub" -a ! -e /etc/ssh/ssh_host_ed25519_key ]; then
+ (umask 077; echo "$key" > /etc/ssh/ssh_host_ed25519_key)
+ echo "$key_pub" > /etc/ssh/ssh_host_ed25519_key.pub
+ fi
+ fi
+ '';
+
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ };
+
+ systemd.services.print-host-key =
+ { description = "Print SSH Host Key";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "sshd.service" ];
+ script =
+ ''
+ # Print the host public key on the console so that the user
+ # can obtain it securely by parsing the output of
+ # ec2-get-console-output.
+ echo "-----BEGIN SSH HOST KEY FINGERPRINTS-----" > /dev/console
+ for i in /etc/ssh/ssh_host_*_key.pub; do
+ ${config.programs.ssh.package}/bin/ssh-keygen -l -f $i > /dev/console
+ done
+ echo "-----END SSH HOST KEY FINGERPRINTS-----" > /dev/console
+ '';
+ serviceConfig.Type = "oneshot";
+ serviceConfig.RemainAfterExit = true;
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.nix b/nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.nix
new file mode 100644
index 00000000000..b531787c31a
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/virtualisation/ecs-agent.nix b/nixpkgs/nixos/modules/virtualisation/ecs-agent.nix
new file mode 100644
index 00000000000..fc51b159579
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/ecs-agent.nix
@@ -0,0 +1,46 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.ecs-agent;
+in {
+ options.services.ecs-agent = {
+ enable = mkEnableOption "Amazon ECS agent";
+
+ package = mkOption {
+ type = types.path;
+ description = "The ECS agent package to use";
+ default = pkgs.ecs-agent;
+ defaultText = "pkgs.ecs-agent";
+ };
+
+ extra-environment = mkOption {
+ type = types.attrsOf types.str;
+ description = "The environment the ECS agent should run with. See the ECS agent documentation for keys that work here.";
+ default = {};
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ # This service doesn't run if docker isn't running, and unlike potentially remote services like e.g., postgresql, docker has
+ # to be running locally so `docker.enable` will always be set if the ECS agent is enabled.
+ virtualisation.docker.enable = true;
+
+ systemd.services.ecs-agent = {
+ inherit (cfg.package.meta) description;
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ environment = cfg.extra-environment;
+
+ script = ''
+ if [ ! -z "$ECS_DATADIR" ]; then
+ mkdir -p "$ECS_DATADIR"
+ fi
+ ${cfg.package.bin}/bin/agent
+ '';
+ };
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/virtualisation/gce-images.nix b/nixpkgs/nixos/modules/virtualisation/gce-images.nix
new file mode 100644
index 00000000000..5354d91deb9
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/gce-images.nix
@@ -0,0 +1,9 @@
+let self = {
+ "14.12" = "gs://nixos-cloud-images/nixos-14.12.471.1f09b77-x86_64-linux.raw.tar.gz";
+ "15.09" = "gs://nixos-cloud-images/nixos-15.09.425.7870f20-x86_64-linux.raw.tar.gz";
+ "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";
+ "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/nixpkgs/nixos/modules/virtualisation/google-compute-config.nix b/nixpkgs/nixos/modules/virtualisation/google-compute-config.nix
new file mode 100644
index 00000000000..327324f2921
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/google-compute-config.nix
@@ -0,0 +1,148 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ gce = pkgs.google-compute-engine;
+in
+{
+ imports = [
+ ../profiles/headless.nix
+ ../profiles/qemu-guest.nix
+ ];
+
+
+ fileSystems."/" = {
+ fsType = "ext4";
+ 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.
+ 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 SSH keys
+ # and disable password authentication in general
+ services.openssh.enable = true;
+ services.openssh.permitRootLogin = "prohibit-password";
+ services.openssh.passwordAuthentication = mkDefault false;
+
+ # enable OS Login. This also requires setting enable-oslogin=TRUE metadata on
+ # instance or project level
+ security.googleOsLogin.enable = true;
+
+ # Use GCE udev rules for dynamic disk volumes
+ services.udev.packages = [ gce ];
+
+ # 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;
+
+ systemd.services.google-instance-setup = {
+ description = "Google Compute Engine Instance Setup";
+ after = [ "network-online.target" "network.target" "rsyslog.service" ];
+ before = [ "sshd.service" ];
+ path = with pkgs; [ coreutils ethtool openssh ];
+ serviceConfig = {
+ ExecStart = "${gce}/bin/google_instance_setup";
+ StandardOutput="journal+console";
+ Type = "oneshot";
+ };
+ wantedBy = [ "sshd.service" "multi-user.target" ];
+ };
+
+ systemd.services.google-network-daemon = {
+ description = "Google Compute Engine Network Daemon";
+ after = [ "network-online.target" "network.target" "google-instance-setup.service" ];
+ path = with pkgs; [ iproute ];
+ serviceConfig = {
+ ExecStart = "${gce}/bin/google_network_daemon";
+ StandardOutput="journal+console";
+ Type="simple";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ systemd.services.google-clock-skew-daemon = {
+ description = "Google Compute Engine Clock Skew Daemon";
+ after = [ "network.target" "google-instance-setup.service" "google-network-daemon.service" ];
+ serviceConfig = {
+ ExecStart = "${gce}/bin/google_clock_skew_daemon";
+ StandardOutput="journal+console";
+ Type = "simple";
+ };
+ wantedBy = ["multi-user.target"];
+ };
+
+
+ systemd.services.google-shutdown-scripts = {
+ description = "Google Compute Engine Shutdown Scripts";
+ after = [
+ "network-online.target"
+ "network.target"
+ "rsyslog.service"
+ "google-instance-setup.service"
+ "google-network-daemon.service"
+ ];
+ serviceConfig = {
+ ExecStart = "${pkgs.coreutils}/bin/true";
+ ExecStop = "${gce}/bin/google_metadata_script_runner --script-type shutdown";
+ RemainAfterExit = true;
+ StandardOutput="journal+console";
+ TimeoutStopSec = "0";
+ Type = "oneshot";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ systemd.services.google-startup-scripts = {
+ description = "Google Compute Engine Startup Scripts";
+ after = [
+ "network-online.target"
+ "network.target"
+ "rsyslog.service"
+ "google-instance-setup.service"
+ "google-network-daemon.service"
+ ];
+ serviceConfig = {
+ ExecStart = "${gce}/bin/google_metadata_script_runner --script-type startup";
+ KillMode = "process";
+ StandardOutput = "journal+console";
+ Type = "oneshot";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ environment.etc."sysctl.d/11-gce-network-security.conf".source = "${gce}/sysctl.d/11-gce-network-security.conf";
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/google-compute-image.nix b/nixpkgs/nixos/modules/virtualisation/google-compute-image.nix
new file mode 100644
index 00000000000..d172ae38fdc
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/google-compute-image.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.virtualisation.googleComputeImage;
+ defaultConfigFile = pkgs.writeText "configuration.nix" ''
+ { ... }:
+ {
+ imports = [
+ <nixpkgs/nixos/modules/virtualisation/google-compute-image.nix>
+ ];
+ }
+ '';
+in
+{
+
+ 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 cfg.configFile == null then defaultConfigFile else cfg.configFile;
+ inherit (cfg) diskSize;
+ inherit config lib pkgs;
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/grow-partition.nix b/nixpkgs/nixos/modules/virtualisation/grow-partition.nix
new file mode 100644
index 00000000000..444c0bc1630
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/grow-partition.nix
@@ -0,0 +1,3 @@
+# This profile is deprecated, use boot.growPartition directly.
+builtins.trace "the profile <nixos/modules/virtualisation/grow-partition.nix> is deprecated, use boot.growPartition instead"
+{ }
diff --git a/nixpkgs/nixos/modules/virtualisation/hyperv-guest.nix b/nixpkgs/nixos/modules/virtualisation/hyperv-guest.nix
new file mode 100644
index 00000000000..0f1f052880c
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/hyperv-guest.nix
@@ -0,0 +1,64 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.virtualisation.hypervGuest;
+
+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 cpu/memory
+ services.udev.packages = lib.singleton (pkgs.writeTextFile {
+ name = "hyperv-cpu-and-memory-hotadd-udev-rules";
+ destination = "/etc/udev/rules.d/99-hyperv-cpu-and-memory-hotadd.rules";
+ text = ''
+ # 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"
+ '';
+ });
+
+ systemd = {
+ packages = [ config.boot.kernelPackages.hyperv-daemons.lib ];
+
+ targets.hyperv-daemons = {
+ wantedBy = [ "multi-user.target" ];
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/kvmgt.nix b/nixpkgs/nixos/modules/virtualisation/kvmgt.nix
new file mode 100644
index 00000000000..36ef6d17df6
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/kvmgt.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.virtualisation.kvmgt;
+
+ kernelPackages = config.boot.kernelPackages;
+
+ vgpuOptions = {
+ uuid = mkOption {
+ type = types.str;
+ description = "UUID of VGPU device. You can generate one with <package>libossp_uuid</package>.";
+ };
+ };
+
+in {
+ options = {
+ virtualisation.kvmgt = {
+ enable = mkEnableOption ''
+ KVMGT (iGVT-g) VGPU support. Allows Qemu/KVM guests to share host's Intel integrated graphics card.
+ Currently only one graphical device can be shared
+ '';
+ # multi GPU support is under the question
+ device = mkOption {
+ type = types.str;
+ default = "0000:00:02.0";
+ description = "PCI ID of graphics card. You can figure it with <command>ls /sys/class/mdev_bus</command>.";
+ };
+ vgpus = mkOption {
+ default = {};
+ type = with types; attrsOf (submodule [ { options = vgpuOptions; } ]);
+ description = ''
+ Virtual GPUs to be used in Qemu. You can find devices via <command>ls /sys/bus/pci/devices/*/mdev_supported_types</command>
+ and find info about device via <command>cat /sys/bus/pci/devices/*/mdev_supported_types/i915-GVTg_V5_4/description</command>
+ '';
+ example = {
+ i915-GVTg_V5_8 = {
+ uuid = "a297db4a-f4c2-11e6-90f6-d3b88d6c9525";
+ };
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = singleton {
+ assertion = versionAtLeast kernelPackages.kernel.version "4.16";
+ message = "KVMGT is not properly supported for kernels older than 4.16";
+ };
+
+ boot.kernelModules = [ "kvmgt" ];
+
+ boot.extraModprobeConfig = ''
+ options i915 enable_gvt=1
+ '';
+
+ systemd.paths = mapAttrs' (name: value:
+ nameValuePair "kvmgt-${name}" {
+ description = "KVMGT VGPU ${name} path";
+ wantedBy = [ "multi-user.target" ];
+ pathConfig = {
+ PathExists = "/sys/bus/pci/devices/${cfg.device}/mdev_supported_types/${name}/create";
+ };
+ }
+ ) cfg.vgpus;
+
+ systemd.services = mapAttrs' (name: value:
+ nameValuePair "kvmgt-${name}" {
+ description = "KVMGT VGPU ${name}";
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ 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'";
+ };
+ }
+ ) cfg.vgpus;
+ };
+
+ meta.maintainers = with maintainers; [ gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/libvirtd.nix b/nixpkgs/nixos/modules/virtualisation/libvirtd.nix
new file mode 100644
index 00000000000..16b79d86919
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/libvirtd.nix
@@ -0,0 +1,264 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.virtualisation.libvirtd;
+ vswitch = config.virtualisation.vswitch;
+ configFile = pkgs.writeText "libvirtd.conf" ''
+ unix_sock_group = "libvirtd"
+ unix_sock_rw_perms = "0770"
+ auth_unix_ro = "none"
+ auth_unix_rw = "none"
+ ${cfg.extraConfig}
+ '';
+ qemuConfigFile = pkgs.writeText "qemu.conf" ''
+ ${optionalString cfg.qemuOvmf ''
+ nvram = ["/run/libvirt/nix-ovmf/OVMF_CODE.fd:/run/libvirt/nix-ovmf/OVMF_VARS.fd"]
+ ''}
+ ${optionalString (!cfg.qemuRunAsRoot) ''
+ user = "qemu-libvirtd"
+ group = "qemu-libvirtd"
+ ''}
+ ${cfg.qemuVerbatimConfig}
+ '';
+ dirName = "libvirt";
+ subDirs = list: [ dirName ] ++ map (e: "${dirName}/${e}") list;
+
+in {
+
+ ###### interface
+
+ options.virtualisation.libvirtd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This option enables libvirtd, a daemon that manages
+ virtual machines. Users in the "libvirtd" group can interact with
+ the daemon (e.g. to start or stop VMs) using the
+ <command>virsh</command> command line tool, among others.
+ '';
+ };
+
+ qemuPackage = mkOption {
+ type = types.package;
+ default = pkgs.qemu;
+ description = ''
+ Qemu package to use with libvirt.
+ `pkgs.qemu` can emulate alien architectures (e.g. aarch64 on x86)
+ `pkgs.qemu_kvm` saves disk space allowing to emulate only host architectures.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra contents appended to the libvirtd configuration file,
+ libvirtd.conf.
+ '';
+ };
+
+ qemuRunAsRoot = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If true, libvirtd runs qemu as root.
+ If false, libvirtd runs qemu as unprivileged user qemu-libvirtd.
+ Changing this option to false may cause file permission issues
+ for existing guests. To fix these, manually change ownership
+ of affected files in /var/lib/libvirt/qemu to qemu-libvirtd.
+ '';
+ };
+
+ qemuVerbatimConfig = mkOption {
+ type = types.lines;
+ default = ''
+ namespaces = []
+ '';
+ description = ''
+ Contents written to the qemu configuration file, qemu.conf.
+ Make sure to include a proper namespace configuration when
+ supplying custom configuration.
+ '';
+ };
+
+ qemuOvmf = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Allows libvirtd to take advantage of OVMF when creating new
+ QEMU VMs with UEFI boot.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--verbose" ];
+ description = ''
+ Extra command line arguments passed to libvirtd on startup.
+ '';
+ };
+
+ onBoot = mkOption {
+ type = types.enum ["start" "ignore" ];
+ default = "start";
+ description = ''
+ Specifies the action to be done to / on the guests when the host boots.
+ The "start" option starts all guests that were running prior to shutdown
+ regardless of their autostart settings. The "ignore" option will not
+ start the formally running guest on boot. However, any guest marked as
+ autostart will still be automatically started by libvirtd.
+ '';
+ };
+
+ onShutdown = mkOption {
+ type = types.enum ["shutdown" "suspend" ];
+ default = "suspend";
+ description = ''
+ When shutting down / restarting the host what method should
+ be used to gracefully halt the guests. Setting to "shutdown"
+ will cause an ACPI shutdown of each guest. "suspend" will
+ attempt to save the state of the guests ready to restore on boot.
+ '';
+ };
+
+ allowedBridges = mkOption {
+ type = types.listOf types.str;
+ default = [ "virbr0" ];
+ description = ''
+ List of bridge devices that can be used by qemu:///session
+ '';
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment = {
+ # this file is expected in /etc/qemu and not sysconfdir (/var/lib)
+ etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n" (e:
+ "allow ${e}") cfg.allowedBridges;
+ systemPackages = with pkgs; [ libvirt libressl.nc cfg.qemuPackage ];
+ };
+
+ boot.kernelModules = [ "tun" ];
+
+ users.groups.libvirtd.gid = config.ids.gids.libvirtd;
+
+ # libvirtd runs qemu as this user and group by default
+ users.extraGroups.qemu-libvirtd.gid = config.ids.gids.qemu-libvirtd;
+ users.extraUsers.qemu-libvirtd = {
+ uid = config.ids.uids.qemu-libvirtd;
+ isNormalUser = false;
+ group = "qemu-libvirtd";
+ };
+
+ security.wrappers.qemu-bridge-helper = {
+ source = "/run/${dirName}/nix-helpers/qemu-bridge-helper";
+ };
+
+ systemd.packages = [ pkgs.libvirt ];
+
+ systemd.services.libvirtd-config = {
+ description = "Libvirt Virtual Machine Management Daemon - configuration";
+ script = ''
+ # Copy default libvirt network config .xml files to /var/lib
+ # Files modified by the user will not be overwritten
+ for i in $(cd ${pkgs.libvirt}/var/lib && echo \
+ libvirt/qemu/networks/*.xml libvirt/qemu/networks/autostart/*.xml \
+ libvirt/nwfilter/*.xml );
+ do
+ mkdir -p /var/lib/$(dirname $i) -m 755
+ cp -npd ${pkgs.libvirt}/var/lib/$i /var/lib/$i
+ done
+
+ # Copy generated qemu config to libvirt directory
+ cp -f ${qemuConfigFile} /var/lib/${dirName}/qemu.conf
+
+ # stable (not GC'able as in /nix/store) paths for using in <emulator> section of xml configs
+ for emulator in ${pkgs.libvirt}/libexec/libvirt_lxc ${cfg.qemuPackage}/bin/qemu-kvm ${cfg.qemuPackage}/bin/qemu-system-*; do
+ ln -s --force "$emulator" /run/${dirName}/nix-emulators/
+ done
+
+ for helper in libexec/qemu-bridge-helper bin/qemu-pr-helper; do
+ ln -s --force ${cfg.qemuPackage}/$helper /run/${dirName}/nix-helpers/
+ done
+
+ ${optionalString cfg.qemuOvmf ''
+ ln -s --force ${pkgs.OVMF.fd}/FV/OVMF_CODE.fd /run/${dirName}/nix-ovmf/
+ ln -s --force ${pkgs.OVMF.fd}/FV/OVMF_VARS.fd /run/${dirName}/nix-ovmf/
+ ''}
+ '';
+
+ serviceConfig = {
+ Type = "oneshot";
+ RuntimeDirectoryPreserve = "yes";
+ LogsDirectory = subDirs [ "qemu" ];
+ RuntimeDirectory = subDirs [ "nix-emulators" "nix-helpers" "nix-ovmf" ];
+ StateDirectory = subDirs [ "dnsmasq" ];
+ };
+ };
+
+ systemd.services.libvirtd = {
+ description = "Libvirt Virtual Machine Management Daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "libvirtd-config.service" ];
+ after = [ "systemd-udev-settle.service" "libvirtd-config.service" ]
+ ++ optional vswitch.enable "vswitchd.service";
+
+ environment.LIBVIRTD_ARGS = ''--config "${configFile}" ${concatStringsSep " " cfg.extraOptions}'';
+
+ path = [ cfg.qemuPackage ] # libvirtd requires qemu-img to manage disk images
+ ++ optional vswitch.enable vswitch.package;
+
+ serviceConfig = {
+ Type = "notify";
+ KillMode = "process"; # when stopping, leave the VMs alone
+ Restart = "no";
+ };
+ restartIfChanged = false;
+ };
+
+ systemd.services.libvirt-guests = {
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [ coreutils libvirt gawk ];
+ restartIfChanged = false;
+
+ environment.ON_BOOT = "${cfg.onBoot}";
+ environment.ON_SHUTDOWN = "${cfg.onShutdown}";
+ };
+
+ systemd.sockets.virtlogd = {
+ description = "Virtual machine log manager socket";
+ wantedBy = [ "sockets.target" ];
+ listenStreams = [ "/run/${dirName}/virtlogd-sock" ];
+ };
+
+ systemd.services.virtlogd = {
+ description = "Virtual machine log manager";
+ serviceConfig.ExecStart = "@${pkgs.libvirt}/sbin/virtlogd virtlogd";
+ restartIfChanged = false;
+ };
+
+ systemd.sockets.virtlockd = {
+ description = "Virtual machine lock manager socket";
+ wantedBy = [ "sockets.target" ];
+ listenStreams = [ "/run/${dirName}/virtlockd-sock" ];
+ };
+
+ systemd.services.virtlockd = {
+ description = "Virtual machine lock manager";
+ serviceConfig.ExecStart = "@${pkgs.libvirt}/sbin/virtlockd virtlockd";
+ restartIfChanged = false;
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/lxc-container.nix b/nixpkgs/nixos/modules/virtualisation/lxc-container.nix
new file mode 100644
index 00000000000..d4936484018
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/lxc-container.nix
@@ -0,0 +1,26 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ imports = [
+ ../profiles/docker-container.nix # FIXME, shouldn't include something from profiles/
+ ];
+
+ # Allow the user to login as root without password.
+ users.users.root.initialHashedPassword = mkOverride 150 "";
+
+ # Some more help text.
+ services.mingetty.helpLine =
+ ''
+
+ Log in as "root" with an empty password.
+ '';
+
+ # Containers should be light-weight, so start sshd on demand.
+ services.openssh.enable = mkDefault true;
+ services.openssh.startWhenNeeded = mkDefault true;
+
+ # Allow ssh connections
+ networking.firewall.allowedTCPPorts = [ 22 ];
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/lxc.nix b/nixpkgs/nixos/modules/virtualisation/lxc.nix
new file mode 100644
index 00000000000..9b5adaf0824
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/lxc.nix
@@ -0,0 +1,82 @@
+# LXC Configuration
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.virtualisation.lxc;
+
+in
+
+{
+ ###### interface
+
+ options.virtualisation.lxc = {
+ enable =
+ mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ ''
+ This enables Linux Containers (LXC), which provides tools
+ for creating and managing system or application containers
+ on Linux.
+ '';
+ };
+
+ systemConfig =
+ mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ ''
+ This is the system-wide LXC config. See
+ <citerefentry><refentrytitle>lxc.system.conf</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry>.
+ '';
+ };
+
+ defaultConfig =
+ mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ ''
+ Default config (default.conf) for new containers, i.e. for
+ network config. See <citerefentry><refentrytitle>lxc.container.conf
+ </refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ '';
+ };
+
+ usernetConfig =
+ mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ ''
+ This is the config file for managing unprivileged user network
+ administration access in LXC. See <citerefentry>
+ <refentrytitle>lxc-user-net</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>.
+ '';
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.lxc ];
+ environment.etc."lxc/lxc.conf".text = cfg.systemConfig;
+ environment.etc."lxc/lxc-usernet".text = cfg.usernetConfig;
+ environment.etc."lxc/default.conf".text = cfg.defaultConfig;
+ systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
+
+ security.apparmor.packages = [ pkgs.lxc ];
+ security.apparmor.profiles = [
+ "${pkgs.lxc}/etc/apparmor.d/lxc-containers"
+ "${pkgs.lxc}/etc/apparmor.d/usr.bin.lxc-start"
+ ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/lxcfs.nix b/nixpkgs/nixos/modules/virtualisation/lxcfs.nix
new file mode 100644
index 00000000000..b2457403463
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/lxcfs.nix
@@ -0,0 +1,45 @@
+# LXC Configuration
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.virtualisation.lxc.lxcfs;
+in {
+ meta.maintainers = [ maintainers.mic92 ];
+
+ ###### interface
+ options.virtualisation.lxc.lxcfs = {
+ enable =
+ mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This enables LXCFS, a FUSE filesystem for LXC.
+ To use lxcfs in include the following configuration in your
+ container configuration:
+ <code>
+ virtualisation.lxc.defaultConfig = "lxc.include = ''${pkgs.lxcfs}/share/lxc/config/common.conf.d/00-lxcfs.conf";
+ </code>
+ '';
+ };
+ };
+
+ ###### implementation
+ config = mkIf cfg.enable {
+ systemd.services.lxcfs = {
+ description = "FUSE filesystem for LXC";
+ wantedBy = [ "multi-user.target" ];
+ before = [ "lxc.service" ];
+ restartIfChanged = false;
+ serviceConfig = {
+ ExecStartPre="${pkgs.coreutils}/bin/mkdir -p /var/lib/lxcfs";
+ ExecStart="${pkgs.lxcfs}/bin/lxcfs /var/lib/lxcfs";
+ ExecStopPost="-${pkgs.fuse}/bin/fusermount -u /var/lib/lxcfs";
+ KillMode="process";
+ Restart="on-failure";
+ };
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/lxd.nix b/nixpkgs/nixos/modules/virtualisation/lxd.nix
new file mode 100644
index 00000000000..505c11abd20
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/lxd.nix
@@ -0,0 +1,83 @@
+# Systemd services for lxd.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.virtualisation.lxd;
+
+in
+
+{
+ ###### interface
+
+ options = {
+
+ virtualisation.lxd = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This option enables lxd, a daemon that manages
+ containers. Users in the "lxd" group can interact with
+ the daemon (e.g. to start or stop containers) using the
+ <command>lxc</command> command line tool, among others.
+ '';
+ };
+ zfsSupport = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ enables lxd to use zfs as a storage for containers.
+ This option is enabled by default if a zfs pool is configured
+ with nixos.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.lxd ];
+
+ security.apparmor = {
+ enable = true;
+ profiles = [
+ "${pkgs.lxc}/etc/apparmor.d/usr.bin.lxc-start"
+ "${pkgs.lxc}/etc/apparmor.d/lxc-containers"
+ ];
+ packages = [ pkgs.lxc ];
+ };
+
+ systemd.services.lxd = {
+ description = "LXD Container Management Daemon";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "systemd-udev-settle.service" ];
+
+ path = lib.optional cfg.zfsSupport pkgs.zfs;
+
+ preStart = ''
+ mkdir -m 0755 -p /var/lib/lxc/rootfs
+ '';
+
+ serviceConfig = {
+ ExecStart = "@${pkgs.lxd.bin}/bin/lxd lxd --group lxd";
+ Type = "simple";
+ KillMode = "process"; # when stopping, leave the containers alone
+ };
+
+ };
+
+ users.groups.lxd.gid = config.ids.gids.lxd;
+
+ users.users.root = {
+ subUidRanges = [ { startUid = 1000000; count = 65536; } ];
+ subGidRanges = [ { startGid = 1000000; count = 65536; } ];
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/openstack-config.nix b/nixpkgs/nixos/modules/virtualisation/openstack-config.nix
new file mode 100644
index 00000000000..c2da5d0d230
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/openstack-config.nix
@@ -0,0 +1,58 @@
+{ 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";
+ fsType = "ext4";
+ 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/nixpkgs/nixos/modules/virtualisation/openvswitch.nix b/nixpkgs/nixos/modules/virtualisation/openvswitch.nix
new file mode 100644
index 00000000000..edec3740230
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/openvswitch.nix
@@ -0,0 +1,176 @@
+# Systemd services for openvswitch
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.virtualisation.vswitch;
+
+in {
+
+ options.virtualisation.vswitch = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Open vSwitch. A configuration daemon (ovs-server)
+ will be started.
+ '';
+ };
+
+ resetOnStart = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to reset the Open vSwitch configuration database to a default
+ configuration on every start of the systemd <literal>ovsdb.service</literal>.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.openvswitch;
+ defaultText = "pkgs.openvswitch";
+ description = ''
+ Open vSwitch package to use.
+ '';
+ };
+
+ ipsec = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to start racoon service for openvswitch.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable (let
+
+ # Where the communication sockets live
+ runDir = "/run/openvswitch";
+
+ # The path to the an initialized version of the database
+ db = pkgs.stdenv.mkDerivation {
+ name = "vswitch.db";
+ dontUnpack = true;
+ buildPhase = "true";
+ buildInputs = with pkgs; [
+ cfg.package
+ ];
+ installPhase = "mkdir -p $out";
+ };
+
+ in (mkMerge [{
+
+ environment.systemPackages = [ cfg.package pkgs.ipsecTools ];
+
+ boot.kernelModules = [ "tun" "openvswitch" ];
+
+ boot.extraModulePackages = [ cfg.package ];
+
+ systemd.services.ovsdb = {
+ description = "Open_vSwitch Database Server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "systemd-udev-settle.service" ];
+ path = [ cfg.package ];
+ restartTriggers = [ db cfg.package ];
+ # Create the config database
+ preStart =
+ ''
+ mkdir -p ${runDir}
+ mkdir -p /var/db/openvswitch
+ chmod +w /var/db/openvswitch
+ ${optionalString cfg.resetOnStart "rm -f /var/db/openvswitch/conf.db"}
+ if [[ ! -e /var/db/openvswitch/conf.db ]]; then
+ ${cfg.package}/bin/ovsdb-tool create \
+ "/var/db/openvswitch/conf.db" \
+ "${cfg.package}/share/openvswitch/vswitch.ovsschema"
+ fi
+ chmod -R +w /var/db/openvswitch
+ '';
+ serviceConfig = {
+ ExecStart =
+ ''
+ ${cfg.package}/bin/ovsdb-server \
+ --remote=punix:${runDir}/db.sock \
+ --private-key=db:Open_vSwitch,SSL,private_key \
+ --certificate=db:Open_vSwitch,SSL,certificate \
+ --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert \
+ --unixctl=ovsdb.ctl.sock \
+ --pidfile=/run/openvswitch/ovsdb.pid \
+ --detach \
+ /var/db/openvswitch/conf.db
+ '';
+ Restart = "always";
+ RestartSec = 3;
+ PIDFile = "/run/openvswitch/ovsdb.pid";
+ # Use service type 'forking' to correctly determine when ovsdb-server is ready.
+ Type = "forking";
+ };
+ postStart = ''
+ ${cfg.package}/bin/ovs-vsctl --timeout 3 --retry --no-wait init
+ '';
+ };
+
+ systemd.services.vswitchd = {
+ description = "Open_vSwitch Daemon";
+ wantedBy = [ "multi-user.target" ];
+ bindsTo = [ "ovsdb.service" ];
+ after = [ "ovsdb.service" ];
+ path = [ cfg.package ];
+ serviceConfig = {
+ ExecStart = ''
+ ${cfg.package}/bin/ovs-vswitchd \
+ --pidfile=/run/openvswitch/ovs-vswitchd.pid \
+ --detach
+ '';
+ PIDFile = "/run/openvswitch/ovs-vswitchd.pid";
+ # Use service type 'forking' to correctly determine when vswitchd is ready.
+ Type = "forking";
+ };
+ };
+
+ }
+ (mkIf cfg.ipsec {
+ services.racoon.enable = true;
+ services.racoon.configPath = "${runDir}/ipsec/etc/racoon/racoon.conf";
+
+ networking.firewall.extraCommands = ''
+ iptables -I INPUT -t mangle -p esp -j MARK --set-mark 1/1
+ iptables -I INPUT -t mangle -p udp --dport 4500 -j MARK --set-mark 1/1
+ '';
+
+ systemd.services.ovs-monitor-ipsec = {
+ description = "Open_vSwitch Ipsec Daemon";
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "ovsdb.service" ];
+ before = [ "vswitchd.service" "racoon.service" ];
+ environment.UNIXCTLPATH = "/tmp/ovsdb.ctl.sock";
+ serviceConfig = {
+ ExecStart = ''
+ ${cfg.package}/bin/ovs-monitor-ipsec \
+ --root-prefix ${runDir}/ipsec \
+ --pidfile /run/openvswitch/ovs-monitor-ipsec.pid \
+ --monitor --detach \
+ unix:/run/openvswitch/db.sock
+ '';
+ PIDFile = "/run/openvswitch/ovs-monitor-ipsec.pid";
+ # Use service type 'forking' to correctly determine when ovs-monitor-ipsec is ready.
+ Type = "forking";
+ };
+
+ preStart = ''
+ rm -r ${runDir}/ipsec/etc/racoon/certs || true
+ mkdir -p ${runDir}/ipsec/{etc/racoon,etc/init.d/,usr/sbin/}
+ ln -fs ${pkgs.ipsecTools}/bin/setkey ${runDir}/ipsec/usr/sbin/setkey
+ ln -fs ${pkgs.writeScript "racoon-restart" ''
+ #!${pkgs.runtimeShell}
+ /run/current-system/sw/bin/systemctl $1 racoon
+ ''} ${runDir}/ipsec/etc/init.d/racoon
+ '';
+ };
+ })]));
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/parallels-guest.nix b/nixpkgs/nixos/modules/virtualisation/parallels-guest.nix
new file mode 100644
index 00000000000..828419fb4b9
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/parallels-guest.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ prl-tools = config.hardware.parallels.package;
+in
+
+{
+
+ options = {
+ hardware.parallels = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This enables Parallels Tools for Linux guests, along with provided
+ video, mouse and other hardware drivers.
+ '';
+ };
+
+ autoMountShares = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Control prlfsmountd service. When this service is running, shares can not be manually
+ mounted through `mount -t prl_fs ...` as this service will remount and trample any set options.
+ Recommended to enable for simple file sharing, but extended share use such as for code should
+ disable this to manually mount shares.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = config.boot.kernelPackages.prl-tools;
+ defaultText = "config.boot.kernelPackages.prl-tools";
+ example = literalExample "config.boot.kernelPackages.prl-tools";
+ description = ''
+ Defines which package to use for prl-tools. Override to change the version.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf config.hardware.parallels.enable {
+ services.xserver = {
+ drivers = singleton
+ { name = "prlvideo"; modules = [ prl-tools ]; };
+
+ screenSection = ''
+ Option "NoMTRR"
+ '';
+
+ config = ''
+ Section "InputClass"
+ Identifier "prlmouse"
+ MatchIsPointer "on"
+ MatchTag "prlmouse"
+ Driver "prlmouse"
+ EndSection
+ '';
+ };
+
+ hardware.opengl.package = prl-tools;
+ hardware.opengl.package32 = pkgs.pkgsi686Linux.linuxPackages.prl-tools.override { libsOnly = true; kernel = null; };
+ hardware.opengl.setLdLibraryPath = true;
+
+ services.udev.packages = [ prl-tools ];
+
+ environment.systemPackages = [ prl-tools ];
+
+ boot.extraModulePackages = [ prl-tools ];
+
+ boot.kernelModules = [ "prl_tg" "prl_eth" "prl_fs" "prl_fs_freeze" ];
+
+ services.timesyncd.enable = false;
+
+ systemd.services.prltoolsd = {
+ description = "Parallels Tools' service";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${prl-tools}/bin/prltoolsd -f";
+ PIDFile = "/var/run/prltoolsd.pid";
+ };
+ };
+
+ systemd.services.prlfsmountd = mkIf config.hardware.parallels.autoMountShares {
+ description = "Parallels Shared Folders Daemon";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = rec {
+ ExecStart = "${prl-tools}/sbin/prlfsmountd ${PIDFile}";
+ ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /media";
+ ExecStopPost = "${prl-tools}/sbin/prlfsmountd -u";
+ PIDFile = "/run/prlfsmountd.pid";
+ };
+ };
+
+ systemd.services.prlshprint = {
+ description = "Parallels Shared Printer Tool";
+ wantedBy = [ "multi-user.target" ];
+ bindsTo = [ "cups.service" ];
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${prl-tools}/bin/prlshprint";
+ };
+ };
+
+ systemd.user.services = {
+ prlcc = {
+ description = "Parallels Control Center";
+ wantedBy = [ "graphical-session.target" ];
+ serviceConfig = {
+ ExecStart = "${prl-tools}/bin/prlcc";
+ };
+ };
+ prldnd = {
+ description = "Parallels Control Center";
+ wantedBy = [ "graphical-session.target" ];
+ serviceConfig = {
+ ExecStart = "${prl-tools}/bin/prldnd";
+ };
+ };
+ prl_wmouse_d = {
+ description = "Parallels Walking Mouse Daemon";
+ wantedBy = [ "graphical-session.target" ];
+ serviceConfig = {
+ ExecStart = "${prl-tools}/bin/prl_wmouse_d";
+ };
+ };
+ prlcp = {
+ description = "Parallels CopyPaste Tool";
+ wantedBy = [ "graphical-session.target" ];
+ serviceConfig = {
+ ExecStart = "${prl-tools}/bin/prlcp";
+ };
+ };
+ prlsga = {
+ description = "Parallels Shared Guest Applications Tool";
+ wantedBy = [ "graphical-session.target" ];
+ serviceConfig = {
+ ExecStart = "${prl-tools}/bin/prlsga";
+ };
+ };
+ prlshprof = {
+ description = "Parallels Shared Profile Tool";
+ wantedBy = [ "graphical-session.target" ];
+ serviceConfig = {
+ ExecStart = "${prl-tools}/bin/prlshprof";
+ };
+ };
+ };
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/qemu-guest-agent.nix b/nixpkgs/nixos/modules/virtualisation/qemu-guest-agent.nix
new file mode 100644
index 00000000000..665224e35d8
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/qemu-guest-agent.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.qemuGuest;
+in {
+
+ options.services.qemuGuest = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the qemu guest agent.";
+ };
+ };
+
+ config = mkIf cfg.enable (
+ mkMerge [
+ {
+
+ services.udev.extraRules = ''
+ SUBSYSTEM=="virtio-ports", ATTR{name}=="org.qemu.guest_agent.0", TAG+="systemd" ENV{SYSTEMD_WANTS}="qemu-guest-agent.service"
+ '';
+
+ systemd.services.qemu-guest-agent = {
+ description = "Run the QEMU Guest Agent";
+ serviceConfig = {
+ ExecStart = "${pkgs.qemu.ga}/bin/qemu-ga";
+ Restart = "always";
+ RestartSec = 0;
+ };
+ };
+ }
+ ]
+ );
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix b/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix
new file mode 100644
index 00000000000..ed3431554be
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix
@@ -0,0 +1,585 @@
+# This module creates a virtual machine from the NixOS configuration.
+# Building the `config.system.build.vm' attribute gives you a command
+# that starts a KVM/QEMU VM running the NixOS configuration defined in
+# `config'. The Nix store is shared read-only with the host, which
+# makes (re)building VMs very efficient. However, it also means you
+# can't reconfigure the guest inside the guest - you need to rebuild
+# the VM in the host. On the other hand, the root filesystem is a
+# read/writable disk image persistent across VM reboots.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+with import ../../lib/qemu-flags.nix { inherit pkgs; };
+
+let
+
+ qemu = config.system.build.qemu or pkgs.qemu_test;
+
+ vmName =
+ if config.networking.hostName == ""
+ then "noname"
+ else config.networking.hostName;
+
+ cfg = config.virtualisation;
+
+ qemuGraphics = lib.optionalString (!cfg.graphics) "-nographic";
+
+ consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles;
+
+ # XXX: This is very ugly and in the future we really should use attribute
+ # sets to build ALL of the QEMU flags instead of this mixed mess of Nix
+ # expressions and shell script stuff.
+ mkDiskIfaceDriveFlag = idx: driveArgs: let
+ inherit (cfg.qemu) diskInterface;
+ # The drive identifier created by incrementing the index by one using the
+ # shell.
+ drvId = "drive$((${idx} + 1))";
+ # NOTE: DO NOT shell escape, because this may contain shell variables.
+ commonArgs = "index=${idx},id=${drvId},${driveArgs}";
+ isSCSI = diskInterface == "scsi";
+ devArgs = "${diskInterface}-hd,drive=${drvId}";
+ args = "-drive ${commonArgs},if=none -device lsi53c895a -device ${devArgs}";
+ in if isSCSI then args else "-drive ${commonArgs},if=${diskInterface}";
+
+ # Shell script to start the VM.
+ startVM =
+ ''
+ #! ${pkgs.runtimeShell}
+
+ NIX_DISK_IMAGE=$(readlink -f ''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}})
+
+ if ! test -e "$NIX_DISK_IMAGE"; then
+ ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \
+ ${toString config.virtualisation.diskSize}M || exit 1
+ fi
+
+ # Create a directory for storing temporary data of the running VM.
+ if [ -z "$TMPDIR" -o -z "$USE_TMPDIR" ]; then
+ TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir)
+ fi
+
+ # Create a directory for exchanging data with the VM.
+ mkdir -p $TMPDIR/xchg
+
+ ${if cfg.useBootLoader then ''
+ # Create a writable copy/snapshot of the boot disk.
+ # A writable boot disk can be booted from automatically.
+ ${qemu}/bin/qemu-img create -f qcow2 -b ${bootDisk}/disk.img $TMPDIR/disk.img || exit 1
+
+ ${if cfg.useEFIBoot then ''
+ # VM needs a writable flash BIOS.
+ cp ${bootDisk}/bios.bin $TMPDIR || exit 1
+ chmod 0644 $TMPDIR/bios.bin || exit 1
+ '' else ''
+ ''}
+ '' else ''
+ ''}
+
+ cd $TMPDIR
+ idx=2
+ extraDisks=""
+ ${flip concatMapStrings cfg.emptyDiskImages (size: ''
+ if ! test -e "empty$idx.qcow2"; then
+ ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
+ fi
+ extraDisks="$extraDisks ${mkDiskIfaceDriveFlag "$idx" "file=$(pwd)/empty$idx.qcow2,werror=report"}"
+ idx=$((idx + 1))
+ '')}
+
+ # Start QEMU.
+ exec ${qemuBinary qemu} \
+ -name ${vmName} \
+ -m ${toString config.virtualisation.memorySize} \
+ -smp ${toString config.virtualisation.cores} \
+ -device virtio-rng-pci \
+ ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \
+ -virtfs local,path=/nix/store,security_model=none,mount_tag=store \
+ -virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \
+ -virtfs local,path=''${SHARED_DIR:-$TMPDIR/xchg},security_model=none,mount_tag=shared \
+ ${if cfg.useBootLoader then ''
+ ${mkDiskIfaceDriveFlag "0" "file=$NIX_DISK_IMAGE,cache=writeback,werror=report"} \
+ ${mkDiskIfaceDriveFlag "1" "file=$TMPDIR/disk.img,media=disk"} \
+ ${if cfg.useEFIBoot then ''
+ -pflash $TMPDIR/bios.bin \
+ '' else ''
+ ''}
+ '' else ''
+ ${mkDiskIfaceDriveFlag "0" "file=$NIX_DISK_IMAGE,cache=writeback,werror=report"} \
+ -kernel ${config.system.build.toplevel}/kernel \
+ -initrd ${config.system.build.toplevel}/initrd \
+ -append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS" \
+ ''} \
+ $extraDisks \
+ ${qemuGraphics} \
+ ${toString config.virtualisation.qemu.options} \
+ $QEMU_OPTS \
+ "$@"
+ '';
+
+
+ regInfo = pkgs.closureInfo { rootPaths = config.virtualisation.pathsInNixDB; };
+
+
+ # Generate a hard disk image containing a /boot partition and GRUB
+ # in the MBR. Used when the `useBootLoader' option is set.
+ # FIXME: use nixos/lib/make-disk-image.nix.
+ bootDisk =
+ pkgs.vmTools.runInLinuxVM (
+ pkgs.runCommand "nixos-boot-disk"
+ { preVM =
+ ''
+ mkdir $out
+ diskImage=$out/disk.img
+ bootFlash=$out/bios.bin
+ ${qemu}/bin/qemu-img create -f qcow2 $diskImage "40M"
+ ${if cfg.useEFIBoot then ''
+ cp ${pkgs.OVMF-CSM.fd}/FV/OVMF.fd $bootFlash
+ chmod 0644 $bootFlash
+ '' else ''
+ ''}
+ '';
+ buildInputs = [ pkgs.utillinux ];
+ QEMU_OPTS = if cfg.useEFIBoot
+ then "-pflash $out/bios.bin -nographic -serial pty"
+ else "-nographic -serial pty";
+ }
+ ''
+ # Create a /boot EFI partition with 40M and arbitrary but fixed GUIDs for reproducibility
+ ${pkgs.gptfdisk}/bin/sgdisk \
+ --set-alignment=1 --new=1:34:2047 --change-name=1:BIOSBootPartition --typecode=1:ef02 \
+ --set-alignment=512 --largest-new=2 --change-name=2:EFISystem --typecode=2:ef00 \
+ --attributes=1:set:1 \
+ --attributes=2:set:2 \
+ --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C1 \
+ --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
+ --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
+ --hybrid 2 \
+ --recompute-chs /dev/vda
+ ${pkgs.dosfstools}/bin/mkfs.fat -F16 /dev/vda2
+ export MTOOLS_SKIP_CHECK=1
+ ${pkgs.mtools}/bin/mlabel -i /dev/vda2 ::boot
+
+ # Mount /boot; load necessary modules first.
+ ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/nls/nls_cp437.ko.xz || true
+ ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/nls/nls_iso8859-1.ko.xz || true
+ ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/fat/fat.ko.xz || true
+ ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/fat/vfat.ko.xz || true
+ ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/efivarfs/efivarfs.ko.xz || true
+ mkdir /boot
+ mount /dev/vda2 /boot
+
+ # This is needed for GRUB 0.97, which doesn't know about virtio devices.
+ mkdir /boot/grub
+ echo '(hd0) /dev/vda' > /boot/grub/device.map
+
+ # Install GRUB and generate the GRUB boot menu.
+ touch /etc/NIXOS
+ mkdir -p /nix/var/nix/profiles
+ ${config.system.build.toplevel}/bin/switch-to-configuration boot
+
+ umount /boot
+ '' # */
+ );
+
+in
+
+{
+ imports = [
+ ../profiles/qemu-guest.nix
+ ./docker-preloader.nix
+ ];
+
+ options = {
+
+ virtualisation.memorySize =
+ mkOption {
+ default = 384;
+ description =
+ ''
+ Memory size (M) of virtual machine.
+ '';
+ };
+
+ virtualisation.diskSize =
+ mkOption {
+ default = 512;
+ description =
+ ''
+ Disk size (M) of virtual machine.
+ '';
+ };
+
+ virtualisation.diskImage =
+ mkOption {
+ default = "./${vmName}.qcow2";
+ description =
+ ''
+ Path to the disk image containing the root filesystem.
+ The image will be created on startup if it does not
+ exist.
+ '';
+ };
+
+ virtualisation.bootDevice =
+ mkOption {
+ type = types.str;
+ example = "/dev/vda";
+ description =
+ ''
+ The disk to be used for the root filesystem.
+ '';
+ };
+
+ virtualisation.emptyDiskImages =
+ mkOption {
+ default = [];
+ type = types.listOf types.int;
+ description =
+ ''
+ Additional disk images to provide to the VM. The value is
+ a list of size in megabytes of each disk. These disks are
+ writeable by the VM.
+ '';
+ };
+
+ virtualisation.graphics =
+ mkOption {
+ default = true;
+ description =
+ ''
+ Whether to run QEMU with a graphics window, or in nographic mode.
+ Serial console will be enabled on both settings, but this will
+ change the preferred console.
+ '';
+ };
+
+ virtualisation.cores =
+ mkOption {
+ default = 1;
+ type = types.int;
+ description =
+ ''
+ Specify the number of cores the guest is permitted to use.
+ The number can be higher than the available cores on the
+ host system.
+ '';
+ };
+
+ virtualisation.pathsInNixDB =
+ mkOption {
+ default = [];
+ description =
+ ''
+ The list of paths whose closure is registered in the Nix
+ database in the VM. All other paths in the host Nix store
+ appear in the guest Nix store as well, but are considered
+ garbage (because they are not registered in the Nix
+ database in the guest).
+ '';
+ };
+
+ virtualisation.vlans =
+ mkOption {
+ default = [ 1 ];
+ example = [ 1 2 ];
+ description =
+ ''
+ Virtual networks to which the VM is connected. Each
+ number <replaceable>N</replaceable> in this list causes
+ the VM to have a virtual Ethernet interface attached to a
+ separate virtual network on which it will be assigned IP
+ address
+ <literal>192.168.<replaceable>N</replaceable>.<replaceable>M</replaceable></literal>,
+ where <replaceable>M</replaceable> is the index of this VM
+ in the list of VMs.
+ '';
+ };
+
+ virtualisation.writableStore =
+ mkOption {
+ default = true; # FIXME
+ description =
+ ''
+ If enabled, the Nix store in the VM is made writable by
+ layering an overlay filesystem on top of the host's Nix
+ store.
+ '';
+ };
+
+ virtualisation.writableStoreUseTmpfs =
+ mkOption {
+ default = true;
+ description =
+ ''
+ Use a tmpfs for the writable store instead of writing to the VM's
+ own filesystem.
+ '';
+ };
+
+ networking.primaryIPAddress =
+ mkOption {
+ default = "";
+ internal = true;
+ description = "Primary IP address used in /etc/hosts.";
+ };
+
+ virtualisation.qemu = {
+ options =
+ mkOption {
+ type = types.listOf types.unspecified;
+ default = [];
+ example = [ "-vga std" ];
+ description = "Options passed to QEMU.";
+ };
+
+ consoles = mkOption {
+ type = types.listOf types.str;
+ default = let
+ consoles = [ "${qemuSerialDevice},115200n8" "tty0" ];
+ in if cfg.graphics then consoles else reverseList consoles;
+ example = [ "console=tty1" ];
+ description = ''
+ The output console devices to pass to the kernel command line via the
+ <literal>console</literal> parameter, the primary console is the last
+ item of this list.
+
+ By default it enables both serial console and
+ <literal>tty0</literal>. The preferred console (last one) is based on
+ the value of <option>virtualisation.graphics</option>.
+ '';
+ };
+
+ networkingOptions =
+ mkOption {
+ default = [
+ "-net nic,netdev=user.0,model=virtio"
+ "-netdev user,id=user.0\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
+ ];
+ type = types.listOf types.str;
+ description = ''
+ Networking-related command-line options that should be passed to qemu.
+ The default is to use userspace networking (slirp).
+
+ If you override this option, be advised to keep
+ ''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} (as seen in the default)
+ to keep the default runtime behaviour.
+ '';
+ };
+
+ diskInterface =
+ mkOption {
+ default = "virtio";
+ example = "scsi";
+ type = types.enum [ "virtio" "scsi" "ide" ];
+ description = "The interface used for the virtual hard disks.";
+ };
+
+ guestAgent.enable =
+ mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Enable the Qemu guest agent.
+ '';
+ };
+ };
+
+ virtualisation.useBootLoader =
+ mkOption {
+ default = false;
+ description =
+ ''
+ If enabled, the virtual machine will be booted using the
+ regular boot loader (i.e., GRUB 1 or 2). This allows
+ testing of the boot loader. If
+ disabled (the default), the VM directly boots the NixOS
+ kernel and initial ramdisk, bypassing the boot loader
+ altogether.
+ '';
+ };
+
+ virtualisation.useEFIBoot =
+ mkOption {
+ default = false;
+ description =
+ ''
+ If enabled, the virtual machine will provide a EFI boot
+ manager.
+ useEFIBoot is ignored if useBootLoader == false.
+ '';
+ };
+
+ };
+
+ config = {
+
+ boot.loader.grub.device = mkVMOverride cfg.bootDevice;
+
+ boot.initrd.extraUtilsCommands =
+ ''
+ # We need mke2fs in the initrd.
+ copy_bin_and_libs ${pkgs.e2fsprogs}/bin/mke2fs
+ '';
+
+ boot.initrd.postDeviceCommands =
+ ''
+ # If the disk image appears to be empty, run mke2fs to
+ # initialise.
+ FSTYPE=$(blkid -o value -s TYPE ${cfg.bootDevice} || true)
+ if test -z "$FSTYPE"; then
+ mke2fs -t ext4 ${cfg.bootDevice}
+ fi
+ '';
+
+ boot.initrd.postMountCommands =
+ ''
+ # Mark this as a NixOS machine.
+ mkdir -p $targetRoot/etc
+ echo -n > $targetRoot/etc/NIXOS
+
+ # Fix the permissions on /tmp.
+ chmod 1777 $targetRoot/tmp
+
+ mkdir -p $targetRoot/boot
+
+ ${optionalString cfg.writableStore ''
+ echo "mounting overlay filesystem on /nix/store..."
+ mkdir -p 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store
+ mount -t overlay overlay $targetRoot/nix/store \
+ -o lowerdir=$targetRoot/nix/.ro-store,upperdir=$targetRoot/nix/.rw-store/store,workdir=$targetRoot/nix/.rw-store/work || fail
+ ''}
+ '';
+
+ # After booting, register the closure of the paths in
+ # `virtualisation.pathsInNixDB' in the Nix database in the VM. This
+ # allows Nix operations to work in the VM. The path to the
+ # registration file is passed through the kernel command line to
+ # allow `system.build.toplevel' to be included. (If we had a direct
+ # reference to ${regInfo} here, then we would get a cyclic
+ # dependency.)
+ boot.postBootCommands =
+ ''
+ if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then
+ ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]}
+ fi
+ '';
+
+ boot.initrd.availableKernelModules =
+ optional cfg.writableStore "overlay"
+ ++ optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx";
+
+ virtualisation.bootDevice =
+ mkDefault (if cfg.qemu.diskInterface == "scsi" then "/dev/sda" else "/dev/vda");
+
+ virtualisation.pathsInNixDB = [ config.system.build.toplevel ];
+
+ # FIXME: Consolidate this one day.
+ virtualisation.qemu.options = mkMerge [
+ (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ "-vga std" "-usb" "-device usb-tablet,bus=usb-bus.0" ])
+ (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [ "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" ])
+ ];
+
+ # Mount the host filesystem via 9P, and bind-mount the Nix store
+ # of the host into our own filesystem. We use mkVMOverride to
+ # allow this module to be applied to "normal" NixOS system
+ # configuration, where the regular value for the `fileSystems'
+ # attribute should be disregarded for the purpose of building a VM
+ # test image (since those filesystems don't exist in the VM).
+ fileSystems = mkVMOverride (
+ { "/".device = cfg.bootDevice;
+ ${if cfg.writableStore then "/nix/.ro-store" else "/nix/store"} =
+ { device = "store";
+ fsType = "9p";
+ options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ];
+ neededForBoot = true;
+ };
+ "/tmp" = mkIf config.boot.tmpOnTmpfs
+ { device = "tmpfs";
+ fsType = "tmpfs";
+ neededForBoot = true;
+ # Sync with systemd's tmp.mount;
+ options = [ "mode=1777" "strictatime" "nosuid" "nodev" ];
+ };
+ "/tmp/xchg" =
+ { device = "xchg";
+ fsType = "9p";
+ options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ];
+ neededForBoot = true;
+ };
+ "/tmp/shared" =
+ { device = "shared";
+ fsType = "9p";
+ options = [ "trans=virtio" "version=9p2000.L" ];
+ neededForBoot = true;
+ };
+ } // optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs)
+ { "/nix/.rw-store" =
+ { fsType = "tmpfs";
+ options = [ "mode=0755" ];
+ neededForBoot = true;
+ };
+ } // optionalAttrs cfg.useBootLoader
+ { "/boot" =
+ { device = "/dev/vdb2";
+ fsType = "vfat";
+ options = [ "ro" ];
+ noCheck = true; # fsck fails on a r/o filesystem
+ };
+ });
+
+ swapDevices = mkVMOverride [ ];
+ boot.initrd.luks.devices = mkVMOverride {};
+
+ # Don't run ntpd in the guest. It should get the correct time from KVM.
+ services.timesyncd.enable = false;
+
+ services.qemuGuest.enable = cfg.qemu.guestAgent.enable;
+
+ system.build.vm = pkgs.runCommand "nixos-vm" { preferLocalBuild = true; }
+ ''
+ mkdir -p $out/bin
+ ln -s ${config.system.build.toplevel} $out/system
+ ln -s ${pkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${vmName}-vm
+ '';
+
+ # When building a regular system configuration, override whatever
+ # video driver the host uses.
+ services.xserver.videoDrivers = mkVMOverride [ "modesetting" ];
+ services.xserver.defaultDepth = mkVMOverride 0;
+ services.xserver.resolutions = mkVMOverride [ { x = 1024; y = 768; } ];
+ services.xserver.monitorSection =
+ ''
+ # Set a higher refresh rate so that resolutions > 800x600 work.
+ HorizSync 30-140
+ VertRefresh 50-160
+ '';
+
+ # Wireless won't work in the VM.
+ networking.wireless.enable = mkVMOverride false;
+ networking.connman.enable = mkVMOverride false;
+
+ # Speed up booting by not waiting for ARP.
+ networking.dhcpcd.extraConfig = "noarp";
+
+ networking.usePredictableInterfaceNames = false;
+
+ system.requiredKernelConfig = with config.lib.kernelConfig;
+ [ (isEnabled "VIRTIO_BLK")
+ (isEnabled "VIRTIO_PCI")
+ (isEnabled "VIRTIO_NET")
+ (isEnabled "EXT4_FS")
+ (isYes "BLK_DEV")
+ (isYes "PCI")
+ (isYes "EXPERIMENTAL")
+ (isYes "NETDEVICES")
+ (isYes "NET_CORE")
+ (isYes "INET")
+ (isYes "NETWORK_FILESYSTEMS")
+ ] ++ optional (!cfg.graphics) [
+ (isYes "SERIAL_8250_CONSOLE")
+ (isYes "SERIAL_8250")
+ ];
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/railcar.nix b/nixpkgs/nixos/modules/virtualisation/railcar.nix
new file mode 100644
index 00000000000..12da1c75fc3
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/railcar.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.railcar;
+ generateUnit = name: containerConfig:
+ let
+ container = pkgs.ociTools.buildContainer {
+ args = [
+ (pkgs.writeShellScript "run.sh" containerConfig.cmd).outPath
+ ];
+ };
+ in
+ nameValuePair "railcar-${name}" {
+ enable = true;
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${cfg.package}/bin/railcar -r ${cfg.stateDir} run ${name} -b ${container}
+ '';
+ Type = containerConfig.runType;
+ };
+ };
+ mount = with types; (submodule {
+ options = {
+ type = mkOption {
+ type = str;
+ default = "none";
+ description = ''
+ The type of the filesystem to be mounted.
+ Linux: filesystem types supported by the kernel as listed in
+ `/proc/filesystems` (e.g., "minix", "ext2", "ext3", "jfs", "xfs",
+ "reiserfs", "msdos", "proc", "nfs", "iso9660"). For bind mounts
+ (when options include either bind or rbind), the type is a dummy,
+ often "none" (not listed in /proc/filesystems).
+ '';
+ };
+ source = mkOption {
+ type = str;
+ description = "Source for the in-container mount";
+ };
+ options = mkOption {
+ type = loaOf (str);
+ default = [ "bind" ];
+ description = ''
+ Mount options of the filesystem to be used.
+
+ Support optoions are listed in the mount(8) man page. Note that
+ both filesystem-independent and filesystem-specific options
+ are listed.
+ '';
+ };
+ };
+ });
+in
+{
+ options.services.railcar = {
+ enable = mkEnableOption "railcar";
+
+ containers = mkOption {
+ default = {};
+ description = "Declarative container configuration";
+ type = with types; loaOf (submodule ({ name, config, ... }: {
+ options = {
+ cmd = mkOption {
+ type = types.lines;
+ description = "Command or script to run inside the container";
+ };
+
+ mounts = mkOption {
+ type = with types; attrsOf mount;
+ default = {};
+ description = ''
+ A set of mounts inside the container.
+
+ The defaults have been chosen for simple bindmounts, meaning
+ that you only need to provide the "source" parameter.
+ '';
+ example = ''
+ { "/data" = { source = "/var/lib/data"; }; }
+ '';
+ };
+
+ runType = mkOption {
+ type = types.str;
+ default = "oneshot";
+ description = "The systemd service run type";
+ };
+
+ os = mkOption {
+ type = types.str;
+ default = "linux";
+ description = "OS type of the container";
+ };
+
+ arch = mkOption {
+ type = types.str;
+ default = "x86_64";
+ description = "Computer architecture type of the container";
+ };
+ };
+ }));
+ };
+
+ stateDir = mkOption {
+ type = types.path;
+ default = ''/var/railcar'';
+ description = "Railcar persistent state directory";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.railcar;
+ description = "Railcar package to use";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services = flip mapAttrs' cfg.containers (name: containerConfig:
+ generateUnit name containerConfig
+ );
+ };
+}
+
diff --git a/nixpkgs/nixos/modules/virtualisation/rkt.nix b/nixpkgs/nixos/modules/virtualisation/rkt.nix
new file mode 100644
index 00000000000..fd662b52df5
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/rkt.nix
@@ -0,0 +1,64 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.virtualisation.rkt;
+in
+{
+ options.virtualisation.rkt = {
+ enable = mkEnableOption "rkt metadata service";
+
+ gc = {
+ automatic = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Automatically run the garbage collector at a specific time.";
+ };
+
+ dates = mkOption {
+ default = "03:15";
+ type = types.str;
+ description = ''
+ Specification (in the format described by
+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum></citerefentry>) of the time at
+ which the garbage collector will run.
+ '';
+ };
+
+ options = mkOption {
+ default = "--grace-period=24h";
+ type = types.str;
+ description = ''
+ Options given to <filename>rkt gc</filename> when the
+ garbage collector is run automatically.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.rkt ];
+
+ systemd.services.rkt = {
+ description = "rkt metadata service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.rkt}/bin/rkt metadata-service";
+ };
+ };
+
+ systemd.services.rkt-gc = {
+ description = "rkt garbage collection";
+ startAt = optionalString cfg.gc.automatic cfg.gc.dates;
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.rkt}/bin/rkt gc ${cfg.gc.options}";
+ };
+ };
+
+ users.groups.rkt = {};
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/virtualbox-guest.nix b/nixpkgs/nixos/modules/virtualisation/virtualbox-guest.nix
new file mode 100644
index 00000000000..834b994e92d
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/virtualbox-guest.nix
@@ -0,0 +1,93 @@
+# Module for VirtualBox guests.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.virtualisation.virtualbox.guest;
+ kernel = config.boot.kernelPackages;
+
+in
+
+{
+
+ ###### interface
+
+ options.virtualisation.virtualbox.guest = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to enable the VirtualBox service and other guest additions.";
+ };
+
+ x11 = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether to enable x11 graphics";
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable (mkMerge [{
+ assertions = [{
+ assertion = pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64;
+ message = "Virtualbox not currently supported on ${pkgs.stdenv.hostPlatform.system}";
+ }];
+
+ environment.systemPackages = [ kernel.virtualboxGuestAdditions ];
+
+ boot.extraModulePackages = [ kernel.virtualboxGuestAdditions ];
+
+ boot.supportedFilesystems = [ "vboxsf" ];
+ boot.initrd.supportedFilesystems = [ "vboxsf" ];
+
+ users.groups.vboxsf.gid = config.ids.gids.vboxsf;
+
+ systemd.services.virtualbox =
+ { description = "VirtualBox Guest Services";
+
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "dev-vboxguest.device" ];
+ after = [ "dev-vboxguest.device" ];
+
+ unitConfig.ConditionVirtualization = "oracle";
+
+ serviceConfig.ExecStart = "@${kernel.virtualboxGuestAdditions}/bin/VBoxService VBoxService --foreground";
+ };
+
+ services.udev.extraRules =
+ ''
+ # /dev/vboxuser is necessary for VBoxClient to work. Maybe we
+ # should restrict this to logged-in users.
+ KERNEL=="vboxuser", OWNER="root", GROUP="root", MODE="0666"
+
+ # Allow systemd dependencies on vboxguest.
+ SUBSYSTEM=="misc", KERNEL=="vboxguest", TAG+="systemd"
+ '';
+ } (mkIf cfg.x11 {
+ services.xserver.videoDrivers = mkOverride 50 [ "virtualbox" "modesetting" ];
+
+ services.xserver.config =
+ ''
+ Section "InputDevice"
+ Identifier "VBoxMouse"
+ Driver "vboxmouse"
+ EndSection
+ '';
+
+ services.xserver.serverLayoutSection =
+ ''
+ InputDevice "VBoxMouse"
+ '';
+
+ services.xserver.displayManager.sessionCommands =
+ ''
+ PATH=${makeBinPath [ pkgs.gnugrep pkgs.which pkgs.xorg.xorgserver.out ]}:$PATH \
+ ${kernel.virtualboxGuestAdditions}/bin/VBoxClient-all
+ '';
+ })]);
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix b/nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix
new file mode 100644
index 00000000000..6081d4153a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix
@@ -0,0 +1,153 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.virtualisation.virtualbox.host;
+
+ virtualbox = cfg.package.override {
+ inherit (cfg) enableHardening headless;
+ extensionPack = if cfg.enableExtensionPack then pkgs.virtualboxExtpack else null;
+ };
+
+ kernelModules = config.boot.kernelPackages.virtualbox.override {
+ inherit virtualbox;
+ };
+
+in
+
+{
+ options.virtualisation.virtualbox.host = {
+ enable = mkEnableOption "VirtualBox" // {
+ description = ''
+ Whether to enable VirtualBox.
+
+ <note><para>
+ In order to pass USB devices from the host to the guests, the user
+ needs to be in the <literal>vboxusers</literal> group.
+ </para></note>
+ '';
+ };
+
+ enableExtensionPack = mkEnableOption "VirtualBox extension pack" // {
+ description = ''
+ Whether to install the Oracle Extension Pack for VirtualBox.
+
+ <important><para>
+ You must set <literal>nixpkgs.config.allowUnfree = true</literal> in
+ order to use this. This requires you accept the VirtualBox PUEL.
+ </para></important>
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.virtualbox;
+ defaultText = "pkgs.virtualbox";
+ description = ''
+ Which VirtualBox package to use.
+ '';
+ };
+
+ addNetworkInterface = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Automatically set up a vboxnet0 host-only network interface.
+ '';
+ };
+
+ enableHardening = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable hardened VirtualBox, which ensures that only the binaries in the
+ system path get access to the devices exposed by the kernel modules
+ instead of all users in the vboxusers group.
+
+ <important><para>
+ Disabling this can put your system's security at risk, as local users
+ in the vboxusers group can tamper with the VirtualBox device files.
+ </para></important>
+ '';
+ };
+
+ headless = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Use VirtualBox installation without GUI and Qt dependency. Useful to enable on servers
+ and when virtual machines are controlled only via SSH.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [{
+ warnings = mkIf (config.nixpkgs.config.virtualbox.enableExtensionPack or false)
+ ["'nixpkgs.virtualbox.enableExtensionPack' has no effect, please use 'virtualisation.virtualbox.host.enableExtensionPack'"];
+ boot.kernelModules = [ "vboxdrv" "vboxnetadp" "vboxnetflt" ];
+ boot.extraModulePackages = [ kernelModules ];
+ environment.systemPackages = [ virtualbox ];
+
+ security.wrappers = let
+ mkSuid = program: {
+ source = "${virtualbox}/libexec/virtualbox/${program}";
+ owner = "root";
+ group = "vboxusers";
+ setuid = true;
+ };
+ in mkIf cfg.enableHardening
+ (builtins.listToAttrs (map (x: { name = x; value = mkSuid x; }) [
+ "VBoxHeadless"
+ "VBoxNetAdpCtl"
+ "VBoxNetDHCP"
+ "VBoxNetNAT"
+ "VBoxSDL"
+ "VBoxVolInfo"
+ "VirtualBoxVM"
+ ]));
+
+ users.groups.vboxusers.gid = config.ids.gids.vboxusers;
+
+ services.udev.extraRules =
+ ''
+ KERNEL=="vboxdrv", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
+ KERNEL=="vboxdrvu", OWNER="root", GROUP="root", MODE="0666", TAG+="systemd"
+ KERNEL=="vboxnetctl", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
+ SUBSYSTEM=="usb_device", ACTION=="add", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
+ SUBSYSTEM=="usb", ACTION=="add", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
+ SUBSYSTEM=="usb_device", ACTION=="remove", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
+ SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
+ '';
+
+ # Since we lack the right setuid/setcap binaries, set up a host-only network by default.
+ } (mkIf cfg.addNetworkInterface {
+ systemd.services.vboxnet0 =
+ { description = "VirtualBox vboxnet0 Interface";
+ requires = [ "dev-vboxnetctl.device" ];
+ after = [ "dev-vboxnetctl.device" ];
+ wantedBy = [ "network.target" "sys-subsystem-net-devices-vboxnet0.device" ];
+ path = [ virtualbox ];
+ serviceConfig.RemainAfterExit = true;
+ serviceConfig.Type = "oneshot";
+ serviceConfig.PrivateTmp = true;
+ environment.VBOX_USER_HOME = "/tmp";
+ script =
+ ''
+ if ! [ -e /sys/class/net/vboxnet0 ]; then
+ VBoxManage hostonlyif create
+ cat /tmp/VBoxSVC.log >&2
+ fi
+ '';
+ postStop =
+ ''
+ VBoxManage hostonlyif remove vboxnet0
+ '';
+ };
+
+ networking.interfaces.vboxnet0.ipv4.addresses = [{ address = "192.168.56.1"; prefixLength = 24; }];
+ # Make sure NetworkManager won't assume this interface being up
+ # means we have internet access.
+ networking.networkmanager.unmanaged = ["vboxnet0"];
+ })]);
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/virtualbox-image.nix b/nixpkgs/nixos/modules/virtualisation/virtualbox-image.nix
new file mode 100644
index 00000000000..ab65523592d
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/virtualbox-image.nix
@@ -0,0 +1,111 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.virtualbox;
+
+in {
+
+ options = {
+ virtualbox = {
+ baseImageSize = mkOption {
+ type = types.int;
+ default = 50 * 1024;
+ description = ''
+ The size of the VirtualBox base image in MiB.
+ '';
+ };
+ memorySize = mkOption {
+ type = types.int;
+ default = 1536;
+ description = ''
+ The amount of RAM the VirtualBox appliance can use in MiB.
+ '';
+ };
+ vmDerivationName = mkOption {
+ type = types.str;
+ default = "nixos-ova-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
+ description = ''
+ The name of the derivation for the VirtualBox appliance.
+ '';
+ };
+ vmName = mkOption {
+ type = types.str;
+ default = "NixOS ${config.system.nixos.label} (${pkgs.stdenv.hostPlatform.system})";
+ description = ''
+ The name of the VirtualBox appliance.
+ '';
+ };
+ vmFileName = mkOption {
+ type = types.str;
+ default = "nixos-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.ova";
+ description = ''
+ The file name of the VirtualBox appliance.
+ '';
+ };
+ };
+ };
+
+ config = {
+ system.build.virtualBoxOVA = import ../../lib/make-disk-image.nix {
+ name = cfg.vmDerivationName;
+
+ inherit pkgs lib config;
+ partitionTableType = "legacy";
+ diskSize = cfg.baseImageSize;
+
+ postVM =
+ ''
+ export HOME=$PWD
+ export PATH=${pkgs.virtualbox}/bin:$PATH
+
+ echo "creating VirtualBox pass-through disk wrapper (no copying involved)..."
+ VBoxManage internalcommands createrawvmdk -filename disk.vmdk -rawdisk $diskImage
+
+ echo "creating VirtualBox VM..."
+ vmName="${cfg.vmName}";
+ VBoxManage createvm --name "$vmName" --register \
+ --ostype ${if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "Linux26_64" else "Linux26"}
+ VBoxManage modifyvm "$vmName" \
+ --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 --audioout on \
+ --rtcuseutc on \
+ --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
+
+ echo "exporting VirtualBox VM..."
+ mkdir -p $out
+ fn="$out/${cfg.vmFileName}"
+ VBoxManage export "$vmName" --output "$fn" --options manifest
+
+ rm -v $diskImage
+
+ mkdir -p $out/nix-support
+ echo "file ova $fn" >> $out/nix-support/hydra-build-products
+ '';
+ };
+
+ fileSystems."/" = {
+ device = "/dev/disk/by-label/nixos";
+ autoResize = true;
+ fsType = "ext4";
+ };
+
+ boot.growPartition = true;
+ boot.loader.grub.device = "/dev/sda";
+
+ swapDevices = [{
+ device = "/var/swap";
+ size = 2048;
+ }];
+
+ virtualisation.virtualbox.guest.enable = true;
+
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/vmware-guest.nix b/nixpkgs/nixos/modules/virtualisation/vmware-guest.nix
new file mode 100644
index 00000000000..f418f849759
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/vmware-guest.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.virtualisation.vmware.guest;
+ open-vm-tools = if cfg.headless then pkgs.open-vm-tools-headless else pkgs.open-vm-tools;
+ xf86inputvmmouse = pkgs.xorg.xf86inputvmmouse;
+in
+{
+ options.virtualisation.vmware.guest = {
+ enable = mkEnableOption "VMWare Guest Support";
+ headless = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to disable X11-related features.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [ {
+ assertion = pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64;
+ message = "VMWare guest is not currently supported on ${pkgs.stdenv.hostPlatform.system}";
+ } ];
+
+ boot.initrd.kernelModules = [ "vmw_pvscsi" ];
+
+ environment.systemPackages = [ open-vm-tools ];
+
+ systemd.services.vmware =
+ { description = "VMWare Guest Service";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${open-vm-tools}/bin/vmtoolsd";
+ };
+
+ environment.etc.vmware-tools.source = "${open-vm-tools}/etc/vmware-tools/*";
+
+ services.xserver = mkIf (!cfg.headless) {
+ videoDrivers = mkOverride 50 [ "vmware" ];
+ modules = [ xf86inputvmmouse ];
+
+ config = ''
+ Section "InputClass"
+ Identifier "VMMouse"
+ MatchDevicePath "/dev/input/event*"
+ MatchProduct "ImPS/2 Generic Wheel Mouse"
+ Driver "vmmouse"
+ EndSection
+ '';
+
+ displayManager.sessionCommands = ''
+ ${open-vm-tools}/bin/vmware-user-suid-wrapper
+ '';
+ };
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/xe-guest-utilities.nix b/nixpkgs/nixos/modules/virtualisation/xe-guest-utilities.nix
new file mode 100644
index 00000000000..675cf929737
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/xe-guest-utilities.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.xe-guest-utilities;
+in {
+ options = {
+ services.xe-guest-utilities = {
+ enable = mkEnableOption "the Xen guest utilities daemon";
+ };
+ };
+ config = mkIf cfg.enable {
+ services.udev.packages = [ pkgs.xe-guest-utilities ];
+ systemd.tmpfiles.rules = [ "d /run/xenstored 0755 - - -" ];
+
+ systemd.services.xe-daemon = {
+ description = "xen daemon file";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "xe-linux-distribution.service" ];
+ requires = [ "proc-xen.mount" ];
+ path = [ pkgs.coreutils pkgs.iproute ];
+ serviceConfig = {
+ PIDFile = "/run/xe-daemon.pid";
+ ExecStart = "${pkgs.xe-guest-utilities}/bin/xe-daemon -p /run/xe-daemon.pid";
+ ExecStop = "${pkgs.procps}/bin/pkill -TERM -F /run/xe-daemon.pid";
+ };
+ };
+
+ systemd.services.xe-linux-distribution = {
+ description = "xen linux distribution service";
+ wantedBy = [ "multi-user.target" ];
+ before = [ "xend.service" ];
+ path = [ pkgs.xe-guest-utilities pkgs.coreutils pkgs.gawk pkgs.gnused ];
+ serviceConfig = {
+ Type = "simple";
+ RemainAfterExit = "yes";
+ ExecStart = "${pkgs.xe-guest-utilities}/bin/xe-linux-distribution /var/cache/xe-linux-distribution";
+ };
+ };
+
+ systemd.mounts = [
+ { description = "Mount /proc/xen files";
+ what = "xenfs";
+ where = "/proc/xen";
+ type = "xenfs";
+ unitConfig = {
+ ConditionPathExists = "/proc/xen";
+ RefuseManualStop = "true";
+ };
+ }
+ ];
+ };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/xen-dom0.nix b/nixpkgs/nixos/modules/virtualisation/xen-dom0.nix
new file mode 100644
index 00000000000..06d5c63476f
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/xen-dom0.nix
@@ -0,0 +1,461 @@
+# Xen hypervisor (Dom0) support.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.virtualisation.xen;
+in
+
+{
+ ###### interface
+
+ options = {
+
+ virtualisation.xen.enable =
+ mkOption {
+ default = false;
+ type = types.bool;
+ description =
+ ''
+ Setting this option enables the Xen hypervisor, a
+ virtualisation technology that allows multiple virtual
+ machines, known as <emphasis>domains</emphasis>, to run
+ concurrently on the physical machine. NixOS runs as the
+ privileged <emphasis>Domain 0</emphasis>. This option
+ requires a reboot to take effect.
+ '';
+ };
+
+ virtualisation.xen.package = mkOption {
+ type = types.package;
+ defaultText = "pkgs.xen";
+ example = literalExample "pkgs.xen-light";
+ description = ''
+ The package used for Xen binary.
+ '';
+ relatedPackages = [ "xen" "xen-light" ];
+ };
+
+ virtualisation.xen.package-qemu = mkOption {
+ type = types.package;
+ defaultText = "pkgs.xen";
+ example = literalExample "pkgs.qemu_xen-light";
+ description = ''
+ The package with qemu binaries for dom0 qemu and xendomains.
+ '';
+ relatedPackages = [ "xen"
+ { name = "qemu_xen-light"; comment = "For use with pkgs.xen-light."; }
+ ];
+ };
+
+ virtualisation.xen.bootParams =
+ mkOption {
+ default = "";
+ description =
+ ''
+ Parameters passed to the Xen hypervisor at boot time.
+ '';
+ };
+
+ virtualisation.xen.domain0MemorySize =
+ mkOption {
+ default = 0;
+ example = 512;
+ description =
+ ''
+ Amount of memory (in MiB) allocated to Domain 0 on boot.
+ If set to 0, all memory is assigned to Domain 0.
+ '';
+ };
+
+ virtualisation.xen.bridge = {
+ name = mkOption {
+ default = "xenbr0";
+ description = ''
+ Name of bridge the Xen domUs connect to.
+ '';
+ };
+
+ address = mkOption {
+ type = types.str;
+ default = "172.16.0.1";
+ description = ''
+ IPv4 address of the bridge.
+ '';
+ };
+
+ prefixLength = mkOption {
+ type = types.addCheck types.int (n: n >= 0 && n <= 32);
+ default = 16;
+ description = ''
+ Subnet mask of the bridge interface, specified as the number of
+ bits in the prefix (<literal>24</literal>).
+ A DHCP server will provide IP addresses for the whole, remaining
+ subnet.
+ '';
+ };
+
+ forwardDns = mkOption {
+ default = false;
+ description = ''
+ If set to <literal>true</literal>, the DNS queries from the
+ hosts connected to the bridge will be forwarded to the DNS
+ servers specified in /etc/resolv.conf .
+ '';
+ };
+
+ };
+
+ virtualisation.xen.stored =
+ mkOption {
+ type = types.path;
+ description =
+ ''
+ Xen Store daemon to use. Defaults to oxenstored of the xen package.
+ '';
+ };
+
+ virtualisation.xen.domains = {
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ ''
+ Options defined here will override the defaults for xendomains.
+ The default options can be seen in the file included from
+ /etc/default/xendomains.
+ '';
+ };
+ };
+
+ virtualisation.xen.trace =
+ mkOption {
+ default = false;
+ description =
+ ''
+ Enable Xen tracing.
+ '';
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ assertions = [ {
+ assertion = pkgs.stdenv.isx86_64;
+ message = "Xen currently not supported on ${pkgs.stdenv.hostPlatform.system}";
+ } {
+ assertion = config.boot.loader.grub.enable && (config.boot.loader.grub.efiSupport == false);
+ message = "Xen currently does not support EFI boot";
+ } ];
+
+ virtualisation.xen.package = mkDefault pkgs.xen;
+ virtualisation.xen.package-qemu = mkDefault pkgs.xen;
+ virtualisation.xen.stored = mkDefault "${cfg.package}/bin/oxenstored";
+
+ environment.systemPackages = [ cfg.package ];
+
+ # Make sure Domain 0 gets the required configuration
+ #boot.kernelPackages = pkgs.boot.kernelPackages.override { features={xen_dom0=true;}; };
+
+ boot.kernelModules =
+ [ "xen-evtchn" "xen-gntdev" "xen-gntalloc" "xen-blkback" "xen-netback"
+ "xen-pciback" "evtchn" "gntdev" "netbk" "blkbk" "xen-scsibk"
+ "usbbk" "pciback" "xen-acpi-processor" "blktap2" "tun" "netxen_nic"
+ "xen_wdt" "xen-acpi-processor" "xen-privcmd" "xen-scsiback"
+ "xenfs"
+ ];
+
+ # The xenfs module is needed in system.activationScripts.xen, but
+ # the modprobe command there fails silently. Include xenfs in the
+ # initrd as a work around.
+ boot.initrd.kernelModules = [ "xenfs" ];
+
+ # The radeonfb kernel module causes the screen to go black as soon
+ # as it's loaded, so don't load it.
+ boot.blacklistedKernelModules = [ "radeonfb" ];
+
+ # Increase the number of loopback devices from the default (8),
+ # which is way too small because every VM virtual disk requires a
+ # loopback device.
+ boot.extraModprobeConfig =
+ ''
+ options loop max_loop=64
+ '';
+
+ virtualisation.xen.bootParams = [] ++
+ optionals cfg.trace [ "loglvl=all" "guest_loglvl=all" ] ++
+ optional (cfg.domain0MemorySize != 0) "dom0_mem=${toString cfg.domain0MemorySize}M";
+
+ system.extraSystemBuilderCmds =
+ ''
+ ln -s ${cfg.package}/boot/xen.gz $out/xen.gz
+ echo "${toString cfg.bootParams}" > $out/xen-params
+ '';
+
+ # Mount the /proc/xen pseudo-filesystem.
+ system.activationScripts.xen =
+ ''
+ if [ -d /proc/xen ]; then
+ ${pkgs.kmod}/bin/modprobe xenfs 2> /dev/null
+ ${pkgs.utillinux}/bin/mountpoint -q /proc/xen || \
+ ${pkgs.utillinux}/bin/mount -t xenfs none /proc/xen
+ fi
+ '';
+
+ # Domain 0 requires a pvops-enabled kernel.
+ system.requiredKernelConfig = with config.lib.kernelConfig;
+ [ (isYes "XEN")
+ (isYes "X86_IO_APIC")
+ (isYes "ACPI")
+ (isYes "XEN_DOM0")
+ (isYes "PCI_XEN")
+ (isYes "XEN_DEV_EVTCHN")
+ (isYes "XENFS")
+ (isYes "XEN_COMPAT_XENFS")
+ (isYes "XEN_SYS_HYPERVISOR")
+ (isYes "XEN_GNTDEV")
+ (isYes "XEN_BACKEND")
+ (isModule "XEN_NETDEV_BACKEND")
+ (isModule "XEN_BLKDEV_BACKEND")
+ (isModule "XEN_PCIDEV_BACKEND")
+ (isYes "XEN_BALLOON")
+ (isYes "XEN_SCRUB_PAGES")
+ ];
+
+
+ environment.etc =
+ [ { source = "${cfg.package}/etc/xen/xl.conf";
+ target = "xen/xl.conf";
+ }
+ { source = "${cfg.package}/etc/xen/scripts";
+ target = "xen/scripts";
+ }
+ { text = ''
+ source ${cfg.package}/etc/default/xendomains
+
+ ${cfg.domains.extraConfig}
+ '';
+ target = "default/xendomains";
+ }
+ ]
+ ++ lib.optionals (builtins.compareVersions cfg.package.version "4.10" >= 0) [
+ # in V 4.10 oxenstored requires /etc/xen/oxenstored.conf to start
+ { source = "${cfg.package}/etc/xen/oxenstored.conf";
+ target = "xen/oxenstored.conf";
+ }
+ ];
+
+ # Xen provides udev rules.
+ services.udev.packages = [ cfg.package ];
+
+ services.udev.path = [ pkgs.bridge-utils pkgs.iproute ];
+
+ systemd.services.xen-store = {
+ description = "Xen Store Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "xen-store.socket" ];
+ requires = [ "xen-store.socket" ];
+ preStart = ''
+ export XENSTORED_ROOTDIR="/var/lib/xenstored"
+ rm -f "$XENSTORED_ROOTDIR"/tdb* &>/dev/null
+
+ mkdir -p /var/run
+ mkdir -p /var/log/xen # Running xl requires /var/log/xen and /var/lib/xen,
+ mkdir -p /var/lib/xen # so we create them here unconditionally.
+ grep -q control_d /proc/xen/capabilities
+ '';
+ serviceConfig = if (builtins.compareVersions cfg.package.version "4.8" < 0) then
+ { ExecStart = ''
+ ${cfg.stored}${optionalString cfg.trace " -T /var/log/xen/xenstored-trace.log"} --no-fork
+ '';
+ } else {
+ ExecStart = ''
+ ${cfg.package}/etc/xen/scripts/launch-xenstore
+ '';
+ Type = "notify";
+ RemainAfterExit = true;
+ NotifyAccess = "all";
+ };
+ postStart = ''
+ ${optionalString (builtins.compareVersions cfg.package.version "4.8" < 0) ''
+ time=0
+ timeout=30
+ # Wait for xenstored to actually come up, timing out after 30 seconds
+ while [ $time -lt $timeout ] && ! `${cfg.package}/bin/xenstore-read -s / >/dev/null 2>&1` ; do
+ time=$(($time+1))
+ sleep 1
+ done
+
+ # Exit if we timed out
+ if ! [ $time -lt $timeout ] ; then
+ echo "Could not start Xenstore Daemon"
+ exit 1
+ fi
+ ''}
+ echo "executing xen-init-dom0"
+ ${cfg.package}/lib/xen/bin/xen-init-dom0
+ '';
+ };
+
+ systemd.sockets.xen-store = {
+ description = "XenStore Socket for userspace API";
+ wantedBy = [ "sockets.target" ];
+ socketConfig = {
+ ListenStream = [ "/var/run/xenstored/socket" "/var/run/xenstored/socket_ro" ];
+ SocketMode = "0660";
+ SocketUser = "root";
+ SocketGroup = "root";
+ };
+ };
+
+
+ systemd.services.xen-console = {
+ description = "Xen Console Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "xen-store.service" ];
+ requires = [ "xen-store.service" ];
+ preStart = ''
+ mkdir -p /var/run/xen
+ ${optionalString cfg.trace "mkdir -p /var/log/xen"}
+ grep -q control_d /proc/xen/capabilities
+ '';
+ serviceConfig = {
+ ExecStart = ''
+ ${cfg.package}/bin/xenconsoled\
+ ${optionalString ((builtins.compareVersions cfg.package.version "4.8" >= 0)) " -i"}\
+ ${optionalString cfg.trace " --log=all --log-dir=/var/log/xen"}
+ '';
+ };
+ };
+
+
+ systemd.services.xen-qemu = {
+ description = "Xen Qemu Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "xen-console.service" ];
+ requires = [ "xen-store.service" ];
+ serviceConfig.ExecStart = ''
+ ${cfg.package-qemu}/${cfg.package-qemu.qemu-system-i386} \
+ -xen-attach -xen-domid 0 -name dom0 -M xenpv \
+ -nographic -monitor /dev/null -serial /dev/null -parallel /dev/null
+ '';
+ };
+
+
+ systemd.services.xen-watchdog = {
+ description = "Xen Watchdog Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "xen-qemu.service" "xen-domains.service" ];
+ serviceConfig.ExecStart = "${cfg.package}/bin/xenwatchdogd 30 15";
+ serviceConfig.Type = "forking";
+ serviceConfig.RestartSec = "1";
+ serviceConfig.Restart = "on-failure";
+ };
+
+
+ systemd.services.xen-bridge = {
+ description = "Xen bridge";
+ wantedBy = [ "multi-user.target" ];
+ before = [ "xen-domains.service" ];
+ preStart = ''
+ mkdir -p /var/run/xen
+ touch /var/run/xen/dnsmasq.pid
+ touch /var/run/xen/dnsmasq.etherfile
+ touch /var/run/xen/dnsmasq.leasefile
+
+ IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Usable\ range`
+ export XEN_BRIDGE_IP_RANGE_START="${"\${data[1]//[[:blank:]]/}"}"
+ export XEN_BRIDGE_IP_RANGE_END="${"\${data[2]//[[:blank:]]/}"}"
+
+ IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address`
+ export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}"
+
+ IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ mask`
+ export XEN_BRIDGE_NETMASK="${"\${data[1]//[[:blank:]]/}"}"
+
+ echo "${cfg.bridge.address} host gw dns" > /var/run/xen/dnsmasq.hostsfile
+
+ cat <<EOF > /var/run/xen/dnsmasq.conf
+ no-daemon
+ pid-file=/var/run/xen/dnsmasq.pid
+ interface=${cfg.bridge.name}
+ except-interface=lo
+ bind-interfaces
+ auth-zone=xen.local,$XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength}
+ domain=xen.local
+ addn-hosts=/var/run/xen/dnsmasq.hostsfile
+ expand-hosts
+ strict-order
+ no-hosts
+ bogus-priv
+ ${optionalString (!cfg.bridge.forwardDns) ''
+ no-resolv
+ no-poll
+ auth-server=dns.xen.local,${cfg.bridge.name}
+ ''}
+ filterwin2k
+ clear-on-reload
+ domain-needed
+ dhcp-hostsfile=/var/run/xen/dnsmasq.etherfile
+ dhcp-authoritative
+ dhcp-range=$XEN_BRIDGE_IP_RANGE_START,$XEN_BRIDGE_IP_RANGE_END
+ dhcp-no-override
+ no-ping
+ dhcp-leasefile=/var/run/xen/dnsmasq.leasefile
+ EOF
+
+ # DHCP
+ ${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p tcp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p udp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
+ # DNS
+ ${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p tcp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p udp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
+
+ ${pkgs.bridge-utils}/bin/brctl addbr ${cfg.bridge.name}
+ ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} ${cfg.bridge.address}
+ ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} netmask $XEN_BRIDGE_NETMASK
+ ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} up
+ '';
+ serviceConfig.ExecStart = "${pkgs.dnsmasq}/bin/dnsmasq --conf-file=/var/run/xen/dnsmasq.conf";
+ postStop = ''
+ IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address`
+ export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}"
+
+ ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} down
+ ${pkgs.bridge-utils}/bin/brctl delbr ${cfg.bridge.name}
+
+ # DNS
+ ${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p udp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p tcp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
+ # DHCP
+ ${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p udp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p tcp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
+ '';
+ };
+
+
+ systemd.services.xen-domains = {
+ description = "Xen domains - automatically starts, saves and restores Xen domains";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "xen-bridge.service" "xen-qemu.service" ];
+ requires = [ "xen-bridge.service" "xen-qemu.service" ];
+ ## To prevent a race between dhcpcd and xend's bridge setup script
+ ## (which renames eth* to peth* and recreates eth* as a virtual
+ ## device), start dhcpcd after xend.
+ before = [ "dhcpd.service" ];
+ restartIfChanged = false;
+ serviceConfig.RemainAfterExit = "yes";
+ path = [ cfg.package cfg.package-qemu ];
+ environment.XENDOM_CONFIG = "${cfg.package}/etc/sysconfig/xendomains";
+ preStart = "mkdir -p /var/lock/subsys -m 755";
+ serviceConfig.ExecStart = "${cfg.package}/etc/init.d/xendomains start";
+ serviceConfig.ExecStop = "${cfg.package}/etc/init.d/xendomains stop";
+ };
+
+ };
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/xen-domU.nix b/nixpkgs/nixos/modules/virtualisation/xen-domU.nix
new file mode 100644
index 00000000000..c00b984c2ce
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/xen-domU.nix
@@ -0,0 +1,19 @@
+# Common configuration for Xen DomU NixOS virtual machines.
+
+{ ... }:
+
+{
+ boot.loader.grub.version = 2;
+ boot.loader.grub.device = "nodev";
+
+ boot.initrd.kernelModules =
+ [ "xen-blkfront" "xen-tpmfront" "xen-kbdfront" "xen-fbfront"
+ "xen-netfront" "xen-pcifront" "xen-scsifront"
+ ];
+
+ # Send syslog messages to the Xen console.
+ services.syslogd.tty = "hvc0";
+
+ # Don't run ntpd, since we should get the correct time from Dom0.
+ services.timesyncd.enable = false;
+}