diff options
Diffstat (limited to 'nixos/modules')
66 files changed, 2157 insertions, 1174 deletions
diff --git a/nixos/modules/hardware/opengl.nix b/nixos/modules/hardware/opengl.nix index 7b5e669d47f4..9108bcbd1652 100644 --- a/nixos/modules/hardware/opengl.nix +++ b/nixos/modules/hardware/opengl.nix @@ -69,48 +69,19 @@ in package = mkOption { type = types.package; internal = true; - default = cfg.mesaPackage; description = lib.mdDoc '' The package that provides the OpenGL implementation. - - The default is Mesa's drivers which should cover all OpenGL-capable - hardware. If you want to use another Mesa version, adjust - {option}`mesaPackage`. ''; }; + package32 = mkOption { type = types.package; internal = true; - default = cfg.mesaPackage32; - description = lib.mdDoc '' - Same as {option}`package` but for the 32-bit OpenGL implementation on - 64-bit systems. Used when {option}`driSupport32Bit` is set. - ''; - }; - - mesaPackage = mkOption { - type = types.package; - default = pkgs.mesa; - defaultText = literalExpression "pkgs.mesa"; - example = literalExpression "pkgs.mesa_22"; description = lib.mdDoc '' - The Mesa driver package used for rendering support on the system. - - You should only need to adjust this if you require a newer Mesa - version for your hardware or because you need to patch a bug. + The package that provides the 32-bit OpenGL implementation on + 64-bit systems. Used when {option}`driSupport32Bit` is + set. ''; - apply = mesa: mesa.drivers or (throw "`mesa` package must have a `drivers` output."); - }; - mesaPackage32 = mkOption { - type = types.package; - default = pkgs.pkgsi686Linux.mesa; - defaultText = literalExpression "pkgs.pkgsi686Linux.mesa"; - example = literalExpression "pkgs.pkgsi686Linux.mesa_22"; - description = lib.mdDoc '' - Same as {option}`mesaPackage` but for the 32-bit Mesa on 64-bit - systems. Used when {option}`driSupport32Bit` is set. - ''; - apply = mesa: mesa.drivers or (throw "`mesa` package must have a `drivers` output."); }; extraPackages = mkOption { @@ -126,6 +97,7 @@ in ::: ''; }; + extraPackages32 = mkOption { type = types.listOf types.package; default = []; @@ -181,6 +153,9 @@ in environment.sessionVariables.LD_LIBRARY_PATH = mkIf cfg.setLdLibraryPath ([ "/run/opengl-driver/lib" ] ++ optional cfg.driSupport32Bit "/run/opengl-driver-32/lib"); + hardware.opengl.package = mkDefault pkgs.mesa.drivers; + hardware.opengl.package32 = mkDefault pkgs.pkgsi686Linux.mesa.drivers; + boot.extraModulePackages = optional (elem "virtualbox" videoDrivers) kernelPackages.virtualboxGuestAdditions; }; } diff --git a/nixos/modules/hardware/video/webcam/ipu6.nix b/nixos/modules/hardware/video/webcam/ipu6.nix new file mode 100644 index 000000000000..fce78cda34c7 --- /dev/null +++ b/nixos/modules/hardware/video/webcam/ipu6.nix @@ -0,0 +1,57 @@ +{ config, lib, pkgs, ... }: +let + + inherit (lib) mkDefault mkEnableOption mkIf mkOption optional types; + + cfg = config.hardware.ipu6; + +in +{ + + options.hardware.ipu6 = { + + enable = mkEnableOption (lib.mdDoc "support for Intel IPU6/MIPI cameras"); + + platform = mkOption { + type = types.enum [ "ipu6" "ipu6ep" ]; + description = lib.mdDoc '' + Choose the version for your hardware platform. + + Use `ipu6` for Tiger Lake and `ipu6ep` for Alder Lake respectively. + ''; + }; + + }; + + config = mkIf cfg.enable { + + boot.extraModulePackages = with config.boot.kernelPackages; [ + ipu6-drivers + ]; + + hardware.firmware = with pkgs; [ ] + ++ optional (cfg.platform == "ipu6") ipu6-camera-bin + ++ optional (cfg.platform == "ipu6ep") ipu6ep-camera-bin; + + services.udev.extraRules = '' + SUBSYSTEM=="intel-ipu6-psys", MODE="0660", GROUP="video" + ''; + + services.v4l2-relayd.instances.ipu6 = { + enable = mkDefault true; + + cardLabel = mkDefault "Intel MIPI Camera"; + + extraPackages = with pkgs.gst_all_1; [ ] + ++ optional (cfg.platform == "ipu6") icamerasrc-ipu6 + ++ optional (cfg.platform == "ipu6ep") icamerasrc-ipu6ep; + + input = { + pipeline = "icamerasrc"; + format = mkIf (cfg.platform == "ipu6ep") (mkDefault "NV12"); + }; + }; + + }; + +} diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl index 946e73dac586..a082ed3450e9 100644 --- a/nixos/modules/installer/tools/nixos-generate-config.pl +++ b/nixos/modules/installer/tools/nixos-generate-config.pl @@ -200,7 +200,7 @@ sub pciCheck { } # In case this is a virtio scsi device, we need to explicitly make this available. - if ($vendor eq "0x1af4" && $device eq "0x1004") { + if ($vendor eq "0x1af4" && ($device eq "0x1004" || $device eq "0x1048") ) { push @initrdAvailableKernelModules, "virtio_scsi"; } @@ -473,7 +473,7 @@ EOF } # Don't emit tmpfs entry for /tmp, because it most likely comes from the - # boot.tmpOnTmpfs option in configuration.nix (managed declaratively). + # boot.tmp.useTmpfs option in configuration.nix (managed declaratively). next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs"); # Emit the filesystem. diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index bed50b81604d..5b278b5e8062 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -233,7 +233,7 @@ in # nix-serve = 199; # unused, removed 2020-12-12 #tvheadend = 200; # dynamically allocated as of 2021-09-18 uwsgi = 201; - gitit = 202; + # gitit = 202; # unused, module was removed 2023-04-03 riemanntools = 203; subsonic = 204; # riak = 205; # unused, remove 2022-07-22 diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index ed6e36eb5733..f1c459f75570 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -99,6 +99,7 @@ ./hardware/video/switcheroo-control.nix ./hardware/video/uvcvideo/default.nix ./hardware/video/webcam/facetimehd.nix + ./hardware/video/webcam/ipu6.nix ./hardware/wooting.nix ./hardware/xone.nix ./hardware/xpadneo.nix @@ -148,6 +149,7 @@ ./programs/cdemu.nix ./programs/cfs-zen-tweaks.nix ./programs/chromium.nix + ./programs/clash-verge.nix ./programs/cnping.nix ./programs/command-not-found/command-not-found.nix ./programs/criu.nix @@ -179,6 +181,7 @@ ./programs/haguichi.nix ./programs/hamster.nix ./programs/htop.nix + ./programs/hyprland.nix ./programs/iay.nix ./programs/iftop.nix ./programs/i3lock.nix @@ -195,6 +198,7 @@ ./programs/mdevctl.nix ./programs/mepo.nix ./programs/mininet.nix + ./programs/minipro.nix ./programs/miriway.nix ./programs/mosh.nix ./programs/msmtp.nix @@ -276,6 +280,7 @@ ./security/doas.nix ./security/duosec.nix ./security/google_oslogin.nix + ./security/ipa.nix ./security/lock-kernel-modules.nix ./security/misc.nix ./security/oath.nix @@ -510,6 +515,7 @@ ./services/hardware/usbrelayd.nix ./services/hardware/vdr.nix ./services/hardware/keyd.nix + ./services/home-automation/esphome.nix ./services/home-automation/evcc.nix ./services/home-automation/home-assistant.nix ./services/home-automation/zigbee2mqtt.nix @@ -612,7 +618,6 @@ ./services/misc/gammu-smsd.nix ./services/misc/geoipupdate.nix ./services/misc/gitea.nix - # ./services/misc/gitit.nix ./services/misc/gitlab.nix ./services/misc/gitolite.nix ./services/misc/gitweb.nix @@ -665,6 +670,7 @@ ./services/misc/polaris.nix ./services/misc/portunus.nix ./services/misc/prowlarr.nix + ./services/misc/pufferpanel.nix ./services/misc/pykms.nix ./services/misc/radarr.nix ./services/misc/readarr.nix @@ -879,6 +885,7 @@ ./services/networking/iscsi/initiator.nix ./services/networking/iscsi/root-initiator.nix ./services/networking/iscsi/target.nix + ./services/networking/ivpn.nix ./services/networking/iwd.nix ./services/networking/jibri/default.nix ./services/networking/jicofo.nix @@ -1037,6 +1044,7 @@ ./services/networking/wg-netmanager.nix ./services/networking/webhook.nix ./services/networking/wg-quick.nix + ./services/networking/wgautomesh.nix ./services/networking/wireguard.nix ./services/networking/wpa_supplicant.nix ./services/networking/wstunnel.nix @@ -1127,8 +1135,9 @@ ./services/video/epgstation/default.nix ./services/video/mirakurun.nix ./services/video/replay-sorcery.nix - ./services/video/rtsp-simple-server.nix + ./services/video/mediamtx.nix ./services/video/unifi-video.nix + ./services/video/v4l2-relayd.nix ./services/wayland/cage.nix ./services/web-apps/akkoma.nix ./services/web-apps/alps.nix @@ -1161,7 +1170,6 @@ ./services/web-apps/hledger-web.nix ./services/web-apps/icingaweb2/icingaweb2.nix ./services/web-apps/icingaweb2/module-monitoring.nix - ./services/web-apps/ihatemoney ./services/web-apps/invidious.nix ./services/web-apps/invoiceplane.nix ./services/web-apps/isso.nix @@ -1177,6 +1185,7 @@ ./services/web-apps/mattermost.nix ./services/web-apps/mediawiki.nix ./services/web-apps/miniflux.nix + ./services/web-apps/monica.nix ./services/web-apps/moodle.nix ./services/web-apps/netbox.nix ./services/web-apps/nextcloud.nix diff --git a/nixos/modules/profiles/installation-device.nix b/nixos/modules/profiles/installation-device.nix index 980720691a43..32884f4b8754 100644 --- a/nixos/modules/profiles/installation-device.nix +++ b/nixos/modules/profiles/installation-device.nix @@ -52,9 +52,9 @@ with lib; services.getty.helpLine = '' The "nixos" and "root" accounts have empty passwords. - An ssh daemon is running. You then must set a password - for either "root" or "nixos" with `passwd` or add an ssh key - to /home/nixos/.ssh/authorized_keys be able to login. + To log in over ssh you must set a password for either "nixos" or "root" + with `passwd` (prefix with `sudo` for "root"), or add your public key to + /home/nixos/.ssh/authorized_keys or /root/.ssh/authorized_keys. If you need a wireless connection, type `sudo systemctl start wpa_supplicant` and configure a @@ -65,8 +65,8 @@ with lib; start the graphical user interface. ''; - # We run sshd by default. Login via root is only possible after adding a - # password via "passwd" or by adding a ssh key to /home/nixos/.ssh/authorized_keys. + # We run sshd by default. Login is only possible after adding a + # password via "passwd" or by adding a ssh key to ~/.ssh/authorized_keys. # The latter one is particular useful if keys are manually added to # installation device for head-less systems i.e. arm boards by manually # mounting the storage in a different system. diff --git a/nixos/modules/programs/clash-verge.nix b/nixos/modules/programs/clash-verge.nix new file mode 100644 index 000000000000..29977be3858f --- /dev/null +++ b/nixos/modules/programs/clash-verge.nix @@ -0,0 +1,41 @@ +{ config, lib, pkgs, ... }: + +{ + options.programs.clash-verge = { + enable = lib.mkEnableOption (lib.mdDoc '' + Clash Verge. + ''); + + autoStart = lib.mkEnableOption (lib.mdDoc '' + Clash Verge Auto Launch. + ''); + + tunMode = lib.mkEnableOption (lib.mdDoc '' + Clash Verge Tun Mode. + ''); + }; + + config = + let + cfg = config.programs.clash-verge; + in + lib.mkIf cfg.enable { + + environment.systemPackages = [ + pkgs.clash-verge + (lib.mkIf cfg.autoStart (pkgs.makeAutostartItem { + name = "clash-verge"; + package = pkgs.clash-verge; + })) + ]; + + security.wrappers.clash-verge = lib.mkIf cfg.tunMode { + owner = "root"; + group = "root"; + capabilities = "cap_net_bind_service,cap_net_admin=+ep"; + source = "${lib.getExe pkgs.clash-verge}"; + }; + }; + + meta.maintainers = with lib.maintainers; [ zendo ]; +} diff --git a/nixos/modules/programs/hyprland.nix b/nixos/modules/programs/hyprland.nix new file mode 100644 index 000000000000..b14f1f77fcf8 --- /dev/null +++ b/nixos/modules/programs/hyprland.nix @@ -0,0 +1,84 @@ +{ config +, lib +, pkgs +, ... +}: +with lib; let + cfg = config.programs.hyprland; + + defaultHyprlandPackage = pkgs.hyprland.override { + enableXWayland = cfg.xwayland.enable; + hidpiXWayland = cfg.xwayland.hidpi; + nvidiaPatches = cfg.nvidiaPatches; + }; +in +{ + options.programs.hyprland = { + enable = mkEnableOption null // { + description = mdDoc '' + Hyprland, the dynamic tiling Wayland compositor that doesn't sacrifice on its looks. + + You can manually launch Hyprland by executing {command}`Hyprland` on a TTY. + + A configuration file will be generated in {file}`~/.config/hypr/hyprland.conf`. + See <https://wiki.hyprland.org> for more information. + ''; + }; + + package = mkOption { + type = types.path; + default = defaultHyprlandPackage; + defaultText = literalExpression '' + pkgs.hyprland.override { + enableXWayland = config.programs.hyprland.xwayland.enable; + hidpiXWayland = config.programs.hyprland.xwayland.hidpi; + nvidiaPatches = config.programs.hyprland.nvidiaPatches; + } + ''; + example = literalExpression "<Hyprland flake>.packages.<system>.default"; + description = mdDoc '' + The Hyprland package to use. + Setting this option will make {option}`programs.hyprland.xwayland` and + {option}`programs.hyprland.nvidiaPatches` not work. + ''; + }; + + xwayland = { + enable = mkEnableOption (mdDoc "XWayland") // { default = true; }; + hidpi = mkEnableOption null // { + description = mdDoc '' + Enable HiDPI XWayland, based on [XWayland MR 733](https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/733). + See <https://wiki.hyprland.org/Nix/Options-Overrides/#xwayland-hidpi> for more info. + ''; + }; + }; + + nvidiaPatches = mkEnableOption (mdDoc "patching wlroots for better Nvidia support"); + }; + + config = mkIf cfg.enable { + environment = { + systemPackages = [ cfg.package ]; + + }; + + fonts.enableDefaultFonts = mkDefault true; + hardware.opengl.enable = mkDefault true; + + programs = { + dconf.enable = mkDefault true; + xwayland.enable = mkDefault true; + }; + + security.polkit.enable = true; + + services.xserver.displayManager.sessionPackages = [ cfg.package ]; + + xdg.portal = { + enable = mkDefault true; + extraPortals = [ + pkgs.xdg-desktop-portal-hyprland + ]; + }; + }; +} diff --git a/nixos/modules/programs/minipro.nix b/nixos/modules/programs/minipro.nix new file mode 100644 index 000000000000..a947f83f2ee0 --- /dev/null +++ b/nixos/modules/programs/minipro.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.programs.minipro; +in +{ + options = { + programs.minipro = { + enable = lib.mkEnableOption (lib.mdDoc "minipro") // { + description = lib.mdDoc '' + Installs minipro and its udev rules. + Users of the `plugdev` group can interact with connected MiniPRO chip programmers. + ''; + }; + + package = lib.mkPackageOptionMD pkgs "minipro" { }; + }; + }; + + config = lib.mkIf cfg.enable { + users.groups.plugdev = { }; + environment.systemPackages = [ cfg.package ]; + services.udev.packages = [ cfg.package ]; + }; + + meta = { + maintainers = with lib.maintainers; [ infinidoge ]; + }; +} diff --git a/nixos/modules/programs/neovim.nix b/nixos/modules/programs/neovim.nix index 4562e5a2c29b..3f0e9fc173bd 100644 --- a/nixos/modules/programs/neovim.nix +++ b/nixos/modules/programs/neovim.nix @@ -4,12 +4,8 @@ with lib; let cfg = config.programs.neovim; - - runtime' = filter (f: f.enable) (attrValues cfg.runtime); - - runtime = pkgs.linkFarm "neovim-runtime" (map (x: { name = "etc/${x.target}"; path = x.source; }) runtime'); - -in { +in +{ options.programs.neovim = { enable = mkOption { type = types.bool; @@ -70,7 +66,7 @@ in { configure = mkOption { type = types.attrs; - default = {}; + default = { }; example = literalExpression '' { customRC = ''' @@ -105,7 +101,7 @@ in { }; runtime = mkOption { - default = {}; + default = { }; example = literalExpression '' { "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; } ''; @@ -115,14 +111,15 @@ in { type = with types; attrsOf (submodule ( { name, config, ... }: - { options = { + { + options = { enable = mkOption { type = types.bool; default = true; description = lib.mdDoc '' - Whether this /etc file should be generated. This - option allows specific /etc files to be disabled. + Whether this runtime directory should be generated. This + option allows specific runtime files to be disabled. ''; }; @@ -147,14 +144,9 @@ in { }; - config = { - target = mkDefault name; - source = mkIf (config.text != null) ( - let name' = "neovim-runtime" + baseNameOf name; - in mkDefault (pkgs.writeText name' config.text)); - }; - - })); + config.target = mkDefault name; + } + )); }; }; @@ -165,14 +157,17 @@ in { ]; environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "nvim"); - programs.neovim.finalPackage = pkgs.wrapNeovim cfg.package { - inherit (cfg) viAlias vimAlias withPython3 withNodeJs withRuby; - configure = cfg.configure // { + environment.etc = listToAttrs (attrValues (mapAttrs + (name: value: { + name = "xdg/nvim/${name}"; + value = value // { + target = "xdg/nvim/${value.target}"; + }; + }) + cfg.runtime)); - customRC = (cfg.configure.customRC or "") + '' - set runtimepath^=${runtime}/etc - ''; - }; + programs.neovim.finalPackage = pkgs.wrapNeovim cfg.package { + inherit (cfg) viAlias vimAlias withPython3 withNodeJs withRuby configure; }; }; } diff --git a/nixos/modules/programs/regreet.nix b/nixos/modules/programs/regreet.nix index 89b93737f4a2..f6c750a45bf5 100644 --- a/nixos/modules/programs/regreet.nix +++ b/nixos/modules/programs/regreet.nix @@ -50,7 +50,7 @@ in config = lib.mkIf cfg.enable { services.greetd = { enable = lib.mkDefault true; - settings.default_session.command = lib.mkDefault "${lib.getExe pkgs.cage} -s -- ${lib.getExe cfg.package}"; + settings.default_session.command = lib.mkDefault "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -- ${lib.getExe cfg.package}"; }; environment.etc = { diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix index 1ec698820a8b..7c85d1e7c3d5 100644 --- a/nixos/modules/programs/ssh.nix +++ b/nixos/modules/programs/ssh.nix @@ -26,7 +26,7 @@ let + (if h.publicKey != null then h.publicKey else readFile h.publicKeyFile) )) + "\n"; - knownHostsFiles = [ "/etc/ssh/ssh_known_hosts" "/etc/ssh/ssh_known_hosts2" ] + knownHostsFiles = [ "/etc/ssh/ssh_known_hosts" ] ++ map pkgs.copyPathToStore cfg.knownHostsFiles; in @@ -232,9 +232,8 @@ in description = lib.mdDoc '' Files containing SSH host keys to set as global known hosts. `/etc/ssh/ssh_known_hosts` (which is - generated by {option}`programs.ssh.knownHosts`) and - `/etc/ssh/ssh_known_hosts2` are always - included. + generated by {option}`programs.ssh.knownHosts`) is + always included. ''; example = literalExpression '' [ diff --git a/nixos/modules/programs/tmux.nix b/nixos/modules/programs/tmux.nix index 2e5c8c30e0f8..4f452f1d7f9b 100644 --- a/nixos/modules/programs/tmux.nix +++ b/nixos/modules/programs/tmux.nix @@ -160,7 +160,10 @@ in { default = defaultTerminal; example = "screen-256color"; type = types.str; - description = lib.mdDoc "Set the $TERM variable."; + description = lib.mdDoc '' + Set the $TERM variable. Use tmux-direct if italics or 24bit true color + support is needed. + ''; }; secureSocket = mkOption { diff --git a/nixos/modules/programs/tsm-client.nix b/nixos/modules/programs/tsm-client.nix index 7adff7cd28cb..41560544c2c7 100644 --- a/nixos/modules/programs/tsm-client.nix +++ b/nixos/modules/programs/tsm-client.nix @@ -6,7 +6,7 @@ let inherit (lib.attrsets) attrNames filterAttrs hasAttr mapAttrs mapAttrsToList optionalAttrs; inherit (lib.modules) mkDefault mkIf; inherit (lib.options) literalExpression mkEnableOption mkOption; - inherit (lib.strings) concatStringsSep optionalString toLower; + inherit (lib.strings) concatLines optionalString toLower; inherit (lib.types) addCheck attrsOf lines nonEmptyStr nullOr package path port str strMatching submodule; # Checks if given list of strings contains unique @@ -164,7 +164,7 @@ let mkLine = k: v: k + optionalString (v!="") " ${v}"; lines = mapAttrsToList mkLine attrset; in - concatStringsSep "\n" lines; + concatLines lines; config.stanza = '' server ${config.name} ${config.text} @@ -263,7 +263,7 @@ let ${optionalString (cfg.defaultServername!=null) "defaultserver ${cfg.defaultServername}"} - ${concatStringsSep "\n" (mapAttrsToList (k: v: v.stanza) cfg.servers)} + ${concatLines (mapAttrsToList (k: v: v.stanza) cfg.servers)} ''; in diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix index 0b152e54cf95..6bb21cb3ef66 100644 --- a/nixos/modules/programs/zsh/zsh.nix +++ b/nixos/modules/programs/zsh/zsh.nix @@ -236,6 +236,9 @@ in setopt ${concatStringsSep " " cfg.setOptions} ''} + # Alternative method of determining short and full hostname. + HOST=${config.networking.fqdnOrHostName} + # Setup command line history. # Don't export these, otherwise other shells (bash) will try to use same HISTFILE. SAVEHIST=${toString cfg.histSize} diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index 158c7934195b..45a27029dff1 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -58,6 +58,7 @@ with lib; (mkRemovedOptionModule [ "services" "fourStoreEndpoint" ] "The fourStoreEndpoint module has been removed") (mkRemovedOptionModule [ "services" "fprot" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "frab" ] "The frab module has been removed") + (mkRemovedOptionModule [ "services" "ihatemoney" ] "The ihatemoney module has been removed for lack of downstream maintainer") (mkRemovedOptionModule [ "services" "kippo" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "mailpile" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "marathon" ] "The corresponding package was removed from nixpkgs.") @@ -106,6 +107,7 @@ with lib; (mkRemovedOptionModule [ "services" "openfire" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "riak" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "cryptpad" ] "The corresponding package was removed from nixpkgs.") + (mkRemovedOptionModule [ "services" "rtsp-simple-server" ] "Package has been completely rebranded by upstream as mediamtx, and thus the service and the package were renamed in NixOS as well.") (mkRemovedOptionModule [ "i18n" "inputMethod" "fcitx" ] "The fcitx module has been removed. Plesae use fcitx5 instead") diff --git a/nixos/modules/security/ipa.nix b/nixos/modules/security/ipa.nix new file mode 100644 index 000000000000..7075be95040e --- /dev/null +++ b/nixos/modules/security/ipa.nix @@ -0,0 +1,258 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.security.ipa; + pyBool = x: + if x + then "True" + else "False"; + + ldapConf = pkgs.writeText "ldap.conf" '' + # Turning this off breaks GSSAPI used with krb5 when rdns = false + SASL_NOCANON on + + URI ldaps://${cfg.server} + BASE ${cfg.basedn} + TLS_CACERT /etc/ipa/ca.crt + ''; + nssDb = + pkgs.runCommand "ipa-nssdb" + { + nativeBuildInputs = [pkgs.nss.tools]; + } '' + mkdir -p $out + certutil -d $out -N --empty-password + certutil -d $out -A --empty-password -n "${cfg.realm} IPA CA" -t CT,C,C -i ${cfg.certificate} + ''; +in { + options = { + security.ipa = { + enable = mkEnableOption (lib.mdDoc "FreeIPA domain integration"); + + certificate = mkOption { + type = types.package; + description = lib.mdDoc '' + IPA server CA certificate. + + Use `nix-prefetch-url http://$server/ipa/config/ca.crt` to + obtain the file and the hash. + ''; + example = literalExpression '' + pkgs.fetchurl { + url = http://ipa.example.com/ipa/config/ca.crt; + sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + }; + ''; + }; + + domain = mkOption { + type = types.str; + example = "example.com"; + description = lib.mdDoc "Domain of the IPA server."; + }; + + realm = mkOption { + type = types.str; + example = "EXAMPLE.COM"; + description = lib.mdDoc "Kerberos realm."; + }; + + server = mkOption { + type = types.str; + example = "ipa.example.com"; + description = lib.mdDoc "IPA Server hostname."; + }; + + basedn = mkOption { + type = types.str; + example = "dc=example,dc=com"; + description = lib.mdDoc "Base DN to use when performing LDAP operations."; + }; + + offlinePasswords = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Whether to store offline passwords when the server is down."; + }; + + cacheCredentials = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Whether to cache credentials."; + }; + + ifpAllowedUids = mkOption { + type = types.listOf types.string; + default = ["root"]; + description = lib.mdDoc "A list of users allowed to access the ifp dbus interface."; + }; + + dyndns = { + enable = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Whether to enable FreeIPA automatic hostname updates."; + }; + + interface = mkOption { + type = types.str; + example = "eth0"; + default = "*"; + description = lib.mdDoc "Network interface to perform hostname updates through."; + }; + }; + + chromiumSupport = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Whether to whitelist the FreeIPA domain in Chromium."; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = !config.krb5.enable; + message = "krb5 must be disabled through `krb5.enable` for FreeIPA integration to work."; + } + { + assertion = !config.users.ldap.enable; + message = "ldap must be disabled through `users.ldap.enable` for FreeIPA integration to work."; + } + ]; + + environment.systemPackages = with pkgs; [krb5Full freeipa]; + + environment.etc = { + "ipa/default.conf".text = '' + [global] + basedn = ${cfg.basedn} + realm = ${cfg.realm} + domain = ${cfg.domain} + server = ${cfg.server} + host = ${config.networking.hostName} + xmlrpc_uri = https://${cfg.server}/ipa/xml + enable_ra = True + ''; + + "ipa/nssdb".source = nssDb; + + "krb5.conf".text = '' + [libdefaults] + default_realm = ${cfg.realm} + dns_lookup_realm = false + dns_lookup_kdc = true + rdns = false + ticket_lifetime = 24h + forwardable = true + udp_preference_limit = 0 + + [realms] + ${cfg.realm} = { + kdc = ${cfg.server}:88 + master_kdc = ${cfg.server}:88 + admin_server = ${cfg.server}:749 + default_domain = ${cfg.domain} + pkinit_anchors = FILE:/etc/ipa/ca.crt + } + + [domain_realm] + .${cfg.domain} = ${cfg.realm} + ${cfg.domain} = ${cfg.realm} + ${cfg.server} = ${cfg.realm} + + [dbmodules] + ${cfg.realm} = { + db_library = ${pkgs.freeipa}/lib/krb5/plugins/kdb/ipadb.so + } + ''; + + "openldap/ldap.conf".source = ldapConf; + }; + + environment.etc."chromium/policies/managed/freeipa.json" = mkIf cfg.chromiumSupport { + text = '' + { "AuthServerWhitelist": "*.${cfg.domain}" } + ''; + }; + + system.activationScripts.ipa = stringAfter ["etc"] '' + # libcurl requires a hard copy of the certificate + if ! ${pkgs.diffutils}/bin/diff ${cfg.certificate} /etc/ipa/ca.crt > /dev/null 2>&1; then + rm -f /etc/ipa/ca.crt + cp ${cfg.certificate} /etc/ipa/ca.crt + fi + + if [ ! -f /etc/krb5.keytab ]; then + cat <<EOF + + In order to complete FreeIPA integration, please join the domain by completing the following steps: + 1. Authenticate as an IPA user authorized to join new hosts, e.g. kinit admin@${cfg.realm} + 2. Join the domain and obtain the keytab file: ipa-join + 3. Install the keytab file: sudo install -m 600 krb5.keytab /etc/ + 4. Restart sssd systemd service: sudo systemctl restart sssd + + EOF + fi + ''; + + services.sssd.config = '' + [domain/${cfg.domain}] + id_provider = ipa + auth_provider = ipa + access_provider = ipa + chpass_provider = ipa + + ipa_domain = ${cfg.domain} + ipa_server = _srv_, ${cfg.server} + ipa_hostname = ${config.networking.hostName}.${cfg.domain} + + cache_credentials = ${pyBool cfg.cacheCredentials} + krb5_store_password_if_offline = ${pyBool cfg.offlinePasswords} + ${optionalString ((toLower cfg.domain) != (toLower cfg.realm)) + "krb5_realm = ${cfg.realm}"} + + dyndns_update = ${pyBool cfg.dyndns.enable} + dyndns_iface = ${cfg.dyndns.interface} + + ldap_tls_cacert = /etc/ipa/ca.crt + ldap_user_extra_attrs = mail:mail, sn:sn, givenname:givenname, telephoneNumber:telephoneNumber, lock:nsaccountlock + + [sssd] + debug_level = 65510 + services = nss, sudo, pam, ssh, ifp + domains = ${cfg.domain} + + [nss] + homedir_substring = /home + + [pam] + pam_pwd_expiration_warning = 3 + pam_verbosity = 3 + + [sudo] + debug_level = 65510 + + [autofs] + + [ssh] + + [pac] + + [ifp] + user_attributes = +mail, +telephoneNumber, +givenname, +sn, +lock + allowed_uids = ${concatStringsSep ", " cfg.ifpAllowedUids} + ''; + + services.ntp.servers = singleton cfg.server; + services.sssd.enable = true; + services.ntp.enable = true; + + security.pki.certificateFiles = singleton cfg.certificate; + }; +} diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix index bc2d79ac10ac..08a2967e9c7f 100644 --- a/nixos/modules/services/backup/borgbackup.nix +++ b/nixos/modules/services/backup/borgbackup.nix @@ -66,6 +66,7 @@ let ${mkKeepArgs cfg} \ ${optionalString (cfg.prune.prefix != null) "--glob-archives ${escapeShellArg "${cfg.prune.prefix}*"}"} \ $extraPruneArgs + borg compact $extraArgs $extraCompactArgs ${cfg.postPrune} ''); @@ -638,6 +639,15 @@ in { example = "--save-space"; }; + extraCompactArgs = mkOption { + type = types.str; + description = lib.mdDoc '' + Additional arguments for {command}`borg compact`. + Can also be set at runtime using `$extraCompactArgs`. + ''; + default = ""; + example = "--cleanup-commits"; + }; }; } )); diff --git a/nixos/modules/services/backup/borgmatic.nix b/nixos/modules/services/backup/borgmatic.nix index e7cd6ae4bb57..5ee036e68c7b 100644 --- a/nixos/modules/services/backup/borgmatic.nix +++ b/nixos/modules/services/backup/borgmatic.nix @@ -72,5 +72,8 @@ in cfg.configurations; systemd.packages = [ pkgs.borgmatic ]; + + # Workaround: https://github.com/NixOS/nixpkgs/issues/81138 + systemd.timers.borgmatic.wantedBy = [ "timers.target" ]; }; } diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix index 8e935d621be4..eebacb3f3ef3 100644 --- a/nixos/modules/services/cluster/kubernetes/kubelet.nix +++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix @@ -63,6 +63,7 @@ in (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "") (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "allowPrivileged" ] "") (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "networkPlugin" ] "") + (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "containerRuntime" ] "") ]; ###### interface @@ -134,12 +135,6 @@ in }; }; - containerRuntime = mkOption { - description = lib.mdDoc "Which container runtime type to use"; - type = enum ["docker" "remote"]; - default = "remote"; - }; - containerRuntimeEndpoint = mkOption { description = lib.mdDoc "Endpoint at which to find the container runtime api interface/socket"; type = str; @@ -331,7 +326,6 @@ in ${optionalString (cfg.tlsKeyFile != null) "--tls-private-key-file=${cfg.tlsKeyFile}"} \ ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \ - --container-runtime=${cfg.containerRuntime} \ --container-runtime-endpoint=${cfg.containerRuntimeEndpoint} \ --cgroup-driver=systemd \ ${cfg.extraOpts} diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix index 5fb715f4d779..1879fef9666f 100644 --- a/nixos/modules/services/computing/boinc/client.nix +++ b/nixos/modules/services/computing/boinc/client.nix @@ -6,7 +6,7 @@ let cfg = config.services.boinc; allowRemoteGuiRpcFlag = optionalString cfg.allowRemoteGuiRpc "--allow_remote_gui_rpc"; - fhsEnv = pkgs.buildFHSUserEnv { + fhsEnv = pkgs.buildFHSEnv { name = "boinc-fhs-env"; targetPkgs = pkgs': [ cfg.package ] ++ cfg.extraEnvPackages; runScript = "/bin/boinc_client"; diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix index 5666199c4845..595374ea1e5b 100644 --- a/nixos/modules/services/continuous-integration/buildbot/master.nix +++ b/nixos/modules/services/continuous-integration/buildbot/master.nix @@ -8,7 +8,8 @@ let cfg = config.services.buildbot-master; opt = options.services.buildbot-master; - python = cfg.package.pythonModule; + package = pkgs.python3.pkgs.toPythonModule cfg.package; + python = package.pythonModule; escapeStr = escape [ "'" ]; @@ -212,10 +213,10 @@ in { package = mkOption { type = types.package; - default = pkgs.python3Packages.buildbot-full; - defaultText = literalExpression "pkgs.python3Packages.buildbot-full"; + default = pkgs.buildbot-full; + defaultText = literalExpression "pkgs.buildbot-full"; description = lib.mdDoc "Package to use for buildbot."; - example = literalExpression "pkgs.python3Packages.buildbot"; + example = literalExpression "pkgs.buildbot"; }; packages = mkOption { @@ -255,7 +256,7 @@ in { 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}"; + environment.PYTHONPATH = "${python.withPackages (self: cfg.pythonPackages self ++ [ package ])}/${python.sitePackages}"; preStart = '' mkdir -vp "${cfg.buildbotDir}" diff --git a/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixos/modules/services/continuous-integration/buildbot/worker.nix index 52c41c4a7584..7e78b8935f81 100644 --- a/nixos/modules/services/continuous-integration/buildbot/worker.nix +++ b/nixos/modules/services/continuous-integration/buildbot/worker.nix @@ -8,7 +8,8 @@ let cfg = config.services.buildbot-worker; opt = options.services.buildbot-worker; - python = cfg.package.pythonModule; + package = pkgs.python3.pkgs.toPythonModule cfg.package; + python = package.pythonModule; tacFile = pkgs.writeText "aur-buildbot-worker.tac" '' import os @@ -129,7 +130,7 @@ in { package = mkOption { type = types.package; - default = pkgs.python3Packages.buildbot-worker; + default = pkgs.buildbot-worker; defaultText = literalExpression "pkgs.python3Packages.buildbot-worker"; description = lib.mdDoc "Package to use for buildbot worker."; example = literalExpression "pkgs.python2Packages.buildbot-worker"; @@ -168,7 +169,7 @@ in { after = [ "network.target" "buildbot-master.service" ]; wantedBy = [ "multi-user.target" ]; path = cfg.packages; - environment.PYTHONPATH = "${python.withPackages (p: [ cfg.package ])}/${python.sitePackages}"; + environment.PYTHONPATH = "${python.withPackages (p: [ package ])}/${python.sitePackages}"; preStart = '' mkdir -vp "${cfg.buildbotDir}/info" diff --git a/nixos/modules/services/development/lorri.nix b/nixos/modules/services/development/lorri.nix index 8c64e3d9a560..74f56f5890fc 100644 --- a/nixos/modules/services/development/lorri.nix +++ b/nixos/modules/services/development/lorri.nix @@ -50,6 +50,6 @@ in { }; }; - environment.systemPackages = [ cfg.package ]; + environment.systemPackages = [ cfg.package pkgs.direnv ]; }; } diff --git a/nixos/modules/services/hardware/auto-cpufreq.nix b/nixos/modules/services/hardware/auto-cpufreq.nix index 9698e72eb31e..df7c01ae54ef 100644 --- a/nixos/modules/services/hardware/auto-cpufreq.nix +++ b/nixos/modules/services/hardware/auto-cpufreq.nix @@ -2,10 +2,26 @@ with lib; let cfg = config.services.auto-cpufreq; + cfgFilename = "auto-cpufreq.conf"; + cfgFile = format.generate cfgFilename cfg.settings; + + format = pkgs.formats.ini {}; in { options = { services.auto-cpufreq = { enable = mkEnableOption (lib.mdDoc "auto-cpufreq daemon"); + + settings = mkOption { + description = lib.mdDoc '' + Configuration for `auto-cpufreq`. + + See its [example configuration file] for supported settings. + [example configuration file]: https://github.com/AdnanHodzic/auto-cpufreq/blob/master/auto-cpufreq.conf-example + ''; + + default = {}; + type = types.submodule { freeformType = format.type; }; + }; }; }; @@ -18,6 +34,11 @@ in { # Workaround for https://github.com/NixOS/nixpkgs/issues/81138 wantedBy = [ "multi-user.target" ]; path = with pkgs; [ bash coreutils ]; + + serviceConfig.ExecStart = [ + "" + "${lib.getExe pkgs.auto-cpufreq} --config ${cfgFile}" + ]; }; }; }; diff --git a/nixos/modules/services/home-automation/esphome.nix b/nixos/modules/services/home-automation/esphome.nix new file mode 100644 index 000000000000..d7dbb6f0b90e --- /dev/null +++ b/nixos/modules/services/home-automation/esphome.nix @@ -0,0 +1,136 @@ +{ config, lib, pkgs, ... }: + +let + inherit (lib) + literalExpression + maintainers + mkEnableOption + mkIf + mkOption + mdDoc + types + ; + + cfg = config.services.esphome; + + stateDir = "/var/lib/esphome"; + + esphomeParams = + if cfg.enableUnixSocket + then "--socket /run/esphome/esphome.sock" + else "--address ${cfg.address} --port ${toString cfg.port}"; +in +{ + meta.maintainers = with maintainers; [ oddlama ]; + + options.services.esphome = { + enable = mkEnableOption (mdDoc "esphome"); + + package = mkOption { + type = types.package; + default = pkgs.esphome; + defaultText = literalExpression "pkgs.esphome"; + description = mdDoc "The package to use for the esphome command."; + }; + + enableUnixSocket = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Listen on a unix socket `/run/esphome/esphome.sock` instead of the TCP port."; + }; + + address = mkOption { + type = types.str; + default = "localhost"; + description = mdDoc "esphome address"; + }; + + port = mkOption { + type = types.port; + default = 6052; + description = mdDoc "esphome port"; + }; + + openFirewall = mkOption { + default = false; + type = types.bool; + description = mdDoc "Whether to open the firewall for the specified port."; + }; + + allowedDevices = mkOption { + default = ["char-ttyS" "char-ttyUSB"]; + example = ["/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0"]; + description = lib.mdDoc '' + A list of device nodes to which {command}`esphome` has access to. + Refer to DeviceAllow in systemd.resource-control(5) for more information. + Beware that if a device is referred to by an absolute path instead of a device category, + it will only allow devices that already are plugged in when the service is started. + ''; + type = types.listOf types.str; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall && !cfg.enableUnixSocket) [cfg.port]; + + systemd.services.esphome = { + description = "ESPHome dashboard"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + path = [cfg.package]; + + # platformio fails to determine the home directory when using DynamicUser + environment.PLATFORMIO_CORE_DIR = "${stateDir}/.platformio"; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/esphome dashboard ${esphomeParams} ${stateDir}"; + DynamicUser = true; + User = "esphome"; + Group = "esphome"; + WorkingDirectory = stateDir; + StateDirectory = "esphome"; + StateDirectoryMode = "0750"; + Restart = "on-failure"; + RuntimeDirectory = mkIf cfg.enableUnixSocket "esphome"; + RuntimeDirectoryMode = "0750"; + + # Hardening + CapabilityBoundingSet = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + DevicePolicy = "closed"; + DeviceAllow = map (d: "${d} rw") cfg.allowedDevices; + SupplementaryGroups = ["dialout"]; + #NoNewPrivileges = true; # Implied by DynamicUser + PrivateUsers = true; + #PrivateTmp = true; # Implied by DynamicUser + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + ProtectSystem = "strict"; + #RemoveIPC = true; # Implied by DynamicUser + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + "AF_UNIX" + ]; + RestrictNamespaces = false; # Required by platformio for chroot + RestrictRealtime = true; + #RestrictSUIDSGID = true; # Implied by DynamicUser + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "@mount" # Required by platformio for chroot + ]; + UMask = "0077"; + }; + }; + }; +} diff --git a/nixos/modules/services/mail/maddy.nix b/nixos/modules/services/mail/maddy.nix index 5f3a9b56292d..d0b525bcb002 100644 --- a/nixos/modules/services/mail/maddy.nix +++ b/nixos/modules/services/mail/maddy.nix @@ -228,8 +228,8 @@ in { default = []; description = lib.mdDoc '' List of IMAP accounts which get automatically created. Note that for - a complete setup, user credentials for these accounts are required too - and can be created using the command `maddyctl creds`. + a complete setup, user credentials for these accounts are required + and can be created using the `ensureCredentials` option. This option does not delete accounts which are not (anymore) listed. ''; example = [ @@ -238,6 +238,33 @@ in { ]; }; + ensureCredentials = mkOption { + default = {}; + description = lib.mdDoc '' + List of user accounts which get automatically created if they don't + exist yet. Note that for a complete setup, corresponding mail boxes + have to get created using the `ensureAccounts` option. + This option does not delete accounts which are not (anymore) listed. + ''; + example = { + "user1@localhost".passwordFile = /secrets/user1-localhost; + "user2@localhost".passwordFile = /secrets/user2-localhost; + }; + type = types.attrsOf (types.submodule { + options = { + passwordFile = mkOption { + type = types.path; + example = "/path/to/file"; + default = null; + description = lib.mdDoc '' + Specifies the path to a file containing the + clear text password for the user. + ''; + }; + }; + }); + }; + }; }; @@ -265,6 +292,13 @@ in { fi '') cfg.ensureAccounts} ''} + ${optionalString (cfg.ensureCredentials != {}) '' + ${concatStringsSep "\n" (mapAttrsToList (name: cfg: '' + if ! ${pkgs.maddy}/bin/maddyctl creds list | grep "${name}"; then + ${pkgs.maddy}/bin/maddyctl creds create --password $(cat ${escapeShellArg cfg.passwordFile}) ${name} + fi + '') cfg.ensureCredentials)} + ''} ''; serviceConfig = { Type = "oneshot"; diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix index 7b6d82219298..3aaec145930d 100644 --- a/nixos/modules/services/mail/roundcube.nix +++ b/nixos/modules/services/mail/roundcube.nix @@ -7,7 +7,7 @@ let fpm = config.services.phpfpm.pools.roundcube; localDB = cfg.database.host == "localhost"; user = cfg.database.username; - phpWithPspell = pkgs.php80.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled); + phpWithPspell = pkgs.php81.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled); in { options.services.roundcube = { diff --git a/nixos/modules/services/misc/gitit.nix b/nixos/modules/services/misc/gitit.nix deleted file mode 100644 index 0fafa76b5487..000000000000 --- a/nixos/modules/services/misc/gitit.nix +++ /dev/null @@ -1,725 +0,0 @@ -{ 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/" + ghc.targetPrefix + ghc.haskellCompilerName + "/" + 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 = lib.mdDoc "Enable the gitit service."; - }; - - haskellPackages = mkOption { - default = pkgs.haskellPackages; - defaultText = literalExpression "pkgs.haskellPackages"; - example = literalExpression "pkgs.haskell.packages.ghc784"; - description = lib.mdDoc "haskellPackages used to build gitit and plugins."; - }; - - extraPackages = mkOption { - type = types.functionTo (types.listOf types.package); - default = self: []; - example = literalExpression '' - haskellPackages: [ - haskellPackages.wreq - ] - ''; - description = lib.mdDoc '' - Extra packages available to ghc when running gitit. The - value must be a function which receives the attrset defined - in {var}`haskellPackages` as the sole argument. - ''; - }; - - address = mkOption { - type = types.str; - default = "0.0.0.0"; - description = lib.mdDoc "IP address on which the web server will listen."; - }; - - port = mkOption { - type = types.int; - default = 5001; - description = lib.mdDoc "Port on which the web server will run."; - }; - - wikiTitle = mkOption { - type = types.str; - default = "Gitit!"; - description = lib.mdDoc "The wiki title."; - }; - - repositoryType = mkOption { - type = types.enum ["git" "darcs" "mercurial"]; - default = "git"; - description = lib.mdDoc "Specifies the type of repository used for wiki content."; - }; - - repositoryPath = mkOption { - type = types.path; - default = homeDir + "/wiki"; - description = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - '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 = lib.mdDoc '' - 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 = lib.mdDoc '' - Number of minutes of inactivity before a session expires. - ''; - }; - - staticDir = mkOption { - type = types.path; - default = gititShared + "/data/static"; - description = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc "Path where rendered pages will be cached."; - }; - - maxUploadSize = mkOption { - type = types.str; - default = "1000K"; - description = lib.mdDoc '' - 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 = lib.mdDoc "Specifies an upper limit on the size (in bytes) of pages."; - }; - - debugMode = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc "Causes debug information to be logged while gitit is running."; - }; - - compressResponses = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc "Specifies whether HTTP responses should be compressed."; - }; - - mimeTypesFile = mkOption { - type = types.path; - default = "/etc/mime/types.info"; - description = lib.mdDoc '' - 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: - ``` - video/x-ms-wmx wmx - ``` - If the file is not found, some simple defaults will be used. - ''; - }; - - useReCaptcha = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc "RPX account access key."; - }; - - mailCommand = mkOption { - type = types.str; - default = "sendmail %s"; - description = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc "Number of days to be included in feeds."; - }; - - feedRefreshTime = mkOption { - type = types.int; - default = 60; - description = lib.mdDoc "Number of minutes to cache feeds before refreshing."; - }; - - pdfExport = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc '' - 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 = lib.mdDoc "OAuth client ID"; - }; - - oauthClientSecret = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc "OAuth client secret"; - }; - - oauthCallback = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc "OAuth callback URL"; - }; - - oauthAuthorizeEndpoint = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc "OAuth authorize endpoint"; - }; - - oauthAccessTokenEndpoint = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc "OAuth access token endpoint"; - }; - - githubOrg = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc "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 - darcs initialize - echo "${gm}" > _darcs/prefs/email - '' - else if repositoryType == "mercurial" then - '' - if [ ! -d .hg ] - then - hg init - cat >> .hg/hgrc <<NAMED -[ui] -username = gitit ${gm} -NAMED - '' - else - '' - if [ ! -d .git ] - then - git init - git config user.email "${gm}" - 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/nixos/modules/services/misc/gpsd.nix b/nixos/modules/services/misc/gpsd.nix index 9b03b6f9662e..ce0f9bb3ba28 100644 --- a/nixos/modules/services/misc/gpsd.nix +++ b/nixos/modules/services/misc/gpsd.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, utils, ... }: with lib; @@ -8,12 +8,15 @@ let gid = config.ids.gids.gpsd; cfg = config.services.gpsd; -in - -{ +in { ###### interface + imports = [ + (lib.mkRemovedOptionModule [ "services" "gpsd" "device" ] + "Use `services.gpsd.devices` instead.") + ]; + options = { services.gpsd = { @@ -26,13 +29,17 @@ in ''; }; - device = mkOption { - type = types.str; - default = "/dev/ttyUSB0"; + devices = mkOption { + type = types.listOf types.str; + default = [ "/dev/ttyUSB0" ]; description = lib.mdDoc '' - A device may be a local serial device for GPS input, or a URL of the form: - `[{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]` - in which case it specifies an input source for DGPS or ntrip data. + List of devices that `gpsd` should subscribe to. + + A device may be a local serial device for GPS input, or a + URL of the form: + `[{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]` in + which case it specifies an input source for DGPS or ntrip + data. ''; }; @@ -89,17 +96,16 @@ in }; - ###### implementation config = mkIf cfg.enable { - users.users.gpsd = - { inherit uid; - group = "gpsd"; - description = "gpsd daemon user"; - home = "/var/empty"; - }; + users.users.gpsd = { + inherit uid; + group = "gpsd"; + description = "gpsd daemon user"; + home = "/var/empty"; + }; users.groups.gpsd = { inherit gid; }; @@ -109,13 +115,15 @@ in after = [ "network.target" ]; serviceConfig = { Type = "forking"; - ExecStart = '' + ExecStart = let + devices = utils.escapeSystemdExecArgs cfg.devices; + in '' ${pkgs.gpsd}/sbin/gpsd -D "${toString cfg.debugLevel}" \ -S "${toString cfg.port}" \ ${optionalString cfg.readonly "-b"} \ ${optionalString cfg.nowait "-n"} \ ${optionalString cfg.listenany "-G"} \ - "${cfg.device}" + ${devices} ''; }; }; diff --git a/nixos/modules/services/misc/pufferpanel.nix b/nixos/modules/services/misc/pufferpanel.nix new file mode 100644 index 000000000000..78ec35646907 --- /dev/null +++ b/nixos/modules/services/misc/pufferpanel.nix @@ -0,0 +1,176 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.services.pufferpanel; +in +{ + options.services.pufferpanel = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = lib.mdDoc '' + Whether to enable PufferPanel game management server. + + Note that [PufferPanel templates] and binaries downloaded by PufferPanel + expect [FHS environment]. It is possible to set {option}`package` option + to use PufferPanel wrapper with FHS environment. For example, to use + `Download Game from Steam` and `Download Java` template operations: + ```Nix + { lib, pkgs, ... }: { + services.pufferpanel = { + enable = true; + extraPackages = with pkgs; [ bash curl gawk gnutar gzip ]; + package = pkgs.buildFHSUserEnv { + name = "pufferpanel-fhs"; + runScript = lib.getExe pkgs.pufferpanel; + targetPkgs = pkgs': with pkgs'; [ icu openssl zlib ]; + }; + }; + } + ``` + + [PufferPanel templates]: https://github.com/PufferPanel/templates + [FHS environment]: https://wikipedia.org/wiki/Filesystem_Hierarchy_Standard + ''; + }; + + package = lib.mkPackageOptionMD pkgs "pufferpanel" { }; + + extraGroups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "podman" ]; + description = lib.mdDoc '' + Additional groups for the systemd service. + ''; + }; + + extraPackages = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + example = lib.literalExpression "[ pkgs.jre ]"; + description = lib.mdDoc '' + Packages to add to the PATH environment variable. Both the {file}`bin` + and {file}`sbin` subdirectories of each package are added. + ''; + }; + + environment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + example = lib.literalExpression '' + { + PUFFER_WEB_HOST = ":8080"; + PUFFER_DAEMON_SFTP_HOST = ":5657"; + PUFFER_DAEMON_CONSOLE_BUFFER = "1000"; + PUFFER_DAEMON_CONSOLE_FORWARD = "true"; + PUFFER_PANEL_REGISTRATIONENABLED = "false"; + } + ''; + description = lib.mdDoc '' + Environment variables to set for the service. Secrets should be + specified using {option}`environmentFile`. + + Refer to the [PufferPanel source code][] for the list of available + configuration options. Variable name is an upper-cased configuration + entry name with underscores instead of dots, prefixed with `PUFFER_`. + For example, `panel.settings.companyName` entry can be set using + {env}`PUFFER_PANEL_SETTINGS_COMPANYNAME`. + + When running with panel enabled (configured with `PUFFER_PANEL_ENABLE` + environment variable), it is recommended disable registration using + `PUFFER_PANEL_REGISTRATIONENABLED` environment variable (registration is + enabled by default). To create the initial administrator user, run + {command}`pufferpanel --workDir /var/lib/pufferpanel user add --admin`. + + Some options override corresponding settings set via web interface (e.g. + `PUFFER_PANEL_REGISTRATIONENABLED`). Those options can be temporarily + toggled or set in settings but do not persist between restarts. + + [PufferPanel source code]: https://github.com/PufferPanel/PufferPanel/blob/master/config/entries.go + ''; + }; + + environmentFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = lib.mdDoc '' + File to load environment variables from. Loaded variables override + values set in {option}`environment`. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.pufferpanel = { + description = "PufferPanel game management server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + path = cfg.extraPackages; + environment = cfg.environment; + + # Note that we export environment variables for service directories if the + # value is not set. An empty environment variable is considered to be set. + # E.g. + # export PUFFER_LOGS=${PUFFER_LOGS-$LOGS_DIRECTORY} + # would set PUFFER_LOGS to $LOGS_DIRECTORY if PUFFER_LOGS environment + # variable is not defined. + script = '' + ${lib.concatLines (lib.mapAttrsToList (name: value: '' + export ${name}="''${${name}-${value}}" + '') { + PUFFER_LOGS = "$LOGS_DIRECTORY"; + PUFFER_DAEMON_DATA_CACHE = "$CACHE_DIRECTORY"; + PUFFER_DAEMON_DATA_SERVERS = "$STATE_DIRECTORY/servers"; + PUFFER_DAEMON_DATA_BINARIES = "$STATE_DIRECTORY/binaries"; + })} + exec ${lib.getExe cfg.package} run --workDir "$STATE_DIRECTORY" + ''; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + + UMask = "0077"; + + SupplementaryGroups = cfg.extraGroups; + + StateDirectory = "pufferpanel"; + StateDirectoryMode = "0700"; + CacheDirectory = "pufferpanel"; + CacheDirectoryMode = "0700"; + LogsDirectory = "pufferpanel"; + LogsDirectoryMode = "0700"; + + EnvironmentFile = cfg.environmentFile; + + # Command "pufferpanel shutdown --pid $MAINPID" sends SIGTERM (code 15) + # to the main process and waits for termination. This is essentially + # KillMode=mixed we are using here. See + # https://freedesktop.org/software/systemd/man/systemd.kill.html#KillMode= + KillMode = "mixed"; + + DynamicUser = true; + ProtectHome = true; + ProtectProc = "invisible"; + ProtectClock = true; + ProtectHostname = true; + ProtectControlGroups = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + PrivateUsers = true; + PrivateDevices = true; + RestrictRealtime = true; + RestrictNamespaces = [ "user" "mnt" ]; # allow buildFHSUserEnv + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; + LockPersonality = true; + DeviceAllow = [ "" ]; + DevicePolicy = "closed"; + CapabilityBoundingSet = [ "" ]; + }; + }; + }; + + meta.maintainers = [ lib.maintainers.tie ]; +} diff --git a/nixos/modules/services/monitoring/grafana-agent.nix b/nixos/modules/services/monitoring/grafana-agent.nix index 270d888afb78..b7761c34fe51 100644 --- a/nixos/modules/services/monitoring/grafana-agent.nix +++ b/nixos/modules/services/monitoring/grafana-agent.nix @@ -140,7 +140,7 @@ in # We can't use Environment=HOSTNAME=%H, as it doesn't include the domain part. export HOSTNAME=$(< /proc/sys/kernel/hostname) - exec ${cfg.package}/bin/agent -config.expand-env -config.file ${configFile} + exec ${lib.getExe cfg.package} -config.expand-env -config.file ${configFile} ''; serviceConfig = { Restart = "always"; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix b/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix index 0c5648c14149..50e1321a1e9c 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix @@ -4,12 +4,12 @@ with lib; let cfg = config.services.prometheus.exporters.smartctl; - args = concatStrings [ - "--web.listen-address=\"${cfg.listenAddress}:${toString cfg.port}\" " - "--smartctl.path=\"${pkgs.smartmontools}/bin/smartctl\" " - "--smartctl.interval=\"${cfg.maxInterval}\" " - "${concatMapStringsSep " " (device: "--smartctl.device=${device}") cfg.devices}" - ]; + args = lib.escapeShellArgs ([ + "--web.listen-address=${cfg.listenAddress}:${toString cfg.port}" + "--smartctl.path=${pkgs.smartmontools}/bin/smartctl" + "--smartctl.interval=${cfg.maxInterval}" + ] ++ map (device: "--smartctl.device=${device}") cfg.devices + ++ cfg.extraFlags); in { port = 9633; diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix index 0cb0e126d4c5..2537bb1b8d80 100644 --- a/nixos/modules/services/network-filesystems/kubo.nix +++ b/nixos/modules/services/network-filesystems/kubo.nix @@ -22,6 +22,18 @@ let configFile = settingsFormat.generate "kubo-config.json" customizedConfig; + # Create a fake repo containing only the file "api". + # $IPFS_PATH will point to this directory instead of the real one. + # For some reason the Kubo CLI tools insist on reading the + # config file when it exists. But the Kubo daemon sets the file + # permissions such that only the ipfs user is allowed to read + # this file. This prevents normal users from talking to the daemon. + # To work around this terrible design, create a fake repo with no + # config file, only an api file and everything should work as expected. + fakeKuboRepo = pkgs.writeTextDir "api" '' + /unix/run/ipfs.sock + ''; + kuboFlags = utils.escapeSystemdExecArgs ( optional cfg.autoMount "--mount" ++ optional cfg.enableGC "--enable-gc" ++ @@ -38,6 +50,22 @@ let splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw); + multiaddrsToListenStreams = addrIn: + let + addrs = if builtins.typeOf addrIn == "list" + then addrIn else [ addrIn ]; + unfilteredResult = map multiaddrToListenStream addrs; + in + builtins.filter (addr: addr != null) unfilteredResult; + + multiaddrsToListenDatagrams = addrIn: + let + addrs = if builtins.typeOf addrIn == "list" + then addrIn else [ addrIn ]; + unfilteredResult = map multiaddrToListenDatagram addrs; + in + builtins.filter (addr: addr != null) unfilteredResult; + multiaddrToListenStream = addrRaw: let addr = splitMulitaddr addrRaw; @@ -154,13 +182,18 @@ in options = { Addresses.API = mkOption { - type = types.str; - default = "/ip4/127.0.0.1/tcp/5001"; - description = lib.mdDoc "Where Kubo exposes its API to"; + type = types.oneOf [ types.str (types.listOf types.str) ]; + default = [ ]; + description = lib.mdDoc '' + Multiaddr or array of multiaddrs describing the address to serve the local HTTP API on. + In addition to the multiaddrs listed here, the daemon will also listen on a Unix domain socket. + To allow the ipfs CLI tools to communicate with the daemon over that socket, + add your user to the correct group, e.g. `users.users.alice.extraGroups = [ config.services.kubo.group ];` + ''; }; Addresses.Gateway = mkOption { - type = types.str; + type = types.oneOf [ types.str (types.listOf types.str) ]; default = "/ip4/127.0.0.1/tcp/8080"; description = lib.mdDoc "Where the IPFS Gateway can be reached"; }; @@ -248,7 +281,7 @@ in ]; environment.systemPackages = [ cfg.package ]; - environment.variables.IPFS_PATH = cfg.dataDir; + environment.variables.IPFS_PATH = fakeKuboRepo; # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000; @@ -319,6 +352,10 @@ in # change when the changes are applied. Whyyyyyy..... ipfs --offline config replace - ''; + postStop = mkIf cfg.autoMount '' + # After an unclean shutdown the fuse mounts at cfg.ipnsMountDir and cfg.ipfsMountDir are locked + umount --quiet '${cfg.ipnsMountDir}' '${cfg.ipfsMountDir}' || true + ''; serviceConfig = { ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${kuboFlags}" ]; User = cfg.user; @@ -334,27 +371,23 @@ in wantedBy = [ "sockets.target" ]; socketConfig = { ListenStream = - let - fromCfg = multiaddrToListenStream cfg.settings.Addresses.Gateway; - in - [ "" ] ++ lib.optional (fromCfg != null) fromCfg; + [ "" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.Gateway); ListenDatagram = - let - fromCfg = multiaddrToListenDatagram cfg.settings.Addresses.Gateway; - in - [ "" ] ++ lib.optional (fromCfg != null) fromCfg; + [ "" ] ++ (multiaddrsToListenDatagrams cfg.settings.Addresses.Gateway); }; }; systemd.sockets.ipfs-api = { wantedBy = [ "sockets.target" ]; - # We also include "%t/ipfs.sock" because there is no way to put the "%t" - # in the multiaddr. - socketConfig.ListenStream = - let - fromCfg = multiaddrToListenStream cfg.settings.Addresses.API; - in - [ "" "%t/ipfs.sock" ] ++ lib.optional (fromCfg != null) fromCfg; + socketConfig = { + # We also include "%t/ipfs.sock" because there is no way to put the "%t" + # in the multiaddr. + ListenStream = + [ "" "%t/ipfs.sock" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.API); + SocketMode = "0660"; + SocketUser = cfg.user; + SocketGroup = cfg.group; + }; }; }; diff --git a/nixos/modules/services/network-filesystems/openafs/server.nix b/nixos/modules/services/network-filesystems/openafs/server.nix index 1c615d3bfb64..ad0fd7835670 100644 --- a/nixos/modules/services/network-filesystems/openafs/server.nix +++ b/nixos/modules/services/network-filesystems/openafs/server.nix @@ -4,7 +4,8 @@ with import ./lib.nix { inherit config lib pkgs; }; let - inherit (lib) concatStringsSep literalExpression mkIf mkOption optionalString types; + inherit (lib) concatStringsSep literalExpression mkIf mkOption mkEnableOption + optionalString types; bosConfig = pkgs.writeText "BosConfig" ('' restrictmode 1 @@ -24,9 +25,15 @@ let 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) '' + '') + (optionalString (cfg.roles.database.enable && cfg.roles.backup.enable && (!cfg.roles.backup.enableFabs)) '' bnode simple buserver 1 - parm ${openafsSrv}/libexec/openafs/buserver ${cfg.roles.backup.buserverArgs} ${optionalString (cfg.roles.backup.cellServDB != []) "-cellservdb /etc/openafs/backup/"} + parm ${openafsSrv}/libexec/openafs/buserver ${cfg.roles.backup.buserverArgs} ${optionalString useBuCellServDB "-cellservdb /etc/openafs/backup/"} + end + '') + (optionalString (cfg.roles.database.enable && + cfg.roles.backup.enable && + cfg.roles.backup.enableFabs) '' + bnode simple buserver 1 + parm ${lib.getBin pkgs.fabs}/bin/fabsys server --config ${fabsConfFile} ${cfg.roles.backup.fabsArgs} end '')); @@ -34,12 +41,27 @@ let pkgs.writeText "NetInfo" ((concatStringsSep "\nf " cfg.advertisedAddresses) + "\n") else null; - buCellServDB = pkgs.writeText "backup-cellServDB-${cfg.cellName}" (mkCellServDB cfg.cellName cfg.roles.backup.cellServDB); + buCellServDB = pkgs.writeText "backup-cellServDB-${cfg.cellName}" + (mkCellServDB cfg.cellName cfg.roles.backup.cellServDB); + + useBuCellServDB = (cfg.roles.backup.cellServDB != []) && (!cfg.roles.backup.enableFabs); cfg = config.services.openafsServer; udpSizeStr = toString cfg.udpPacketSize; + fabsConfFile = pkgs.writeText "fabs.yaml" (builtins.toJSON ({ + afs = { + aklog = cfg.package + "/bin/aklog"; + cell = cfg.cellName; + dumpscan = cfg.package + "/bin/afsdump_scan"; + fs = cfg.package + "/bin/fs"; + pts = cfg.package + "/bin/pts"; + vos = cfg.package + "/bin/vos"; + }; + k5start.command = (lib.getBin pkgs.kstart) + "/bin/k5start"; + } // cfg.roles.backup.fabsExtraConfig)); + in { options = { @@ -80,8 +102,8 @@ in { }; package = mkOption { - default = pkgs.openafs.server or pkgs.openafs; - defaultText = literalExpression "pkgs.openafs.server or pkgs.openafs"; + default = pkgs.openafs; + defaultText = literalExpression "pkgs.openafs"; type = types.package; description = lib.mdDoc "OpenAFS package for the server binaries"; }; @@ -154,16 +176,20 @@ in { }; backup = { - enable = mkOption { - default = false; - type = types.bool; - description = lib.mdDoc '' - Backup server role. Use in conjunction with the - `database` role to maintain the Backup - Database. Normally only used in conjunction with tape storage - or IBM's Tivoli Storage Manager. - ''; - }; + enable = mkEnableOption (lib.mdDoc '' + Backup server role. When using OpenAFS built-in buserver, use in conjunction with the + `database` role to maintain the Backup + Database. Normally only used in conjunction with tape storage + or IBM's Tivoli Storage Manager. + + For a modern backup server, enable this role and see + {option}`enableFabs`. + ''); + + enableFabs = mkEnableOption (lib.mdDoc '' + FABS, the flexible AFS backup system. It stores volumes as dump files, relying on other + pre-existing backup solutions for handling them. + ''); buserverArgs = mkOption { default = ""; @@ -181,6 +207,30 @@ in { other database server machines. ''; }; + + fabsArgs = mkOption { + default = ""; + type = types.str; + description = lib.mdDoc '' + Arguments to the fabsys process. See + {manpage}`fabsys_server(1)` and + {manpage}`fabsys_config(1)`. + ''; + }; + + fabsExtraConfig = mkOption { + default = {}; + type = types.attrs; + description = lib.mdDoc '' + Additional configuration parameters for the FABS backup server. + ''; + example = literalExpression '' + { + afs.localauth = true; + afs.keytab = config.sops.secrets.fabsKeytab.path; + } + ''; + }; }; }; @@ -239,7 +289,7 @@ in { mode = "0644"; }; buCellServDB = { - enable = (cfg.roles.backup.cellServDB != []); + enable = useBuCellServDB; text = mkCellServDB cfg.cellName cfg.roles.backup.cellServDB; target = "openafs/backup/CellServDB"; }; @@ -257,7 +307,7 @@ in { preStart = '' mkdir -m 0755 -p /var/openafs ${optionalString (netInfo != null) "cp ${netInfo} /var/openafs/netInfo"} - ${optionalString (cfg.roles.backup.cellServDB != []) "cp ${buCellServDB}"} + ${optionalString useBuCellServDB "cp ${buCellServDB}"} ''; serviceConfig = { ExecStart = "${openafsBin}/bin/bosserver -nofork"; diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix index 5e6f5217c0ce..7caee8a8eb3d 100644 --- a/nixos/modules/services/networking/ddclient.nix +++ b/nixos/modules/services/networking/ddclient.nix @@ -29,9 +29,9 @@ let configFile = if (cfg.configFile != null) then cfg.configFile else configFile'; preStart = '' - install ${configFile} /run/${RuntimeDirectory}/ddclient.conf + install --mode=600 --owner=$USER ${configFile} /run/${RuntimeDirectory}/ddclient.conf ${lib.optionalString (cfg.configFile == null) (if (cfg.protocol == "nsupdate") then '' - install ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key + install --mode=600 --owner=$USER ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key '' else if (cfg.passwordFile != null) then '' "${pkgs.replace-secret}/bin/replace-secret" "@password_placeholder@" "${cfg.passwordFile}" "/run/${RuntimeDirectory}/ddclient.conf" '' else '' diff --git a/nixos/modules/services/networking/dhcpd.nix b/nixos/modules/services/networking/dhcpd.nix index 0bd5e4ef5535..a981a255c3ee 100644 --- a/nixos/modules/services/networking/dhcpd.nix +++ b/nixos/modules/services/networking/dhcpd.nix @@ -218,6 +218,13 @@ in systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6; + warnings = [ + '' + The dhcpd4 and dhcpd6 modules will be removed from NixOS 23.11, because ISC DHCP reached its end of life. + See https://www.isc.org/blogs/isc-dhcp-eol/ for details. + Please switch to a different implementation like kea, systemd-networkd or dnsmasq. + '' + ]; }; } diff --git a/nixos/modules/services/networking/go-neb.nix b/nixos/modules/services/networking/go-neb.nix index 8c04542c47cc..b65bb5f548ee 100644 --- a/nixos/modules/services/networking/go-neb.nix +++ b/nixos/modules/services/networking/go-neb.nix @@ -60,13 +60,12 @@ in { serviceConfig = { ExecStartPre = lib.optional (cfg.secretFile != null) - (pkgs.writeShellScript "pre-start" '' + ("+" + pkgs.writeShellScript "pre-start" '' umask 077 export $(xargs < ${cfg.secretFile}) ${pkgs.envsubst}/bin/envsubst -i "${configFile}" > ${finalConfigFile} chown go-neb ${finalConfigFile} ''); - PermissionsStartOnly = true; RuntimeDirectory = "go-neb"; ExecStart = "${pkgs.go-neb}/bin/go-neb"; User = "go-neb"; diff --git a/nixos/modules/services/networking/ivpn.nix b/nixos/modules/services/networking/ivpn.nix new file mode 100644 index 000000000000..6df630c1f194 --- /dev/null +++ b/nixos/modules/services/networking/ivpn.nix @@ -0,0 +1,51 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.ivpn; +in +with lib; +{ + options.services.ivpn = { + enable = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + This option enables iVPN daemon. + This sets {option}`networking.firewall.checkReversePath` to "loose", which might be undesirable for security. + ''; + }; + }; + + config = mkIf cfg.enable { + boot.kernelModules = [ "tun" ]; + + environment.systemPackages = with pkgs; [ ivpn ivpn-service ]; + + # iVPN writes to /etc/iproute2/rt_tables + networking.iproute2.enable = true; + networking.firewall.checkReversePath = "loose"; + + systemd.services.ivpn-service = { + description = "iVPN daemon"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network.target" ]; + after = [ + "network-online.target" + "NetworkManager.service" + "systemd-resolved.service" + ]; + path = [ + # Needed for mount + "/run/wrappers" + ]; + startLimitBurst = 5; + startLimitIntervalSec = 20; + serviceConfig = { + ExecStart = "${pkgs.ivpn-service}/bin/ivpn-service --logging"; + Restart = "always"; + RestartSec = 1; + }; + }; + }; + + meta.maintainers = with maintainers; [ ataraxiasjel ]; +} diff --git a/nixos/modules/services/networking/peroxide.nix b/nixos/modules/services/networking/peroxide.nix index 6cac4bf2f89a..885ee1d96cd0 100644 --- a/nixos/modules/services/networking/peroxide.nix +++ b/nixos/modules/services/networking/peroxide.nix @@ -9,7 +9,7 @@ let in { options.services.peroxide = { - enable = mkEnableOption (lib.mdDoc "enable"); + enable = mkEnableOption (lib.mdDoc "peroxide"); package = mkPackageOptionMD pkgs "peroxide" { default = [ "peroxide" ]; diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix index c2c2a370cb00..19ab3f1aa48c 100644 --- a/nixos/modules/services/networking/smokeping.nix +++ b/nixos/modules/services/networking/smokeping.nix @@ -339,14 +339,9 @@ in }; preStart = '' mkdir -m 0755 -p ${smokepingHome}/cache ${smokepingHome}/data - rm -f ${smokepingHome}/cropper - ln -s ${cfg.package}/htdocs/cropper ${smokepingHome}/cropper - rm -f ${smokepingHome}/css - ln -s ${cfg.package}/htdocs/css ${smokepingHome}/css - rm -f ${smokepingHome}/js - ln -s ${cfg.package}/htdocs/js ${smokepingHome}/js - rm -f ${smokepingHome}/smokeping.fcgi - ln -s ${cgiHome} ${smokepingHome}/smokeping.fcgi + ln -sf ${cfg.package}/htdocs/css ${smokepingHome}/css + ln -sf ${cfg.package}/htdocs/js ${smokepingHome}/js + ln -sf ${cgiHome} ${smokepingHome}/smokeping.fcgi ${cfg.package}/bin/smokeping --check --config=${configPath} ${cfg.package}/bin/smokeping --static --config=${configPath} ''; diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix index 9982da304a48..89ddf8215299 100644 --- a/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixos/modules/services/networking/ssh/sshd.nix @@ -536,7 +536,7 @@ in # https://github.com/NixOS/nixpkgs/pull/10155 # https://github.com/NixOS/nixpkgs/pull/41745 services.openssh.authorizedKeysFiles = - [ "%h/.ssh/authorized_keys" "%h/.ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ]; + [ "%h/.ssh/authorized_keys" "/etc/ssh/authorized_keys.d/%u" ]; services.openssh.extraConfig = mkOrder 0 '' diff --git a/nixos/modules/services/networking/wgautomesh.nix b/nixos/modules/services/networking/wgautomesh.nix new file mode 100644 index 000000000000..93227a9b625d --- /dev/null +++ b/nixos/modules/services/networking/wgautomesh.nix @@ -0,0 +1,161 @@ +{ lib, config, pkgs, ... }: +with lib; +let + cfg = config.services.wgautomesh; + settingsFormat = pkgs.formats.toml { }; + configFile = + # Have to remove nulls manually as TOML generator will not just skip key + # if value is null + settingsFormat.generate "wgautomesh-config.toml" + (filterAttrs (k: v: v != null) + (mapAttrs + (k: v: + if k == "peers" + then map (e: filterAttrs (k: v: v != null) e) v + else v) + cfg.settings)); + runtimeConfigFile = + if cfg.enableGossipEncryption + then "/run/wgautomesh/wgautomesh.toml" + else configFile; +in +{ + options.services.wgautomesh = { + enable = mkEnableOption (mdDoc "the wgautomesh daemon"); + logLevel = mkOption { + type = types.enum [ "trace" "debug" "info" "warn" "error" ]; + default = "info"; + description = mdDoc "wgautomesh log level."; + }; + enableGossipEncryption = mkOption { + type = types.bool; + default = true; + description = mdDoc "Enable encryption of gossip traffic."; + }; + gossipSecretFile = mkOption { + type = types.path; + description = mdDoc '' + File containing the shared secret key to use for gossip encryption. + Required if `enableGossipEncryption` is set. + ''; + }; + enablePersistence = mkOption { + type = types.bool; + default = true; + description = mdDoc "Enable persistence of Wireguard peer info between restarts."; + }; + openFirewall = mkOption { + type = types.bool; + default = true; + description = mdDoc "Automatically open gossip port in firewall (recommended)."; + }; + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + options = { + + interface = mkOption { + type = types.str; + description = mdDoc '' + Wireguard interface to manage (it is NOT created by wgautomesh, you + should use another NixOS option to create it such as + `networking.wireguard.interfaces.wg0 = {...};`). + ''; + example = "wg0"; + }; + gossip_port = mkOption { + type = types.port; + description = mdDoc '' + wgautomesh gossip port, this MUST be the same number on all nodes in + the wgautomesh network. + ''; + default = 1666; + }; + lan_discovery = mkOption { + type = types.bool; + default = true; + description = mdDoc "Enable discovery of peers on the same LAN using UDP broadcast."; + }; + upnp_forward_external_port = mkOption { + type = types.nullOr types.port; + default = null; + description = mdDoc '' + Public port number to try to redirect to this machine's Wireguard + daemon using UPnP IGD. + ''; + }; + peers = mkOption { + type = types.listOf (types.submodule { + options = { + pubkey = mkOption { + type = types.str; + description = mdDoc "Wireguard public key of this peer."; + }; + address = mkOption { + type = types.str; + description = mdDoc '' + Wireguard address of this peer (a single IP address, multliple + addresses or address ranges are not supported). + ''; + example = "10.0.0.42"; + }; + endpoint = mkOption { + type = types.nullOr types.str; + description = mdDoc '' + Bootstrap endpoint for connecting to this Wireguard peer if no + other address is known or none are working. + ''; + default = null; + example = "wgnode.mydomain.example:51820"; + }; + }; + }); + default = [ ]; + description = mdDoc "wgautomesh peer list."; + }; + }; + + }; + default = { }; + description = mdDoc "Configuration for wgautomesh."; + }; + }; + + config = mkIf cfg.enable { + services.wgautomesh.settings = { + gossip_secret_file = mkIf cfg.enableGossipEncryption "$CREDENTIALS_DIRECTORY/gossip_secret"; + persist_file = mkIf cfg.enablePersistence "/var/lib/wgautomesh/state"; + }; + + systemd.services.wgautomesh = { + path = [ pkgs.wireguard-tools ]; + environment = { RUST_LOG = "wgautomesh=${cfg.logLevel}"; }; + description = "wgautomesh"; + serviceConfig = { + Type = "simple"; + + ExecStart = "${getExe pkgs.wgautomesh} ${runtimeConfigFile}"; + Restart = "always"; + RestartSec = "30"; + LoadCredential = mkIf cfg.enableGossipEncryption [ "gossip_secret:${cfg.gossipSecretFile}" ]; + + ExecStartPre = mkIf cfg.enableGossipEncryption [ + ''${pkgs.envsubst}/bin/envsubst \ + -i ${configFile} \ + -o ${runtimeConfigFile}'' + ]; + + DynamicUser = true; + StateDirectory = "wgautomesh"; + StateDirectoryMode = "0700"; + RuntimeDirectory = "wgautomesh"; + AmbientCapabilities = "CAP_NET_ADMIN"; + CapabilityBoundingSet = "CAP_NET_ADMIN"; + }; + wantedBy = [ "multi-user.target" ]; + }; + networking.firewall.allowedUDPPorts = + mkIf cfg.openFirewall [ cfg.settings.gossip_port ]; + }; +} + diff --git a/nixos/modules/services/networking/wstunnel.nix b/nixos/modules/services/networking/wstunnel.nix index 440b617f60a3..067d5df48725 100644 --- a/nixos/modules/services/networking/wstunnel.nix +++ b/nixos/modules/services/networking/wstunnel.nix @@ -294,7 +294,7 @@ let DynamicUser = true; SupplementaryGroups = optional (serverCfg.useACMEHost != null) certConfig.group; PrivateTmp = true; - AmbientCapabilities = optional (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; + AmbientCapabilities = optionals (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; NoNewPrivileges = true; RestrictNamespaces = "uts ipc pid user cgroup"; ProtectSystem = "strict"; @@ -340,7 +340,7 @@ let EnvironmentFile = optional (clientCfg.environmentFile != null) clientCfg.environmentFile; DynamicUser = true; PrivateTmp = true; - AmbientCapabilities = (optional (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]) ++ (optional ((clientCfg.dynamicToRemote.port or 1024) < 1024 || (any (x: x.local.port < 1024) clientCfg.localToRemote)) [ "CAP_NET_BIND_SERVICE" ]); + AmbientCapabilities = (optionals (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]) ++ (optionals ((clientCfg.dynamicToRemote.port or 1024) < 1024 || (any (x: x.local.port < 1024) clientCfg.localToRemote)) [ "CAP_NET_BIND_SERVICE" ]); NoNewPrivileges = true; RestrictNamespaces = "uts ipc pid user cgroup"; ProtectSystem = "strict"; diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix index 9ac89e057620..f6a23fb900f0 100644 --- a/nixos/modules/services/printing/cupsd.nix +++ b/nixos/modules/services/printing/cupsd.nix @@ -317,6 +317,7 @@ in environment.etc.cups.source = "/var/lib/cups"; services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper; + services.udev.packages = cfg.drivers; # Allow asswordless printer admin for members of wheel group security.polkit.extraConfig = mkIf polkitEnabled '' diff --git a/nixos/modules/services/search/qdrant.nix b/nixos/modules/services/search/qdrant.nix index a843c44dbb5f..e1f7365d951a 100644 --- a/nixos/modules/services/search/qdrant.nix +++ b/nixos/modules/services/search/qdrant.nix @@ -100,6 +100,7 @@ in { after = [ "network.target" ]; serviceConfig = { + LimitNOFILE=65536; ExecStart = "${pkgs.qdrant}/bin/qdrant --config-path ${configFile}"; DynamicUser = true; Restart = "on-failure"; diff --git a/nixos/modules/services/security/fail2ban.nix b/nixos/modules/services/security/fail2ban.nix index ead24d147071..1962d3f59c9f 100644 --- a/nixos/modules/services/security/fail2ban.nix +++ b/nixos/modules/services/security/fail2ban.nix @@ -78,6 +78,13 @@ in ''; }; + bantime = mkOption { + default = null; + type = types.nullOr types.str; + example = "10m"; + description = lib.mdDoc "Number of seconds that a host is banned."; + }; + maxretry = mkOption { default = 3; type = types.ints.unsigned; @@ -320,6 +327,9 @@ in ''} # Miscellaneous options ignoreip = 127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP} + ${optionalString (cfg.bantime != null) '' + bantime = ${cfg.bantime} + ''} maxretry = ${toString cfg.maxretry} backend = systemd # Actions diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix index 5583c39368f7..2f19decb5cb1 100644 --- a/nixos/modules/services/security/kanidm.nix +++ b/nixos/modules/services/security/kanidm.nix @@ -7,6 +7,18 @@ let serverConfigFile = settingsFormat.generate "server.toml" (filterConfig cfg.serverSettings); clientConfigFile = settingsFormat.generate "kanidm-config.toml" (filterConfig cfg.clientSettings); unixConfigFile = settingsFormat.generate "kanidm-unixd.toml" (filterConfig cfg.unixSettings); + certPaths = builtins.map builtins.dirOf [ cfg.serverSettings.tls_chain cfg.serverSettings.tls_key ]; + + # Merge bind mount paths and remove paths where a prefix is already mounted. + # This makes sure that if e.g. the tls_chain is in the nix store and /nix/store is alread in the mount + # paths, no new bind mount is added. Adding subpaths caused problems on ofborg. + hasPrefixInList = list: newPath: lib.any (path: lib.hasPrefix (builtins.toString path) (builtins.toString newPath)) list; + mergePaths = lib.foldl' (merged: newPath: let + # If the new path is a prefix to some existing path, we need to filter it out + filteredPaths = lib.filter (p: !lib.hasPrefix (builtins.toString newPath) (builtins.toString p)) merged; + # If a prefix of the new path is already in the list, do not add it + filteredNew = if hasPrefixInList filteredPaths newPath then [] else [ newPath ]; + in filteredPaths ++ filteredNew) []; defaultServiceConfig = { BindReadOnlyPaths = [ @@ -16,7 +28,7 @@ let "-/etc/hosts" "-/etc/localtime" ]; - CapabilityBoundingSet = ""; + CapabilityBoundingSet = []; # ProtectClock= adds DeviceAllow=char-rtc r DeviceAllow = ""; # Implies ProtectSystem=strict, which re-mounts all paths @@ -216,22 +228,28 @@ in description = "kanidm identity management daemon"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; - serviceConfig = defaultServiceConfig // { - StateDirectory = "kanidm"; - StateDirectoryMode = "0700"; - ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}"; - User = "kanidm"; - Group = "kanidm"; + serviceConfig = lib.mkMerge [ + # Merge paths and ignore existing prefixes needs to sidestep mkMerge + (defaultServiceConfig // { + BindReadOnlyPaths = mergePaths (defaultServiceConfig.BindReadOnlyPaths ++ certPaths); + }) + { + StateDirectory = "kanidm"; + StateDirectoryMode = "0700"; + ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}"; + User = "kanidm"; + Group = "kanidm"; - AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; - CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; - # This would otherwise override the CAP_NET_BIND_SERVICE capability. - PrivateUsers = false; - # Port needs to be exposed to the host network - PrivateNetwork = false; - RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; - TemporaryFileSystem = "/:ro"; - }; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + # This would otherwise override the CAP_NET_BIND_SERVICE capability. + PrivateUsers = lib.mkForce false; + # Port needs to be exposed to the host network + PrivateNetwork = lib.mkForce false; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + TemporaryFileSystem = "/:ro"; + } + ]; environment.RUST_LOG = "info"; }; @@ -240,34 +258,32 @@ in wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; restartTriggers = [ unixConfigFile clientConfigFile ]; - serviceConfig = defaultServiceConfig // { - CacheDirectory = "kanidm-unixd"; - CacheDirectoryMode = "0700"; - RuntimeDirectory = "kanidm-unixd"; - ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd"; - User = "kanidm-unixd"; - Group = "kanidm-unixd"; + serviceConfig = lib.mkMerge [ + defaultServiceConfig + { + CacheDirectory = "kanidm-unixd"; + CacheDirectoryMode = "0700"; + RuntimeDirectory = "kanidm-unixd"; + ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd"; + User = "kanidm-unixd"; + Group = "kanidm-unixd"; - BindReadOnlyPaths = [ - "/nix/store" - "-/etc/resolv.conf" - "-/etc/nsswitch.conf" - "-/etc/hosts" - "-/etc/localtime" - "-/etc/kanidm" - "-/etc/static/kanidm" - "-/etc/ssl" - "-/etc/static/ssl" - ]; - BindPaths = [ - # To create the socket - "/run/kanidm-unixd:/var/run/kanidm-unixd" - ]; - # Needs to connect to kanidmd - PrivateNetwork = false; - RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; - TemporaryFileSystem = "/:ro"; - }; + BindReadOnlyPaths = [ + "-/etc/kanidm" + "-/etc/static/kanidm" + "-/etc/ssl" + "-/etc/static/ssl" + ]; + BindPaths = [ + # To create the socket + "/run/kanidm-unixd:/var/run/kanidm-unixd" + ]; + # Needs to connect to kanidmd + PrivateNetwork = lib.mkForce false; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; + TemporaryFileSystem = "/:ro"; + } + ]; environment.RUST_LOG = "info"; }; diff --git a/nixos/modules/services/system/cachix-watch-store.nix b/nixos/modules/services/system/cachix-watch-store.nix index 85e9509bcc82..89157b460b9a 100644 --- a/nixos/modules/services/system/cachix-watch-store.nix +++ b/nixos/modules/services/system/cachix-watch-store.nix @@ -62,7 +62,13 @@ in after = [ "network-online.target" ]; path = [ config.nix.package ]; wantedBy = [ "multi-user.target" ]; + unitConfig = { + # allow to restart indefinitely + StartLimitIntervalSec = 0; + }; serviceConfig = { + # don't put too much stress on the machine when restarting + RestartSec = 1; # we don't want to kill children processes as those are deployments KillMode = "process"; Restart = "on-failure"; diff --git a/nixos/modules/services/video/rtsp-simple-server.nix b/nixos/modules/services/video/mediamtx.nix index 2dd62edab787..18a9e3d5fe30 100644 --- a/nixos/modules/services/video/rtsp-simple-server.nix +++ b/nixos/modules/services/video/mediamtx.nix @@ -3,19 +3,19 @@ with lib; let - cfg = config.services.rtsp-simple-server; - package = pkgs.rtsp-simple-server; + cfg = config.services.mediamtx; + package = pkgs.mediamtx; format = pkgs.formats.yaml {}; in { options = { - services.rtsp-simple-server = { - enable = mkEnableOption (lib.mdDoc "RTSP Simple Server"); + services.mediamtx = { + enable = mkEnableOption (lib.mdDoc "MediaMTX"); settings = mkOption { description = lib.mdDoc '' - Settings for rtsp-simple-server. - Read more at <https://github.com/aler9/rtsp-simple-server/blob/main/rtsp-simple-server.yml> + Settings for MediaMTX. + Read more at <https://github.com/aler9/mediamtx/blob/main/mediamtx.yml> ''; type = format.type; @@ -25,7 +25,7 @@ in "stdout" ]; # we set this so when the user uses it, it just works (see LogsDirectory below). but it's not used by default. - logFile = "/var/log/rtsp-simple-server/rtsp-simple-server.log"; + logFile = "/var/log/mediamtx/mediamtx.log"; }; example = { @@ -40,20 +40,20 @@ in env = mkOption { type = with types; attrsOf anything; - description = lib.mdDoc "Extra environment variables for RTSP Simple Server"; + description = lib.mdDoc "Extra environment variables for MediaMTX"; default = {}; example = { - RTSP_CONFKEY = "mykey"; + MTX_CONFKEY = "mykey"; }; }; }; }; config = mkIf (cfg.enable) { - # NOTE: rtsp-simple-server watches this file and automatically reloads if it changes - environment.etc."rtsp-simple-server.yaml".source = format.generate "rtsp-simple-server.yaml" cfg.settings; + # NOTE: mediamtx watches this file and automatically reloads if it changes + environment.etc."mediamtx.yaml".source = format.generate "mediamtx.yaml" cfg.settings; - systemd.services.rtsp-simple-server = { + systemd.services.mediamtx = { environment = cfg.env; after = [ "network.target" ]; @@ -65,15 +65,15 @@ in serviceConfig = { DynamicUser = true; - User = "rtsp-simple-server"; - Group = "rtsp-simple-server"; + User = "mediamtx"; + Group = "mediamtx"; - LogsDirectory = "rtsp-simple-server"; + LogsDirectory = "mediamtx"; # user likely may want to stream cameras, can't hurt to add video group SupplementaryGroups = "video"; - ExecStart = "${package}/bin/rtsp-simple-server /etc/rtsp-simple-server.yaml"; + ExecStart = "${package}/bin/mediamtx /etc/mediamtx.yaml"; }; }; }; diff --git a/nixos/modules/services/video/v4l2-relayd.nix b/nixos/modules/services/video/v4l2-relayd.nix new file mode 100644 index 000000000000..2a9dbe00158f --- /dev/null +++ b/nixos/modules/services/video/v4l2-relayd.nix @@ -0,0 +1,199 @@ +{ config, lib, pkgs, utils, ... }: +let + + inherit (lib) attrValues concatStringsSep filterAttrs length listToAttrs literalExpression + makeSearchPathOutput mkEnableOption mkIf mkOption nameValuePair optionals types; + inherit (utils) escapeSystemdPath; + + cfg = config.services.v4l2-relayd; + + kernelPackages = config.boot.kernelPackages; + + gst = (with pkgs.gst_all_1; [ + gst-plugins-bad + gst-plugins-base + gst-plugins-good + gstreamer.out + ]); + + instanceOpts = { name, ... }: { + options = { + enable = mkEnableOption (lib.mdDoc "this v4l2-relayd instance"); + + name = mkOption { + type = types.str; + default = name; + description = lib.mdDoc '' + The name of the instance. + ''; + }; + + cardLabel = mkOption { + type = types.str; + description = lib.mdDoc '' + The name the camera will show up as. + ''; + }; + + extraPackages = mkOption { + type = with types; listOf package; + default = [ ]; + description = lib.mdDoc '' + Extra packages to add to {env}`GST_PLUGIN_PATH` for the instance. + ''; + }; + + input = { + pipeline = mkOption { + type = types.str; + description = lib.mdDoc '' + The gstreamer-pipeline to use for the input-stream. + ''; + }; + + format = mkOption { + type = types.str; + default = "YUY2"; + description = lib.mdDoc '' + The video-format to read from input-stream. + ''; + }; + + width = mkOption { + type = types.ints.positive; + default = 1280; + description = lib.mdDoc '' + The width to read from input-stream. + ''; + }; + + height = mkOption { + type = types.ints.positive; + default = 720; + description = lib.mdDoc '' + The height to read from input-stream. + ''; + }; + + framerate = mkOption { + type = types.ints.positive; + default = 30; + description = lib.mdDoc '' + The framerate to read from input-stream. + ''; + }; + }; + + output = { + format = mkOption { + type = types.str; + default = "YUY2"; + description = lib.mdDoc '' + The video-format to write to output-stream. + ''; + }; + }; + + }; + }; + +in +{ + + options.services.v4l2-relayd = { + + instances = mkOption { + type = with types; attrsOf (submodule instanceOpts); + default = { }; + example = literalExpression '' + { + example = { + cardLabel = "Example card"; + input.pipeline = "videotestsrc"; + }; + } + ''; + description = lib.mdDoc '' + v4l2-relayd instances to be created. + ''; + }; + + }; + + config = + let + + mkInstanceService = instance: { + description = "Streaming relay for v4l2loopback using GStreamer"; + + after = [ "modprobe@v4l2loopback.service" "systemd-logind.service" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + PrivateNetwork = true; + PrivateTmp = true; + LimitNPROC = 1; + }; + + environment = { + GST_PLUGIN_PATH = makeSearchPathOutput "lib" "lib/gstreamer-1.0" (gst ++ instance.extraPackages); + V4L2_DEVICE_FILE = "/run/v4l2-relayd-${instance.name}/device"; + }; + + script = + let + appsrcOptions = concatStringsSep "," [ + "caps=video/x-raw" + "format=${instance.input.format}" + "width=${toString instance.input.width}" + "height=${toString instance.input.height}" + "framerate=${toString instance.input.framerate}/1" + ]; + + outputPipeline = [ + "appsrc name=appsrc ${appsrcOptions}" + "videoconvert" + ] ++ optionals (instance.input.format != instance.output.format) [ + "video/x-raw,format=${instance.output.format}" + "queue" + ] ++ [ "v4l2sink name=v4l2sink device=$(cat $V4L2_DEVICE_FILE)" ]; + in + '' + exec ${pkgs.v4l2-relayd}/bin/v4l2-relayd -i "${instance.input.pipeline}" -o "${concatStringsSep " ! " outputPipeline}" + ''; + + preStart = '' + mkdir -p $(dirname $V4L2_DEVICE_FILE) + ${kernelPackages.v4l2loopback.bin}/bin/v4l2loopback-ctl add -x 1 -n "${instance.cardLabel}" > $V4L2_DEVICE_FILE + ''; + + postStop = '' + ${kernelPackages.v4l2loopback.bin}/bin/v4l2loopback-ctl delete $(cat $V4L2_DEVICE_FILE) + rm -rf $(dirname $V4L2_DEVICE_FILE) + ''; + }; + + mkInstanceServices = instances: listToAttrs (map + (instance: + nameValuePair "v4l2-relayd-${escapeSystemdPath instance.name}" (mkInstanceService instance) + ) + instances); + + enabledInstances = attrValues (filterAttrs (n: v: v.enable) cfg.instances); + + in + { + + boot = mkIf ((length enabledInstances) > 0) { + extraModulePackages = [ kernelPackages.v4l2loopback ]; + kernelModules = [ "v4l2loopback" ]; + }; + + systemd.services = mkInstanceServices enabledInstances; + + }; + + meta.maintainers = with lib.maintainers; [ betaboon ]; +} diff --git a/nixos/modules/services/web-apps/ihatemoney/default.nix b/nixos/modules/services/web-apps/ihatemoney/default.nix deleted file mode 100644 index a61aa445f82c..000000000000 --- a/nixos/modules/services/web-apps/ihatemoney/default.nix +++ /dev/null @@ -1,153 +0,0 @@ -{ config, pkgs, lib, ... }: -with lib; -let - cfg = config.services.ihatemoney; - user = "ihatemoney"; - group = "ihatemoney"; - db = "ihatemoney"; - python3 = config.services.uwsgi.package.python3; - pkg = python3.pkgs.ihatemoney; - toBool = x: if x then "True" else "False"; - configFile = pkgs.writeText "ihatemoney.cfg" '' - from secrets import token_hex - # load a persistent secret key - SECRET_KEY_FILE = "/var/lib/ihatemoney/secret_key" - SECRET_KEY = "" - try: - with open(SECRET_KEY_FILE) as f: - SECRET_KEY = f.read() - except FileNotFoundError: - pass - if not SECRET_KEY: - print("ihatemoney: generating a new secret key") - SECRET_KEY = token_hex(50) - with open(SECRET_KEY_FILE, "w") as f: - f.write(SECRET_KEY) - del token_hex - del SECRET_KEY_FILE - - # "normal" configuration - DEBUG = False - SQLALCHEMY_DATABASE_URI = '${ - if cfg.backend == "sqlite" - then "sqlite:////var/lib/ihatemoney/ihatemoney.sqlite" - else "postgresql:///${db}"}' - SQLALCHEMY_TRACK_MODIFICATIONS = False - MAIL_DEFAULT_SENDER = (r"${cfg.defaultSender.name}", r"${cfg.defaultSender.email}") - ACTIVATE_DEMO_PROJECT = ${toBool cfg.enableDemoProject} - ADMIN_PASSWORD = r"${toString cfg.adminHashedPassword /*toString null == ""*/}" - ALLOW_PUBLIC_PROJECT_CREATION = ${toBool cfg.enablePublicProjectCreation} - ACTIVATE_ADMIN_DASHBOARD = ${toBool cfg.enableAdminDashboard} - SESSION_COOKIE_SECURE = ${toBool cfg.secureCookie} - ENABLE_CAPTCHA = ${toBool cfg.enableCaptcha} - LEGAL_LINK = r"${toString cfg.legalLink}" - - ${cfg.extraConfig} - ''; -in - { - options.services.ihatemoney = { - enable = mkEnableOption (lib.mdDoc "ihatemoney webapp. Note that this will set uwsgi to emperor mode"); - backend = mkOption { - type = types.enum [ "sqlite" "postgresql" ]; - default = "sqlite"; - description = lib.mdDoc '' - The database engine to use for ihatemoney. - If `postgresql` is selected, then a database called - `${db}` will be created. If you disable this option, - it will however not be removed. - ''; - }; - adminHashedPassword = mkOption { - type = types.nullOr types.str; - default = null; - description = lib.mdDoc "The hashed password of the administrator. To obtain it, run `ihatemoney generate_password_hash`"; - }; - uwsgiConfig = mkOption { - type = types.attrs; - example = { - http = ":8000"; - }; - description = lib.mdDoc "Additional configuration of the UWSGI vassal running ihatemoney. It should notably specify on which interfaces and ports the vassal should listen."; - }; - defaultSender = { - name = mkOption { - type = types.str; - default = "Budget manager"; - description = lib.mdDoc "The display name of the sender of ihatemoney emails"; - }; - email = mkOption { - type = types.str; - default = "ihatemoney@${config.networking.hostName}"; - defaultText = literalExpression ''"ihatemoney@''${config.networking.hostName}"''; - description = lib.mdDoc "The email of the sender of ihatemoney emails"; - }; - }; - secureCookie = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc "Use secure cookies. Disable this when ihatemoney is served via http instead of https"; - }; - enableDemoProject = mkEnableOption (lib.mdDoc "access to the demo project in ihatemoney"); - enablePublicProjectCreation = mkEnableOption (lib.mdDoc "permission to create projects in ihatemoney by anyone"); - enableAdminDashboard = mkEnableOption (lib.mdDoc "ihatemoney admin dashboard"); - enableCaptcha = mkEnableOption (lib.mdDoc "a simplistic captcha for some forms"); - legalLink = mkOption { - type = types.nullOr types.str; - default = null; - description = lib.mdDoc "The URL to a page explaining legal statements about your service, eg. GDPR-related information."; - }; - extraConfig = mkOption { - type = types.str; - default = ""; - description = lib.mdDoc "Extra configuration appended to ihatemoney's configuration file. It is a python file, so pay attention to indentation."; - }; - }; - config = mkIf cfg.enable { - services.postgresql = mkIf (cfg.backend == "postgresql") { - enable = true; - ensureDatabases = [ db ]; - ensureUsers = [ { - name = user; - ensurePermissions = { - "DATABASE ${db}" = "ALL PRIVILEGES"; - }; - } ]; - }; - systemd.services.postgresql = mkIf (cfg.backend == "postgresql") { - wantedBy = [ "uwsgi.service" ]; - before = [ "uwsgi.service" ]; - }; - systemd.tmpfiles.rules = [ - "d /var/lib/ihatemoney 770 ${user} ${group}" - ]; - users = { - users.${user} = { - isSystemUser = true; - inherit group; - }; - groups.${group} = {}; - }; - services.uwsgi = { - enable = true; - plugins = [ "python3" ]; - instance = { - type = "emperor"; - vassals.ihatemoney = { - type = "normal"; - strict = true; - immediate-uid = user; - immediate-gid = group; - # apparently flask uses threads: https://github.com/spiral-project/ihatemoney/commit/c7815e48781b6d3a457eaff1808d179402558f8c - enable-threads = true; - module = "wsgi:application"; - chdir = "${pkg}/${pkg.pythonModule.sitePackages}/ihatemoney"; - env = [ "IHATEMONEY_SETTINGS_FILE_PATH=${configFile}" ]; - pythonPackages = self: [ self.ihatemoney ]; - } // cfg.uwsgiConfig; - }; - }; - }; - } - - diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix index 3e1286dc475d..cf5329be489c 100644 --- a/nixos/modules/services/web-apps/mastodon.nix +++ b/nixos/modules/services/web-apps/mastodon.nix @@ -587,6 +587,13 @@ in { <option>services.mastodon.smtp.authenticate</option> is enabled. ''; } + { + assertion = 1 == builtins.length + (lib.mapAttrsToList + (_: v: builtins.elem "scheduler" v.jobClasses || v.jobClasses == [ ]) + cfg.sidekiqProcesses); + message = "There must be one and only one Sidekiq queue in services.mastodon.sidekiqProcesses with jobClass \"scheduler\"."; + } ]; environment.systemPackages = [ mastodonTootctl ]; diff --git a/nixos/modules/services/web-apps/monica.nix b/nixos/modules/services/web-apps/monica.nix new file mode 100644 index 000000000000..442044fedb14 --- /dev/null +++ b/nixos/modules/services/web-apps/monica.nix @@ -0,0 +1,468 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.services.monica; + monica = pkgs.monica.override { + dataDir = cfg.dataDir; + }; + db = cfg.database; + mail = cfg.mail; + + user = cfg.user; + group = cfg.group; + + # shell script for local administration + artisan = pkgs.writeScriptBin "monica" '' + #! ${pkgs.runtimeShell} + cd ${monica} + sudo() { + if [[ "$USER" != ${user} ]]; then + exec /run/wrappers/bin/sudo -u ${user} "$@" + else + exec "$@" + fi + } + sudo ${pkgs.php}/bin/php artisan "$@" + ''; + + tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME; +in { + options.services.monica = { + enable = mkEnableOption (lib.mdDoc "monica"); + + user = mkOption { + default = "monica"; + description = lib.mdDoc "User monica runs as."; + type = types.str; + }; + + group = mkOption { + default = "monica"; + description = lib.mdDoc "Group monica runs as."; + type = types.str; + }; + + appKeyFile = mkOption { + description = lib.mdDoc '' + A file containing the Laravel APP_KEY - a 32 character long, + base64 encoded key used for encryption where needed. Can be + generated with <code>head -c 32 /dev/urandom | base64</code>. + ''; + example = "/run/keys/monica-appkey"; + type = types.path; + }; + + hostname = lib.mkOption { + type = lib.types.str; + default = + if config.networking.domain != null + then config.networking.fqdn + else config.networking.hostName; + defaultText = lib.literalExpression "config.networking.fqdn"; + example = "monica.example.com"; + description = lib.mdDoc '' + The hostname to serve monica on. + ''; + }; + + appURL = mkOption { + description = lib.mdDoc '' + The root URL that you want to host monica on. All URLs in monica will be generated using this value. + If you change this in the future you may need to run a command to update stored URLs in the database. + Command example: <code>php artisan monica:update-url https://old.example.com https://new.example.com</code> + ''; + default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}"; + defaultText = ''http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostname}''; + example = "https://example.com"; + type = types.str; + }; + + dataDir = mkOption { + description = lib.mdDoc "monica data directory"; + default = "/var/lib/monica"; + type = types.path; + }; + + database = { + host = mkOption { + type = types.str; + default = "localhost"; + description = lib.mdDoc "Database host address."; + }; + port = mkOption { + type = types.port; + default = 3306; + description = lib.mdDoc "Database host port."; + }; + name = mkOption { + type = types.str; + default = "monica"; + description = lib.mdDoc "Database name."; + }; + user = mkOption { + type = types.str; + default = user; + defaultText = lib.literalExpression "user"; + description = lib.mdDoc "Database username."; + }; + passwordFile = mkOption { + type = with types; nullOr path; + default = null; + example = "/run/keys/monica-dbpassword"; + description = lib.mdDoc '' + A file containing the password corresponding to + <option>database.user</option>. + ''; + }; + createLocally = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Create the database and database user locally."; + }; + }; + + mail = { + driver = mkOption { + type = types.enum ["smtp" "sendmail"]; + default = "smtp"; + description = lib.mdDoc "Mail driver to use."; + }; + host = mkOption { + type = types.str; + default = "localhost"; + description = lib.mdDoc "Mail host address."; + }; + port = mkOption { + type = types.port; + default = 1025; + description = lib.mdDoc "Mail host port."; + }; + fromName = mkOption { + type = types.str; + default = "monica"; + description = lib.mdDoc "Mail \"from\" name."; + }; + from = mkOption { + type = types.str; + default = "mail@monica.com"; + description = lib.mdDoc "Mail \"from\" email."; + }; + user = mkOption { + type = with types; nullOr str; + default = null; + example = "monica"; + description = lib.mdDoc "Mail username."; + }; + passwordFile = mkOption { + type = with types; nullOr path; + default = null; + example = "/run/keys/monica-mailpassword"; + description = lib.mdDoc '' + A file containing the password corresponding to + <option>mail.user</option>. + ''; + }; + encryption = mkOption { + type = with types; nullOr (enum ["tls"]); + default = null; + description = lib.mdDoc "SMTP encryption mechanism to use."; + }; + }; + + maxUploadSize = mkOption { + type = types.str; + default = "18M"; + example = "1G"; + description = lib.mdDoc "The maximum size for uploads (e.g. images)."; + }; + + 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 = lib.mdDoc '' + Options for the monica PHP pool. See the documentation on <literal>php-fpm.conf</literal> + for details on configuration directives. + ''; + }; + + nginx = mkOption { + type = types.submodule ( + recursiveUpdate + (import ../web-servers/nginx/vhost-options.nix {inherit config lib;}) {} + ); + default = {}; + example = '' + { + serverAliases = [ + "monica.''${config.networking.domain}" + ]; + # To enable encryption and let let's encrypt take care of certificate + forceSSL = true; + enableACME = true; + } + ''; + description = lib.mdDoc '' + With this option, you can customize the nginx virtualHost settings. + ''; + }; + + config = mkOption { + type = with types; + attrsOf + (nullOr + (either + (oneOf [ + bool + int + port + path + str + ]) + (submodule { + options = { + _secret = mkOption { + type = nullOr str; + description = lib.mdDoc '' + The path to a file containing the value the + option should be set to in the final + configuration file. + ''; + }; + }; + }))); + default = {}; + example = '' + { + ALLOWED_IFRAME_HOSTS = "https://example.com"; + WKHTMLTOPDF = "/home/user/bins/wkhtmltopdf"; + AUTH_METHOD = "oidc"; + OIDC_NAME = "MyLogin"; + OIDC_DISPLAY_NAME_CLAIMS = "name"; + OIDC_CLIENT_ID = "monica"; + OIDC_CLIENT_SECRET = {_secret = "/run/keys/oidc_secret"}; + OIDC_ISSUER = "https://keycloak.example.com/auth/realms/My%20Realm"; + OIDC_ISSUER_DISCOVER = true; + } + ''; + description = lib.mdDoc '' + monica configuration options to set in the + <filename>.env</filename> file. + + Refer to <link xlink:href="https://github.com/monicahq/monica"/> + for details on supported values. + + Settings 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>.env</filename> file, the + <literal>OIDC_CLIENT_SECRET</literal> key will be set to the + contents of the <filename>/run/keys/oidc_secret</filename> + file. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = db.createLocally -> db.user == user; + message = "services.monica.database.user must be set to ${user} if services.monica.database.createLocally is set true."; + } + { + assertion = db.createLocally -> db.passwordFile == null; + message = "services.monica.database.passwordFile cannot be specified if services.monica.database.createLocally is set to true."; + } + ]; + + services.monica.config = { + APP_ENV = "production"; + APP_KEY._secret = cfg.appKeyFile; + APP_URL = cfg.appURL; + DB_HOST = db.host; + DB_PORT = db.port; + DB_DATABASE = db.name; + DB_USERNAME = db.user; + MAIL_DRIVER = mail.driver; + MAIL_FROM_NAME = mail.fromName; + MAIL_FROM = mail.from; + MAIL_HOST = mail.host; + MAIL_PORT = mail.port; + MAIL_USERNAME = mail.user; + MAIL_ENCRYPTION = mail.encryption; + DB_PASSWORD._secret = db.passwordFile; + MAIL_PASSWORD._secret = mail.passwordFile; + APP_SERVICES_CACHE = "/run/monica/cache/services.php"; + APP_PACKAGES_CACHE = "/run/monica/cache/packages.php"; + APP_CONFIG_CACHE = "/run/monica/cache/config.php"; + APP_ROUTES_CACHE = "/run/monica/cache/routes-v7.php"; + APP_EVENTS_CACHE = "/run/monica/cache/events.php"; + SESSION_SECURE_COOKIE = tlsEnabled; + }; + + environment.systemPackages = [artisan]; + + services.mysql = mkIf db.createLocally { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [db.name]; + ensureUsers = [ + { + name = db.user; + ensurePermissions = {"${db.name}.*" = "ALL PRIVILEGES";}; + } + ]; + }; + + services.phpfpm.pools.monica = { + inherit user group; + phpOptions = '' + log_errors = on + post_max_size = ${cfg.maxUploadSize} + upload_max_filesize = ${cfg.maxUploadSize} + ''; + settings = { + "listen.mode" = "0660"; + "listen.owner" = user; + "listen.group" = group; + } // cfg.poolConfig; + }; + + services.nginx = { + enable = mkDefault true; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedBrotliSettings = true; + recommendedProxySettings = true; + virtualHosts.${cfg.hostname} = mkMerge [ + cfg.nginx + { + root = mkForce "${monica}/public"; + locations = { + "/" = { + index = "index.php"; + tryFiles = "$uri $uri/ /index.php?$query_string"; + }; + "~ \.php$".extraConfig = '' + fastcgi_pass unix:${config.services.phpfpm.pools."monica".socket}; + ''; + "~ \.(js|css|gif|png|ico|jpg|jpeg)$" = { + extraConfig = "expires 365d;"; + }; + }; + } + ]; + }; + + systemd.services.monica-setup = { + description = "Preperation tasks for monica"; + before = ["phpfpm-monica.service"]; + after = optional db.createLocally "mysql.service"; + wantedBy = ["multi-user.target"]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = user; + UMask = 077; + WorkingDirectory = "${monica}"; + RuntimeDirectory = "monica/cache"; + RuntimeDirectoryMode = 0700; + }; + path = [pkgs.replace-secret]; + script = let + isSecret = v: isAttrs v && v ? _secret && isString v._secret; + monicaEnvVars = lib.generators.toKeyValue { + mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" { + mkValueString = v: + with builtins; + if isInt v + then toString v + else if isString v + then v + else if true == v + then "true" + else if false == v + then "false" + else if isSecret v + then hashString "sha256" v._secret + else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}"; + }; + }; + secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config); + mkSecretReplacement = file: '' + replace-secret ${escapeShellArgs [(builtins.hashString "sha256" file) file "${cfg.dataDir}/.env"]} + ''; + secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths; + filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{} null])) cfg.config; + monicaEnv = pkgs.writeText "monica.env" (monicaEnvVars filteredConfig); + in '' + # error handling + set -euo pipefail + + # create .env file + install -T -m 0600 -o ${user} ${monicaEnv} "${cfg.dataDir}/.env" + ${secretReplacements} + if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then + sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env" + fi + + # migrate & seed db + ${pkgs.php}/bin/php artisan key:generate --force + ${pkgs.php}/bin/php artisan setup:production -v --force + ''; + }; + + systemd.services.monica-scheduler = { + description = "Background tasks for monica"; + startAt = "minutely"; + after = ["monica-setup.service"]; + serviceConfig = { + Type = "oneshot"; + User = user; + WorkingDirectory = "${monica}"; + ExecStart = "${pkgs.php}/bin/php ${monica}/artisan schedule:run -v"; + }; + }; + + systemd.tmpfiles.rules = [ + "d ${cfg.dataDir} 0710 ${user} ${group} - -" + "d ${cfg.dataDir}/public 0750 ${user} ${group} - -" + "d ${cfg.dataDir}/public/uploads 0750 ${user} ${group} - -" + "d ${cfg.dataDir}/storage 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/app 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/fonts 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/framework 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/framework/cache 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/framework/views 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/logs 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/uploads 0700 ${user} ${group} - -" + ]; + + users = { + users = mkIf (user == "monica") { + monica = { + inherit group; + isSystemUser = true; + }; + "${config.services.nginx.user}".extraGroups = [group]; + }; + groups = mkIf (group == "monica") { + monica = {}; + }; + }; + }; +} + diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix index 5f8d9c5b15f4..b617e9a59379 100644 --- a/nixos/modules/services/web-apps/moodle.nix +++ b/nixos/modules/services/web-apps/moodle.nix @@ -56,7 +56,7 @@ let mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql"; pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql"; - phpExt = pkgs.php80.buildEnv { + phpExt = pkgs.php81.buildEnv { extensions = { all, ... }: with all; [ iconv mbstring curl openssl tokenizer soap ctype zip gd simplexml dom intl sqlite3 pgsql pdo_sqlite pdo_pgsql pdo_odbc pdo_mysql pdo mysqli session zlib xmlreader fileinfo filter opcache exif sodium ]; extraConfig = "max_input_vars = 5000"; }; diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix index f64254d62524..893dfa10acbc 100644 --- a/nixos/modules/services/web-apps/plausible.nix +++ b/nixos/modules/services/web-apps/plausible.nix @@ -9,6 +9,8 @@ in { options.services.plausible = { enable = mkEnableOption (lib.mdDoc "plausible"); + package = mkPackageOptionMD pkgs "plausible" { }; + releaseCookiePath = mkOption { type = with types; either str path; description = lib.mdDoc '' @@ -180,12 +182,12 @@ in { services.epmd.enable = true; - environment.systemPackages = [ pkgs.plausible ]; + environment.systemPackages = [ cfg.package ]; systemd.services = mkMerge [ { plausible = { - inherit (pkgs.plausible.meta) description; + inherit (cfg.package.meta) description; documentation = [ "https://plausible.io/docs/self-hosting" ]; wantedBy = [ "multi-user.target" ]; after = optional cfg.database.clickhouse.setup "clickhouse.service" @@ -233,7 +235,7 @@ in { SMTP_USER_NAME = cfg.mail.smtp.user; }); - path = [ pkgs.plausible ] + path = [ cfg.package ] ++ optional cfg.database.postgres.setup config.services.postgresql.package; script = '' export CONFIG_DIR=$CREDENTIALS_DIRECTORY @@ -241,10 +243,10 @@ in { export RELEASE_COOKIE="$(< $CREDENTIALS_DIRECTORY/RELEASE_COOKIE )" # setup - ${pkgs.plausible}/createdb.sh - ${pkgs.plausible}/migrate.sh + ${cfg.package}/createdb.sh + ${cfg.package}/migrate.sh ${optionalString cfg.adminUser.activate '' - if ! ${pkgs.plausible}/init-admin.sh | grep 'already exists'; then + if ! ${cfg.package}/init-admin.sh | grep 'already exists'; then psql -d plausible <<< "UPDATE users SET email_verified=true;" fi ''} diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index 7811876369b3..4f97fe752ef7 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -31,6 +31,7 @@ let # Mime.types values are taken from brotli sample configuration - https://github.com/google/ngx_brotli # and Nginx Server Configs - https://github.com/h5bp/server-configs-nginx + # "text/html" is implicitly included in {brotli,gzip,zstd}_types compressMimeTypes = [ "application/atom+xml" "application/geo+json" @@ -55,7 +56,6 @@ let "text/calendar" "text/css" "text/csv" - "text/html" "text/javascript" "text/markdown" "text/plain" diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix index 4d0296c8254c..ab4d75b10134 100644 --- a/nixos/modules/services/x11/desktop-managers/pantheon.nix +++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix @@ -288,7 +288,7 @@ in elementary-music elementary-photos elementary-screenshot - # elementary-tasks + elementary-tasks elementary-terminal elementary-videos epiphany diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix index 73a864bb95fe..38f932ffb420 100644 --- a/nixos/modules/services/x11/desktop-managers/plasma5.nix +++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix @@ -429,7 +429,8 @@ in dolphin-plugins ffmpegthumbs kdegraphics-thumbnailers - pkgs.kio-admin + kde-inotify-survey + kio-admin kio-extras ]; optionalPackages = [ diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index c92451997203..a040518a5a57 100755 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -302,8 +302,12 @@ def main() -> None: if is_default: write_loader_conf(*gen) except OSError as e: - profile = f"profile '{gen.profile}'" if gen.profile else "default profile" - print("ignoring {} in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr) + # See https://github.com/NixOS/nixpkgs/issues/114552 + if e.errno == errno.EINVAL: + profile = f"profile '{gen.profile}'" if gen.profile else "default profile" + print("ignoring {} in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr) + else: + raise e for root, _, files in os.walk('@efiSysMountPoint@/efi/nixos/.extra-files', topdown=False): relative_root = root.removeprefix("@efiSysMountPoint@/efi/nixos/.extra-files").removeprefix("/") diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix index 0ab2a875975d..4e7201833db6 100644 --- a/nixos/modules/system/boot/resolved.nix +++ b/nixos/modules/system/boot/resolved.nix @@ -16,7 +16,9 @@ in default = false; type = types.bool; description = lib.mdDoc '' - Whether to enable the systemd DNS resolver daemon. + Whether to enable the systemd DNS resolver daemon, `systemd-resolved`. + + Search for `services.resolved` to see all options. ''; }; diff --git a/nixos/modules/system/boot/tmp.nix b/nixos/modules/system/boot/tmp.nix index 1f9431710aec..fd16cd3fba42 100644 --- a/nixos/modules/system/boot/tmp.nix +++ b/nixos/modules/system/boot/tmp.nix @@ -3,62 +3,67 @@ with lib; let - cfg = config.boot; + cfg = config.boot.tmp; in { - - ###### interface + imports = [ + (mkRenamedOptionModule [ "boot" "cleanTmpDir" ] [ "boot" "tmp" "cleanOnBoot" ]) + (mkRenamedOptionModule [ "boot" "tmpOnTmpfs" ] [ "boot" "tmp" "useTmpfs" ]) + (mkRenamedOptionModule [ "boot" "tmpOnTmpfsSize" ] [ "boot" "tmp" "tmpfsSize" ]) + ]; options = { + boot.tmp = { + cleanOnBoot = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to delete all files in {file}`/tmp` during boot. + ''; + }; - boot.cleanTmpDir = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Whether to delete all files in {file}`/tmp` during boot. - ''; - }; + tmpfsSize = mkOption { + type = types.oneOf [ types.str types.types.ints.positive ]; + default = "50%"; + description = lib.mdDoc '' + Size of tmpfs in percentage. + Percentage is defined by systemd. + ''; + }; - boot.tmpOnTmpfs = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Whether to mount a tmpfs on {file}`/tmp` during boot. - ''; - }; + useTmpfs = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to mount a tmpfs on {file}`/tmp` during boot. - boot.tmpOnTmpfsSize = mkOption { - type = types.oneOf [ types.str types.types.ints.positive ]; - default = "50%"; - description = lib.mdDoc '' - Size of tmpfs in percentage. - Percentage is defined by systemd. - ''; + ::: {.note} + Large Nix builds can fail if the mounted tmpfs is not large enough. + In such a case either increase the tmpfsSize or disable this option. + ::: + ''; + }; }; - }; - ###### implementation - config = { - # When changing remember to update /tmp mount in virtualisation/qemu-vm.nix - systemd.mounts = mkIf cfg.tmpOnTmpfs [ + systemd.mounts = mkIf cfg.useTmpfs [ { what = "tmpfs"; where = "/tmp"; type = "tmpfs"; - mountConfig.Options = concatStringsSep "," [ "mode=1777" - "strictatime" - "rw" - "nosuid" - "nodev" - "size=${toString cfg.tmpOnTmpfsSize}" ]; + mountConfig.Options = concatStringsSep "," [ + "mode=1777" + "strictatime" + "rw" + "nosuid" + "nodev" + "size=${toString cfg.tmpfsSize}" + ]; } ]; - systemd.tmpfiles.rules = optional config.boot.cleanTmpDir "D! /tmp 1777 root root"; - + systemd.tmpfiles.rules = optional cfg.cleanOnBoot "D! /tmp 1777 root root"; }; - } diff --git a/nixos/modules/virtualisation/cri-o.nix b/nixos/modules/virtualisation/cri-o.nix index 3503a6fff6a9..dacd700537c7 100644 --- a/nixos/modules/virtualisation/cri-o.nix +++ b/nixos/modules/virtualisation/cri-o.nix @@ -4,7 +4,10 @@ with lib; let cfg = config.virtualisation.cri-o; - crioPackage = (pkgs.cri-o.override { inherit (cfg) extraPackages; }); + crioPackage = pkgs.cri-o.override { + extraPackages = cfg.extraPackages + ++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package; + }; format = pkgs.formats.toml { }; @@ -19,7 +22,7 @@ in enable = mkEnableOption (lib.mdDoc "Container Runtime Interface for OCI (CRI-O)"); storageDriver = mkOption { - type = types.enum [ "btrfs" "overlay" "vfs" ]; + type = types.enum [ "aufs" "btrfs" "devmapper" "overlay" "vfs" "zfs" ]; default = "overlay"; description = lib.mdDoc "Storage driver to be used"; }; diff --git a/nixos/modules/virtualisation/multipass.nix b/nixos/modules/virtualisation/multipass.nix index 6ef7de4b2bf5..b331b3be7ea5 100644 --- a/nixos/modules/virtualisation/multipass.nix +++ b/nixos/modules/virtualisation/multipass.nix @@ -33,8 +33,8 @@ in description = "Multipass orchestrates virtual Ubuntu instances."; wantedBy = [ "multi-user.target" ]; - wants = [ "network.target" ]; - after = [ "network.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; environment = { "XDG_DATA_HOME" = "/var/lib/multipass/data"; diff --git a/nixos/modules/virtualisation/proxmox-image.nix b/nixos/modules/virtualisation/proxmox-image.nix index 6a4220fd265c..c66a4f178ec7 100644 --- a/nixos/modules/virtualisation/proxmox-image.nix +++ b/nixos/modules/virtualisation/proxmox-image.nix @@ -135,10 +135,11 @@ with lib; cfgLine = name: value: '' ${name}: ${builtins.toString value} ''; + virtio0Storage = builtins.head (builtins.split ":" cfg.qemuConf.virtio0); cfgFile = fileName: properties: pkgs.writeTextDir fileName '' # generated by NixOS ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)} - #qmdump#map:virtio0:drive-virtio0:local-lvm:raw: + #qmdump#map:virtio0:drive-virtio0:${virtio0Storage}:raw: ''; inherit (cfg) partitionTableType; supportEfi = partitionTableType == "efi" || partitionTableType == "hybrid"; diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index 154fdd2e01be..1619c0d9410f 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -1069,12 +1069,12 @@ in fsType = "ext4"; autoFormat = true; }); - "/tmp" = lib.mkIf config.boot.tmpOnTmpfs { + "/tmp" = lib.mkIf config.boot.tmp.useTmpfs { device = "tmpfs"; fsType = "tmpfs"; neededForBoot = true; # Sync with systemd's tmp.mount; - options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ]; + options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ]; }; "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = lib.mkIf cfg.useNixStoreImage { device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}"; |