diff options
Diffstat (limited to 'nixos/modules')
37 files changed, 1672 insertions, 307 deletions
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix index 31adc9b82620b..555db459f57a6 100644 --- a/nixos/modules/config/shells-environment.nix +++ b/nixos/modules/config/shells-environment.nix @@ -163,15 +163,24 @@ in /bin/sh ''; + # For resetting environment with `. /etc/set-environment` when needed + # and discoverability (see motivation of #30418). + environment.etc."set-environment".source = config.system.build.setEnvironment; + system.build.setEnvironment = pkgs.writeText "set-environment" - '' - ${exportedEnvVars} + '' + # DO NOT EDIT -- this file has been generated automatically. + + # Prevent this file from being sourced by child shells. + export __NIXOS_SET_ENVIRONMENT_DONE=1 - ${cfg.extraInit} + ${exportedEnvVars} - # ~/bin if it exists overrides other bin directories. - export PATH="$HOME/bin:$PATH" - ''; + ${cfg.extraInit} + + # ~/bin if it exists overrides other bin directories. + export PATH="$HOME/bin:$PATH" + ''; system.activationScripts.binsh = stringAfter [ "stdio" ] '' diff --git a/nixos/modules/config/xdg/mime.nix b/nixos/modules/config/xdg/mime.nix index f1b672234a344..4323a49ea1dd0 100644 --- a/nixos/modules/config/xdg/mime.nix +++ b/nixos/modules/config/xdg/mime.nix @@ -7,7 +7,7 @@ with lib; type = types.bool; default = true; description = '' - Whether to install files to support the + Whether to install files to support the <link xlink:href="https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html">XDG Shared MIME-info specification</link> and the <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html">XDG MIME Applications specification</link>. ''; @@ -17,18 +17,18 @@ with lib; config = mkIf config.xdg.mime.enable { environment.pathsToLink = [ "/share/mime" ]; - environment.systemPackages = [ - # this package also installs some useful data, as well as its utilities - pkgs.shared-mime-info + environment.systemPackages = [ + # this package also installs some useful data, as well as its utilities + pkgs.shared-mime-info ]; environment.extraSetup = '' - if [ -w $out/share/mime ]; then - XDG_DATA_DIRS=$out/share ${pkgs.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null + if [ -w $out/share/mime ] && [ -d $out/share/mime/packages ]; then + XDG_DATA_DIRS=$out/share ${pkgs.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null fi if [ -w $out/share/applications ]; then - ${pkgs.desktop-file-utils}/bin/update-desktop-database $out/share/applications + ${pkgs.desktop-file-utils}/bin/update-desktop-database $out/share/applications fi ''; }; diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix index 6611a6ca07909..cb182a08a830c 100644 --- a/nixos/modules/installer/tools/nix-fallback-paths.nix +++ b/nixos/modules/installer/tools/nix-fallback-paths.nix @@ -1,6 +1,6 @@ { - x86_64-linux = "/nix/store/r9i30v8nasafg2851wflg71ln49fw03y-nix-2.1"; - i686-linux = "/nix/store/dsg3pr7wwrk51f7la9wgby173j18llqh-nix-2.1"; - aarch64-linux = "/nix/store/m3qgnch4xin21pmd1azas8kkcp9rhkr6-nix-2.1"; - x86_64-darwin = "/nix/store/n7fvy0k555gwkkdszdkhi3h0aahca8h3-nix-2.1"; + x86_64-linux = "/nix/store/h180y3n5k1ypxgm1pcvj243qix5j45zz-nix-2.1.1"; + i686-linux = "/nix/store/v2y4k4v9ml07jmfq739wyflapg3b7b5k-nix-2.1.1"; + aarch64-linux = "/nix/store/v485craglq7xm5996ci8qy5dyc17dab0-nix-2.1.1"; + x86_64-darwin = "/nix/store/lc3ymlix73kaad5srjdgaxp9ngr1sg6g-nix-2.1.1"; } diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index 8292cdc995e08..aafeb997c326b 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -53,7 +53,7 @@ tomcat = 16; #audio = 17; # unused #floppy = 18; # unused - #uucp = 19; # unused + uucp = 19; #lp = 20; # unused #proc = 21; # unused pulseaudio = 22; # must match `pulseaudio' GID diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix index 8fbe218b232af..7f9833e184ab9 100644 --- a/nixos/modules/misc/nixpkgs.nix +++ b/nixos/modules/misc/nixpkgs.nix @@ -62,12 +62,11 @@ in pkgs = mkOption { defaultText = literalExample ''import "''${nixos}/.." { - inherit (config.nixpkgs) config overlays localSystem crossSystem; + inherit (cfg) config overlays localSystem crossSystem; } ''; default = import ../../.. { - localSystem = { inherit (cfg) system; } // cfg.localSystem; - inherit (cfg) config overlays crossSystem; + inherit (cfg) config overlays localSystem crossSystem; }; type = pkgsType; example = literalExample ''import <nixpkgs> {}''; @@ -140,8 +139,11 @@ in localSystem = mkOption { type = types.attrs; # TODO utilize lib.systems.parsedPlatform - default = { system = builtins.currentSystem; }; + default = { inherit (cfg) system; }; example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; }; + # Make sure that the final value has all fields for sake of other modules + # referring to this. TODO make `lib.systems` itself use the module system. + apply = lib.systems.elaborate; defaultText = literalExample ''(import "''${nixos}/../lib").lib.systems.examples.aarch64-multiplatform''; description = '' @@ -180,6 +182,7 @@ in system = mkOption { type = types.str; example = "i686-linux"; + default = { system = builtins.currentSystem; }; description = '' Specifies the Nix platform type on which NixOS should be built. It is better to specify <code>nixpkgs.localSystem</code> instead. @@ -196,6 +199,7 @@ in </programlisting> See <code>nixpkgs.localSystem</code> for more information. + Ignored when <code>nixpkgs.localSystem</code> is set. Ignored when <code>nixpkgs.pkgs</code> is set. ''; }; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 4795922abcfb8..f51a30aec2e90 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -245,6 +245,7 @@ ./services/desktops/gnome3/gnome-user-share.nix ./services/desktops/gnome3/gpaste.nix ./services/desktops/gnome3/gvfs.nix + ./services/desktops/gnome3/rygel.nix ./services/desktops/gnome3/seahorse.nix ./services/desktops/gnome3/sushi.nix ./services/desktops/gnome3/tracker.nix @@ -406,6 +407,7 @@ ./services/misc/taskserver ./services/misc/tzupdate.nix ./services/misc/uhub.nix + ./services/misc/weechat.nix ./services/misc/xmr-stak.nix ./services/misc/zookeeper.nix ./services/monitoring/apcupsd.nix @@ -515,9 +517,11 @@ ./services/networking/heyefi.nix ./services/networking/hostapd.nix ./services/networking/htpdate.nix + ./services/networking/hylafax/default.nix ./services/networking/i2pd.nix ./services/networking/i2p.nix ./services/networking/iodine.nix + ./services/networking/iperf3.nix ./services/networking/ircd-hybrid/default.nix ./services/networking/iwd.nix ./services/networking/keepalived/default.nix diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix index 69a1a482d0747..424e1506b4c5d 100644 --- a/nixos/modules/programs/bash/bash.nix +++ b/nixos/modules/programs/bash/bash.nix @@ -126,7 +126,9 @@ in programs.bash = { shellInit = '' - ${config.system.build.setEnvironment.text} + if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then + . ${config.system.build.setEnvironment} + fi ${cfge.shellInit} ''; @@ -166,11 +168,11 @@ in # Read system-wide modifications. if test -f /etc/profile.local; then - . /etc/profile.local + . /etc/profile.local fi if [ -n "''${BASH_VERSION:-}" ]; then - . /etc/bashrc + . /etc/bashrc fi ''; @@ -191,12 +193,12 @@ in # We are not always an interactive shell. if [ -n "$PS1" ]; then - ${cfg.interactiveShellInit} + ${cfg.interactiveShellInit} fi # Read system-wide modifications. if test -f /etc/bashrc.local; then - . /etc/bashrc.local + . /etc/bashrc.local fi ''; diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix index b7d8a345e65c5..9c9765b06b6f8 100644 --- a/nixos/modules/programs/dconf.nix +++ b/nixos/modules/programs/dconf.nix @@ -32,6 +32,8 @@ in environment.etc = optionals (cfg.profiles != {}) (mapAttrsToList mkDconfProfile cfg.profiles); + services.dbus.packages = [ pkgs.gnome3.dconf ]; + environment.variables.GIO_EXTRA_MODULES = optional cfg.enable "${pkgs.gnome3.dconf.lib}/lib/gio/modules"; # https://github.com/NixOS/nixpkgs/pull/31891 diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix index c8d94a47be28c..c3f742acde2e6 100644 --- a/nixos/modules/programs/fish.nix +++ b/nixos/modules/programs/fish.nix @@ -27,7 +27,7 @@ in ''; type = types.bool; }; - + vendor.config.enable = mkOption { type = types.bool; default = true; @@ -43,7 +43,7 @@ in Whether fish should use completion files provided by other packages. ''; }; - + vendor.functions.enable = mkOption { type = types.bool; default = true; @@ -107,9 +107,11 @@ in # This happens before $__fish_datadir/config.fish sets fish_function_path, so it is currently # unset. We set it and then completely erase it, leaving its configuration to $__fish_datadir/config.fish set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $__fish_datadir/functions - + # source the NixOS environment config - fenv source ${config.system.build.setEnvironment} + if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ] + fenv source ${config.system.build.setEnvironment} + end # clear fish_function_path so that it will be correctly set when we return to $__fish_datadir/config.fish set -e fish_function_path @@ -123,7 +125,7 @@ in set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path fenv source /etc/fish/foreign-env/shellInit > /dev/null set -e fish_function_path[1] - + ${cfg.shellInit} # and leave a note so we don't source this config section again from @@ -137,7 +139,7 @@ in set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path fenv source /etc/fish/foreign-env/loginShellInit > /dev/null set -e fish_function_path[1] - + ${cfg.loginShellInit} # and leave a note so we don't source this config section again from @@ -149,12 +151,11 @@ in status --is-interactive; and not set -q __fish_nixos_interactive_config_sourced and begin ${fishAliases} - set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path fenv source /etc/fish/foreign-env/interactiveShellInit > /dev/null set -e fish_function_path[1] - + ${cfg.promptInit} ${cfg.interactiveShellInit} @@ -170,7 +171,7 @@ in ++ optional cfg.vendor.config.enable "/share/fish/vendor_conf.d" ++ optional cfg.vendor.completions.enable "/share/fish/vendor_completions.d" ++ optional cfg.vendor.functions.enable "/share/fish/vendor_functions.d"; - + environment.systemPackages = [ pkgs.fish ]; environment.shells = [ diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix index d30b3415411f2..b4ca8730958c2 100644 --- a/nixos/modules/programs/zsh/zsh.nix +++ b/nixos/modules/programs/zsh/zsh.nix @@ -70,7 +70,7 @@ in promptInit = mkOption { default = '' if [ "$TERM" != dumb ]; then - autoload -U promptinit && promptinit && prompt walters + autoload -U promptinit && promptinit && prompt walters fi ''; description = '' @@ -116,7 +116,9 @@ in if [ -n "$__ETC_ZSHENV_SOURCED" ]; then return; fi export __ETC_ZSHENV_SOURCED=1 - ${config.system.build.setEnvironment.text} + if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then + . ${config.system.build.setEnvironment} + fi ${cfge.shellInit} @@ -124,7 +126,7 @@ in # Read system-wide modifications. if test -f /etc/zshenv.local; then - . /etc/zshenv.local + . /etc/zshenv.local fi ''; @@ -143,7 +145,7 @@ in # Read system-wide modifications. if test -f /etc/zprofile.local; then - . /etc/zprofile.local + . /etc/zprofile.local fi ''; @@ -169,7 +171,7 @@ in # Tell zsh how to find installed completions for p in ''${(z)NIX_PROFILES}; do - fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions) + fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions) done ${optionalString cfg.enableGlobalCompInit "autoload -U compinit && compinit"} @@ -184,7 +186,7 @@ in # Read system-wide modifications. if test -f /etc/zshrc.local; then - . /etc/zshrc.local + . /etc/zshrc.local fi ''; diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix index 946da92d80e76..092704c6fc3f6 100644 --- a/nixos/modules/security/acme.nix +++ b/nixos/modules/security/acme.nix @@ -302,15 +302,15 @@ in workdir="$(mktemp -d)" # Create CA - openssl genrsa -des3 -passout pass:x -out $workdir/ca.pass.key 2048 - openssl rsa -passin pass:x -in $workdir/ca.pass.key -out $workdir/ca.key + openssl genrsa -des3 -passout pass:xxxx -out $workdir/ca.pass.key 2048 + openssl rsa -passin pass:xxxx -in $workdir/ca.pass.key -out $workdir/ca.key openssl req -new -key $workdir/ca.key -out $workdir/ca.csr \ -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=Security Department/CN=example.com" openssl x509 -req -days 1 -in $workdir/ca.csr -signkey $workdir/ca.key -out $workdir/ca.crt # Create key - openssl genrsa -des3 -passout pass:x -out $workdir/server.pass.key 2048 - openssl rsa -passin pass:x -in $workdir/server.pass.key -out $workdir/server.key + openssl genrsa -des3 -passout pass:xxxx -out $workdir/server.pass.key 2048 + openssl rsa -passin pass:xxxx -in $workdir/server.pass.key -out $workdir/server.key openssl req -new -key $workdir/server.key -out $workdir/server.csr \ -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com" openssl x509 -req -days 1 -in $workdir/server.csr -CA $workdir/ca.crt \ diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix index 1e1c5bc9f035a..09174ed39f5e8 100644 --- a/nixos/modules/services/computing/slurm/slurm.nix +++ b/nixos/modules/services/computing/slurm/slurm.nix @@ -8,6 +8,7 @@ let # configuration file can be generated by http://slurm.schedmd.com/configurator.html configFile = pkgs.writeTextDir "slurm.conf" '' + ClusterName=${cfg.clusterName} ${optionalString (cfg.controlMachine != null) ''controlMachine=${cfg.controlMachine}''} ${optionalString (cfg.controlAddr != null) ''controlAddr=${cfg.controlAddr}''} ${optionalString (cfg.nodeName != null) ''nodeName=${cfg.nodeName}''} @@ -105,6 +106,15 @@ in ''; }; + clusterName = mkOption { + type = types.str; + default = "default"; + example = "myCluster"; + description = '' + Necessary to distinguish accounting records in a multi-cluster environment. + ''; + }; + nodeName = mkOption { type = types.nullOr types.str; default = null; diff --git a/nixos/modules/services/desktops/gnome3/rygel.nix b/nixos/modules/services/desktops/gnome3/rygel.nix new file mode 100644 index 0000000000000..55d5e703aa19d --- /dev/null +++ b/nixos/modules/services/desktops/gnome3/rygel.nix @@ -0,0 +1,30 @@ +# rygel service. +{ config, lib, pkgs, ... }: + +with lib; + +{ + ###### interface + options = { + services.gnome3.rygel = { + enable = mkOption { + default = false; + description = '' + Whether to enable Rygel UPnP Mediaserver. + + You will need to also allow UPnP connections in firewall, see the following <link xlink:href="https://github.com/NixOS/nixpkgs/pull/45045#issuecomment-416030795">comment</link>. + ''; + type = types.bool; + }; + }; + }; + + ###### implementation + config = mkIf config.services.gnome3.rygel.enable { + environment.systemPackages = [ pkgs.gnome3.rygel ]; + + services.dbus.packages = [ pkgs.gnome3.rygel ]; + + systemd.packages = [ pkgs.gnome3.rygel ]; + }; +} diff --git a/nixos/modules/services/misc/weechat.nix b/nixos/modules/services/misc/weechat.nix new file mode 100644 index 0000000000000..1fcfb440485dc --- /dev/null +++ b/nixos/modules/services/misc/weechat.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.weechat; +in + +{ + options.services.weechat = { + enable = mkEnableOption "weechat"; + root = mkOption { + description = "Weechat state directory."; + type = types.str; + default = "/var/lib/weechat"; + }; + sessionName = mkOption { + description = "Name of the `screen' session for weechat."; + default = "weechat-screen"; + type = types.str; + }; + binary = mkOption { + description = "Binary to execute (by default \${weechat}/bin/weechat)."; + example = literalExample '' + ''${pkgs.weechat}/bin/weechat-headless + ''; + default = "${pkgs.weechat}/bin/weechat"; + }; + }; + + config = mkIf cfg.enable { + users = { + groups.weechat = {}; + users.weechat = { + createHome = true; + group = "weechat"; + home = cfg.root; + isSystemUser = true; + }; + }; + + systemd.services.weechat = { + environment.WEECHAT_HOME = cfg.root; + serviceConfig = { + User = "weechat"; + Group = "weechat"; + RemainAfterExit = "yes"; + }; + script = "exec ${pkgs.screen}/bin/screen -Dm -S ${cfg.sessionName} ${cfg.binary}"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network.target" ]; + }; + }; + + meta.doc = ./weechat.xml; +} diff --git a/nixos/modules/services/misc/weechat.xml b/nixos/modules/services/misc/weechat.xml new file mode 100644 index 0000000000000..de86dede2eb55 --- /dev/null +++ b/nixos/modules/services/misc/weechat.xml @@ -0,0 +1,61 @@ +<chapter xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0" + xml:id="module-services-weechat"> + +<title>WeeChat</title> +<para><link xlink:href="https://weechat.org/">WeeChat</link> is a fast and extensible IRC client.</para> + +<section><title>Basic Usage</title> +<para> +By default, the module creates a +<literal><link xlink:href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</link></literal> unit +which runs the chat client in a detached +<literal><link xlink:href="https://www.gnu.org/software/screen/">screen</link></literal> session. + +</para> + +<para> +This can be done by enabling the <literal>weechat</literal> service: + +<programlisting> +{ ... }: + +{ + <link linkend="opt-services.weechat.enable">services.weechat.enable</link> = true; +} +</programlisting> +</para> +<para> +The service is managed by a dedicated user +named <literal>weechat</literal> in the state directory +<literal>/var/lib/weechat</literal>. +</para> +</section> +<section><title>Re-attaching to WeeChat</title> +<para> +WeeChat runs in a screen session owned by a dedicated user. To explicitly +allow your another user to attach to this session, the <literal>screenrc</literal> needs to be tweaked +by adding <link xlink:href="https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser">multiuser</link> support: + +<programlisting> +{ + <link linkend="opt-programs.screen.screenrc">programs.screen.screenrc</link> = '' + multiuser on + acladd normal_user + ''; +} +</programlisting> + +Now, the session can be re-attached like this: + +<programlisting> +screen -r weechat-screen +</programlisting> +</para> +<para> +<emphasis>The session name can be changed using <link linkend="opt-services.weechat.sessionName">services.weechat.sessionName.</link></emphasis> +</para> +</section> +</chapter> diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index 3e801f9b838db..c30647f5460be 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -235,7 +235,7 @@ in { but without GF_ prefix ''; default = {}; - type = types.attrsOf types.str; + type = with types; attrsOf (either str path); }; }; diff --git a/nixos/modules/services/monitoring/riemann.nix b/nixos/modules/services/monitoring/riemann.nix index 237de53456f98..13d2b1cc06027 100644 --- a/nixos/modules/services/monitoring/riemann.nix +++ b/nixos/modules/services/monitoring/riemann.nix @@ -17,9 +17,9 @@ let launcher = writeScriptBin "riemann" '' #!/bin/sh - exec ${jdk}/bin/java ${concatStringsSep "\n" cfg.extraJavaOpts} \ + exec ${jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOpts} \ -cp ${classpath} \ - riemann.bin ${writeText "riemann-config.clj" riemannConfig} + riemann.bin ${cfg.configFile} ''; in { @@ -37,7 +37,8 @@ in { config = mkOption { type = types.lines; description = '' - Contents of the Riemann configuration file. + Contents of the Riemann configuration file. For more complicated + config you should use configFile. ''; }; configFiles = mkOption { @@ -47,7 +48,15 @@ in { Extra files containing Riemann configuration. These files will be loaded at runtime by Riemann (with Clojure's <literal>load-file</literal> function) at the end of the - configuration. + configuration if you use the config option, this is ignored if you + use configFile. + ''; + }; + configFile = mkOption { + type = types.str; + description = '' + A Riemann config file. Any files in the same directory as this file + will be added to the classpath by Riemann. ''; }; extraClasspathEntries = mkOption { @@ -77,6 +86,10 @@ in { group = "riemann"; }; + services.riemann.configFile = mkDefault ( + writeText "riemann-config.clj" riemannConfig + ); + systemd.services.riemann = { wantedBy = [ "multi-user.target" ]; path = [ inetutils ]; @@ -84,6 +97,7 @@ in { User = "riemann"; ExecStart = "${launcher}/bin/riemann"; }; + serviceConfig.LimitNOFILE = 65536; }; }; diff --git a/nixos/modules/services/networking/hylafax/default.nix b/nixos/modules/services/networking/hylafax/default.nix new file mode 100644 index 0000000000000..4c63b822d1656 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/default.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: + +{ + + imports = [ + ./options.nix + ./systemd.nix + ]; + + config = lib.modules.mkIf config.services.hylafax.enable { + environment.systemPackages = [ pkgs.hylafaxplus ]; + users.users.uucp = { + uid = config.ids.uids.uucp; + group = "uucp"; + description = "Unix-to-Unix CoPy system"; + isSystemUser = true; + inherit (config.users.users.nobody) home; + }; + assertions = [{ + assertion = config.services.hylafax.modems != {}; + message = '' + HylaFAX cannot be used without modems. + Please define at least one modem with + <option>config.services.hylafax.modems</option>. + ''; + }]; + }; + +} diff --git a/nixos/modules/services/networking/hylafax/faxq-default.nix b/nixos/modules/services/networking/hylafax/faxq-default.nix new file mode 100644 index 0000000000000..a2630ce66b710 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/faxq-default.nix @@ -0,0 +1,12 @@ +{ ... }: + +# see man:hylafax-config(5) + +{ + + ModemGroup = [ ''"any:.*"'' ]; + ServerTracing = "0x78701"; + SessionTracing = "0x78701"; + UUCPLockDir = "/var/lock"; + +} diff --git a/nixos/modules/services/networking/hylafax/faxq-wait.sh b/nixos/modules/services/networking/hylafax/faxq-wait.sh new file mode 100755 index 0000000000000..8c39e9d20c182 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/faxq-wait.sh @@ -0,0 +1,29 @@ +#! @shell@ -e + +# skip this if there are no modems at all +if ! stat -t "@spoolAreaPath@"/etc/config.* >/dev/null 2>&1 +then + exit 0 +fi + +echo "faxq started, waiting for modem(s) to initialize..." + +for i in `seq @timeoutSec@0 -1 0` # gracefully timeout +do + sleep 0.1 + # done if status files exist, but don't mention initialization + if \ + stat -t "@spoolAreaPath@"/status/* >/dev/null 2>&1 \ + && \ + ! grep --silent --ignore-case 'initializing server' \ + "@spoolAreaPath@"/status/* + then + echo "modem(s) apparently ready" + exit 0 + fi + # if i reached 0, modems probably failed to initialize + if test $i -eq 0 + then + echo "warning: modem initialization timed out" + fi +done diff --git a/nixos/modules/services/networking/hylafax/hfaxd-default.nix b/nixos/modules/services/networking/hylafax/hfaxd-default.nix new file mode 100644 index 0000000000000..8999dae57f416 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/hfaxd-default.nix @@ -0,0 +1,10 @@ +{ ... }: + +# see man:hfaxd(8) + +{ + + ServerTracing = "0x91"; + XferLogFile = "/clientlog"; + +} diff --git a/nixos/modules/services/networking/hylafax/modem-default.nix b/nixos/modules/services/networking/hylafax/modem-default.nix new file mode 100644 index 0000000000000..7529b5b0aafdd --- /dev/null +++ b/nixos/modules/services/networking/hylafax/modem-default.nix @@ -0,0 +1,22 @@ +{ pkgs, ... }: + +# see man:hylafax-config(5) + +{ + + TagLineFont = "etc/LiberationSans-25.pcf"; + TagLineLocale = ''en_US.UTF-8''; + + AdminGroup = "root"; # groups that can change server config + AnswerRotary = "fax"; # don't accept anything else but faxes + LogFileMode = "0640"; + PriorityScheduling = true; + RecvFileMode = "0640"; + ServerTracing = "0x78701"; + SessionTracing = "0x78701"; + UUCPLockDir = "/var/lock"; + + SendPageCmd = ''${pkgs.coreutils}/bin/false''; # prevent pager transmit + SendUUCPCmd = ''${pkgs.coreutils}/bin/false''; # prevent UUCP transmit + +} diff --git a/nixos/modules/services/networking/hylafax/options.nix b/nixos/modules/services/networking/hylafax/options.nix new file mode 100644 index 0000000000000..4ac6d3fa8432d --- /dev/null +++ b/nixos/modules/services/networking/hylafax/options.nix @@ -0,0 +1,375 @@ +{ config, lib, pkgs, ... }: + +let + + inherit (lib.options) literalExample mkEnableOption mkOption; + inherit (lib.types) bool enum int lines loaOf nullOr path str submodule; + inherit (lib.modules) mkDefault mkIf mkMerge; + + commonDescr = '' + Values can be either strings or integers + (which will be added to the config file verbatimly) + or lists thereof + (which will be translated to multiple + lines with the same configuration key). + Boolean values are translated to "Yes" or "No". + The default contains some reasonable + configuration to yield an operational system. + ''; + + str1 = lib.types.addCheck str (s: s!=""); # non-empty string + int1 = lib.types.addCheck int (i: i>0); # positive integer + + configAttrType = + # Options in HylaFAX configuration files can be + # booleans, strings, integers, or list thereof + # representing multiple config directives with the same key. + # This type definition resolves all + # those types into a list of strings. + let + inherit (lib.types) attrsOf coercedTo listOf; + innerType = coercedTo bool (x: if x then "Yes" else "No") + (coercedTo int (toString) str); + in + attrsOf (coercedTo innerType lib.singleton (listOf innerType)); + + cfg = config.services.hylafax; + + modemConfigOptions = { name, config, ... }: { + options = { + name = mkOption { + type = str1; + example = "ttyS1"; + description = '' + Name of modem device, + will be searched for in <filename>/dev</filename>. + ''; + }; + type = mkOption { + type = str1; + example = "cirrus"; + description = '' + Name of modem configuration file, + will be searched for in <filename>config</filename> + in the spooling area directory. + ''; + }; + config = mkOption { + type = configAttrType; + example = { + AreaCode = "49"; + LocalCode = "30"; + FAXNumber = "123456"; + LocalIdentifier = "LostInBerlin"; + }; + description = '' + Attribute set of values for the given modem. + ${commonDescr} + Options defined here override options in + <option>commonModemConfig</option> for this modem. + ''; + }; + }; + config.name = mkDefault name; + config.config.Include = [ "config/${config.type}" ]; + }; + + defaultConfig = + let + inherit (config.security) wrapperDir; + inherit (config.services.mail.sendmailSetuidWrapper) program; + mkIfDefault = cond: value: mkIf cond (mkDefault value); + noWrapper = config.services.mail.sendmailSetuidWrapper==null; + # If a sendmail setuid wrapper exists, + # we add the path to the default configuration file. + # Otherwise, we use `false` to provoke + # an error if hylafax tries to use it. + c.sendmailPath = mkMerge [ + (mkIfDefault noWrapper ''${pkgs.coreutils}/bin/false'') + (mkIfDefault (!noWrapper) ''${wrapperDir}/${program}'') + ]; + importDefaultConfig = file: + lib.attrsets.mapAttrs + (lib.trivial.const mkDefault) + (import file { inherit pkgs; }); + c.commonModemConfig = importDefaultConfig ./modem-default.nix; + c.faxqConfig = importDefaultConfig ./faxq-default.nix; + c.hfaxdConfig = importDefaultConfig ./hfaxd-default.nix; + in + c; + + localConfig = + let + c.hfaxdConfig.UserAccessFile = cfg.userAccessFile; + c.faxqConfig = lib.attrsets.mapAttrs + (lib.trivial.const (v: mkIf (v!=null) v)) + { + AreaCode = cfg.areaCode; + CountryCode = cfg.countryCode; + LongDistancePrefix = cfg.longDistancePrefix; + InternationalPrefix = cfg.internationalPrefix; + }; + c.commonModemConfig = c.faxqConfig; + in + c; + +in + + +{ + + + options.services.hylafax = { + + enable = mkEnableOption ''HylaFAX server''; + + autostart = mkOption { + type = bool; + default = true; + example = false; + description = '' + Autostart the HylaFAX queue manager at system start. + If this is <literal>false</literal>, the queue manager + will still be started if there are pending + jobs or if a user tries to connect to it. + ''; + }; + + countryCode = mkOption { + type = nullOr str1; + default = null; + example = "49"; + description = ''Country code for server and all modems.''; + }; + + areaCode = mkOption { + type = nullOr str1; + default = null; + example = "30"; + description = ''Area code for server and all modems.''; + }; + + longDistancePrefix = mkOption { + type = nullOr str; + default = null; + example = "0"; + description = ''Long distance prefix for server and all modems.''; + }; + + internationalPrefix = mkOption { + type = nullOr str; + default = null; + example = "00"; + description = ''International prefix for server and all modems.''; + }; + + spoolAreaPath = mkOption { + type = path; + default = "/var/spool/fax"; + description = '' + The spooling area will be created/maintained + at the location given here. + ''; + }; + + userAccessFile = mkOption { + type = path; + default = "/etc/hosts.hfaxd"; + description = '' + The <filename>hosts.hfaxd</filename> + file entry in the spooling area + will be symlinked to the location given here. + This file must exist and be + readable only by the <literal>uucp</literal> user. + See hosts.hfaxd(5) for details. + This configuration permits access for all users: + <literal> + environment.etc."hosts.hfaxd" = { + mode = "0600"; + user = "uucp"; + text = ".*"; + }; + </literal> + Note that host-based access can be controlled with + <option>config.systemd.sockets.hylafax-hfaxd.listenStreams</option>; + by default, only 127.0.0.1 is permitted to connect. + ''; + }; + + sendmailPath = mkOption { + type = path; + example = literalExample "''${pkgs.postfix}/bin/sendmail"; + # '' ; # fix vim + description = '' + Path to <filename>sendmail</filename> program. + The default uses the local sendmail wrapper + (see <option>config.services.mail.sendmailSetuidWrapper</option>), + otherwise the <filename>false</filename> + binary to cause an error if used. + ''; + }; + + hfaxdConfig = mkOption { + type = configAttrType; + example.RecvqProtection = "0400"; + description = '' + Attribute set of lines for the global + hfaxd config file <filename>etc/hfaxd.conf</filename>. + ${commonDescr} + ''; + }; + + faxqConfig = mkOption { + type = configAttrType; + example = { + InternationalPrefix = "00"; + LongDistancePrefix = "0"; + }; + description = '' + Attribute set of lines for the global + faxq config file <filename>etc/config</filename>. + ${commonDescr} + ''; + }; + + commonModemConfig = mkOption { + type = configAttrType; + example = { + InternationalPrefix = "00"; + LongDistancePrefix = "0"; + }; + description = '' + Attribute set of default values for + modem config files <filename>etc/config.*</filename>. + ${commonDescr} + Think twice before changing + paths of fax-processing scripts. + ''; + }; + + modems = mkOption { + type = loaOf (submodule [ modemConfigOptions ]); + default = {}; + example.ttyS1 = { + type = "cirrus"; + config = { + FAXNumber = "123456"; + LocalIdentifier = "Smith"; + }; + }; + description = '' + Description of installed modems. + At least on modem must be defined + to enable the HylaFAX server. + ''; + }; + + spoolExtraInit = mkOption { + type = lines; + default = ""; + example = ''chmod 0755 . # everyone may read my faxes''; + description = '' + Additional shell code that is executed within the + spooling area directory right after its setup. + ''; + }; + + faxcron.enable.spoolInit = mkEnableOption '' + Purge old files from the spooling area with + <filename>faxcron</filename> + each time the spooling area is initialized. + ''; + faxcron.enable.frequency = mkOption { + type = nullOr str1; + default = null; + example = "daily"; + description = '' + Purge old files from the spooling area with + <filename>faxcron</filename> with the given frequency + (see systemd.time(7)). + ''; + }; + faxcron.infoDays = mkOption { + type = int1; + default = 30; + description = '' + Set the expiration time for data in the + remote machine information directory in days. + ''; + }; + faxcron.logDays = mkOption { + type = int1; + default = 30; + description = '' + Set the expiration time for + session trace log files in days. + ''; + }; + faxcron.rcvDays = mkOption { + type = int1; + default = 7; + description = '' + Set the expiration time for files in + the received facsimile queue in days. + ''; + }; + + faxqclean.enable.spoolInit = mkEnableOption '' + Purge old files from the spooling area with + <filename>faxqclean</filename> + each time the spooling area is initialized. + ''; + faxqclean.enable.frequency = mkOption { + type = nullOr str1; + default = null; + example = "daily"; + description = '' + Purge old files from the spooling area with + <filename>faxcron</filename> with the given frequency + (see systemd.time(7)). + ''; + }; + faxqclean.archiving = mkOption { + type = enum [ "never" "as-flagged" "always" ]; + default = "as-flagged"; + example = "always"; + description = '' + Enable or suppress job archiving: + <literal>never</literal> disables job archiving, + <literal>as-flagged</literal> archives jobs that + have been flagged for archiving by sendfax, + <literal>always</literal> forces archiving of all jobs. + See also sendfax(1) and faxqclean(8). + ''; + }; + faxqclean.doneqMinutes = mkOption { + type = int1; + default = 15; + example = literalExample ''24*60''; + description = '' + Set the job + age threshold (in minutes) that controls how long + jobs may reside in the doneq directory. + ''; + }; + faxqclean.docqMinutes = mkOption { + type = int1; + default = 60; + example = literalExample ''24*60''; + description = '' + Set the document + age threshold (in minutes) that controls how long + unreferenced files may reside in the docq directory. + ''; + }; + + }; + + + config.services.hylafax = + mkIf + (config.services.hylafax.enable) + (mkMerge [ defaultConfig localConfig ]) + ; + +} diff --git a/nixos/modules/services/networking/hylafax/spool.sh b/nixos/modules/services/networking/hylafax/spool.sh new file mode 100755 index 0000000000000..31e930e8c5976 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/spool.sh @@ -0,0 +1,111 @@ +#! @shell@ -e + +# The following lines create/update the HylaFAX spool directory: +# Subdirectories/files with persistent data are kept, +# other directories/files are removed/recreated, +# mostly from the template spool +# directory in the HylaFAX package. + +# This block explains how the spool area is +# derived from the spool template in the HylaFAX package: +# +# + capital letter: directory; file otherwise +# + P/p: persistent directory +# + F/f: directory with symlinks per entry +# + T/t: temporary data +# + S/s: single symlink into package +# | +# | + u: change ownership to uucp:uucp +# | + U: ..also change access mode to user-only +# | | +# archive P U +# bin S +# client T u (client connection info) +# config S +# COPYRIGHT s +# dev T u (maybe some FIFOs) +# docq P U +# doneq P U +# etc F contains customized config files! +# etc/hosts.hfaxd f +# etc/xferfaxlog f +# info P u (database of called devices) +# log P u (communication logs) +# pollq P U +# recvq P u +# sendq P U +# status T u (modem status info files) +# tmp T U + + +shopt -s dotglob # if bash sees "*", it also includes dot files +lnsym () { ln --symbol "$@" ; } +lnsymfrc () { ln --symbolic --force "$@" ; } +cprd () { cp --remove-destination "$@" ; } +update () { install --owner=@faxuser@ --group=@faxgroup@ "$@" ; } + + +## create/update spooling area + +update --mode=0750 -d "@spoolAreaPath@" +cd "@spoolAreaPath@" + +persist=(archive docq doneq info log pollq recvq sendq) + +# remove entries that don't belong here +touch dummy # ensure "*" resolves to something +for k in * +do + keep=0 + for j in "${persist[@]}" xferfaxlog clientlog faxcron.lastrun + do + if test "$k" == "$j" + then + keep=1 + break + fi + done + if test "$keep" == "0" + then + rm --recursive "$k" + fi +done + +# create persistent data directories (unless they exist already) +update --mode=0700 -d "${persist[@]}" +chmod 0755 info log recvq + +# create ``xferfaxlog``, ``faxcron.lastrun``, ``clientlog`` +touch clientlog faxcron.lastrun xferfaxlog +chown @faxuser@:@faxgroup@ clientlog faxcron.lastrun xferfaxlog + +# create symlinks for frozen directories/files +lnsym --target-directory=. "@hylafax@"/spool/{COPYRIGHT,bin,config} + +# create empty temporary directories +update --mode=0700 -d client dev status +update -d tmp + + +## create and fill etc + +install -d "@spoolAreaPath@/etc" +cd "@spoolAreaPath@/etc" + +# create symlinks to all files in template's etc +lnsym --target-directory=. "@hylafax@/spool/etc"/* + +# set LOCKDIR in setup.cache +sed --regexp-extended 's|^(UUCP_LOCKDIR=).*$|\1'"'@lockPath@'|g" --in-place setup.cache + +# etc/{xferfaxlog,lastrun} are stored in the spool root +lnsymfrc --target-directory=. ../xferfaxlog +lnsymfrc --no-target-directory ../faxcron.lastrun lastrun + +# etc/hosts.hfaxd is provided by the NixOS configuration +lnsymfrc --no-target-directory "@userAccessFile@" hosts.hfaxd + +# etc/config and etc/config.${DEVID} must be copied: +# hfaxd reads these file after locking itself up in a chroot +cprd --no-target-directory "@globalConfigPath@" config +cprd --target-directory=. "@modemConfigPath@"/* diff --git a/nixos/modules/services/networking/hylafax/systemd.nix b/nixos/modules/services/networking/hylafax/systemd.nix new file mode 100644 index 0000000000000..91d9c1a37da64 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/systemd.nix @@ -0,0 +1,249 @@ +{ config, lib, pkgs, ... }: + + +let + + inherit (lib) mkIf mkMerge; + inherit (lib) concatStringsSep optionalString; + + cfg = config.services.hylafax; + mapModems = lib.flip map (lib.attrValues cfg.modems); + + mkConfigFile = name: conf: + # creates hylafax config file, + # makes sure "Include" is listed *first* + let + mkLines = conf: + (lib.concatLists + (lib.flip lib.mapAttrsToList conf + (k: map (v: ''${k}: ${v}'') + ))); + include = mkLines { Include = conf.Include or []; }; + other = mkLines ( conf // { Include = []; } ); + in + pkgs.writeText ''hylafax-config${name}'' + (concatStringsSep "\n" (include ++ other)); + + globalConfigPath = mkConfigFile "" cfg.faxqConfig; + + modemConfigPath = + let + mkModemConfigFile = { config, name, ... }: + mkConfigFile ''.${name}'' + (cfg.commonModemConfig // config); + mkLine = { name, type, ... }@modem: '' + # check if modem config file exists: + test -f "${pkgs.hylafaxplus}/spool/config/${type}" + ln \ + --symbolic \ + --no-target-directory \ + "${mkModemConfigFile modem}" \ + "$out/config.${name}" + ''; + in + pkgs.runCommand "hylafax-config-modems" {} + ''mkdir --parents "$out/" ${concatStringsSep "\n" (mapModems mkLine)}''; + + setupSpoolScript = pkgs.substituteAll { + name = "hylafax-setup-spool.sh"; + src = ./spool.sh; + isExecutable = true; + inherit (pkgs.stdenv) shell; + hylafax = pkgs.hylafaxplus; + faxuser = "uucp"; + faxgroup = "uucp"; + lockPath = "/var/lock"; + inherit globalConfigPath modemConfigPath; + inherit (cfg) sendmailPath spoolAreaPath userAccessFile; + }; + + waitFaxqScript = pkgs.substituteAll { + # This script checks the modems status files + # and waits until all modems report readiness. + name = "hylafax-faxq-wait-start.sh"; + src = ./faxq-wait.sh; + isExecutable = true; + timeoutSec = toString 10; + inherit (pkgs.stdenv) shell; + inherit (cfg) spoolAreaPath; + }; + + sockets."hylafax-hfaxd" = { + description = "HylaFAX server socket"; + documentation = [ "man:hfaxd(8)" ]; + wantedBy = [ "multi-user.target" ]; + listenStreams = [ "127.0.0.1:4559" ]; + socketConfig.FreeBind = true; + socketConfig.Accept = true; + }; + + paths."hylafax-faxq" = { + description = "HylaFAX queue manager sendq watch"; + documentation = [ "man:faxq(8)" "man:sendq(5)" ]; + wantedBy = [ "multi-user.target" ]; + pathConfig.PathExistsGlob = [ ''${cfg.spoolAreaPath}/sendq/q*'' ]; + }; + + timers = mkMerge [ + ( + mkIf (cfg.faxcron.enable.frequency!=null) + { "hylafax-faxcron".timerConfig.Persistent = true; } + ) + ( + mkIf (cfg.faxqclean.enable.frequency!=null) + { "hylafax-faxqclean".timerConfig.Persistent = true; } + ) + ]; + + hardenService = + # Add some common systemd service hardening settings, + # but allow each service (here) to override + # settings by explicitely setting those to `null`. + # More hardening would be nice but makes + # customizing hylafax setups very difficult. + # If at all, it should only be added along + # with some options to customize it. + let + hardening = { + PrivateDevices = true; # breaks /dev/tty... + PrivateNetwork = true; + PrivateTmp = true; + ProtectControlGroups = true; + #ProtectHome = true; # breaks custom spool dirs + ProtectKernelModules = true; + ProtectKernelTunables = true; + #ProtectSystem = "strict"; # breaks custom spool dirs + RestrictNamespaces = true; + RestrictRealtime = true; + }; + filter = key: value: (value != null) || ! (lib.hasAttr key hardening); + apply = service: lib.filterAttrs filter (hardening // (service.serviceConfig or {})); + in + service: service // { serviceConfig = apply service; }; + + services."hylafax-spool" = { + description = "HylaFAX spool area preparation"; + documentation = [ "man:hylafax-server(4)" ]; + script = '' + ${setupSpoolScript} + cd "${cfg.spoolAreaPath}" + ${cfg.spoolExtraInit} + if ! test -f "${cfg.spoolAreaPath}/etc/hosts.hfaxd" + then + echo hosts.hfaxd is missing + exit 1 + fi + ''; + serviceConfig.ExecStop = ''${setupSpoolScript}''; + serviceConfig.RemainAfterExit = true; + serviceConfig.Type = "oneshot"; + unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ]; + }; + + services."hylafax-faxq" = { + description = "HylaFAX queue manager"; + documentation = [ "man:faxq(8)" ]; + requires = [ "hylafax-spool.service" ]; + after = [ "hylafax-spool.service" ]; + wants = mapModems ( { name, ... }: ''hylafax-faxgetty@${name}.service'' ); + wantedBy = mkIf cfg.autostart [ "multi-user.target" ]; + serviceConfig.Type = "forking"; + serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/faxq -q "${cfg.spoolAreaPath}"''; + # This delays the "readiness" of this service until + # all modems are initialized (or a timeout is reached). + # Otherwise, sending a fax with the fax service + # stopped will always yield a failed send attempt: + # The fax service is started when the job is created with + # `sendfax`, but modems need some time to initialize. + serviceConfig.ExecStartPost = [ ''${waitFaxqScript}'' ]; + # faxquit fails if the pipe is already gone + # (e.g. the service is already stopping) + serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}"''; + # disable some systemd hardening settings + serviceConfig.PrivateDevices = null; + serviceConfig.RestrictRealtime = null; + }; + + services."hylafax-hfaxd@" = { + description = "HylaFAX server"; + documentation = [ "man:hfaxd(8)" ]; + after = [ "hylafax-faxq.service" ]; + requires = [ "hylafax-faxq.service" ]; + serviceConfig.StandardInput = "socket"; + serviceConfig.StandardOutput = "socket"; + serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/hfaxd -q "${cfg.spoolAreaPath}" -d -I''; + unitConfig.RequiresMountsFor = [ cfg.userAccessFile ]; + # disable some systemd hardening settings + serviceConfig.PrivateDevices = null; + serviceConfig.PrivateNetwork = null; + }; + + services."hylafax-faxcron" = rec { + description = "HylaFAX spool area maintenance"; + documentation = [ "man:faxcron(8)" ]; + after = [ "hylafax-spool.service" ]; + requires = [ "hylafax-spool.service" ]; + wantedBy = mkIf cfg.faxcron.enable.spoolInit requires; + startAt = mkIf (cfg.faxcron.enable.frequency!=null) cfg.faxcron.enable.frequency; + serviceConfig.ExecStart = concatStringsSep " " [ + ''${pkgs.hylafaxplus}/spool/bin/faxcron'' + ''-q "${cfg.spoolAreaPath}"'' + ''-info ${toString cfg.faxcron.infoDays}'' + ''-log ${toString cfg.faxcron.logDays}'' + ''-rcv ${toString cfg.faxcron.rcvDays}'' + ]; + }; + + services."hylafax-faxqclean" = rec { + description = "HylaFAX spool area queue cleaner"; + documentation = [ "man:faxqclean(8)" ]; + after = [ "hylafax-spool.service" ]; + requires = [ "hylafax-spool.service" ]; + wantedBy = mkIf cfg.faxqclean.enable.spoolInit requires; + startAt = mkIf (cfg.faxqclean.enable.frequency!=null) cfg.faxqclean.enable.frequency; + serviceConfig.ExecStart = concatStringsSep " " [ + ''${pkgs.hylafaxplus}/spool/bin/faxqclean'' + ''-q "${cfg.spoolAreaPath}"'' + ''-v'' + (optionalString (cfg.faxqclean.archiving!="never") ''-a'') + (optionalString (cfg.faxqclean.archiving=="always") ''-A'') + ''-j ${toString (cfg.faxqclean.doneqMinutes*60)}'' + ''-d ${toString (cfg.faxqclean.docqMinutes*60)}'' + ]; + }; + + mkFaxgettyService = { name, ... }: + lib.nameValuePair ''hylafax-faxgetty@${name}'' rec { + description = "HylaFAX faxgetty for %I"; + documentation = [ "man:faxgetty(8)" ]; + bindsTo = [ "dev-%i.device" ]; + requires = [ "hylafax-spool.service" ]; + after = bindsTo ++ requires; + before = [ "hylafax-faxq.service" "getty.target" ]; + unitConfig.StopWhenUnneeded = true; + unitConfig.AssertFileNotEmpty = ''${cfg.spoolAreaPath}/etc/config.%I''; + serviceConfig.UtmpIdentifier = "%I"; + serviceConfig.TTYPath = "/dev/%I"; + serviceConfig.Restart = "always"; + serviceConfig.KillMode = "process"; + serviceConfig.IgnoreSIGPIPE = false; + serviceConfig.ExecStart = ''-${pkgs.hylafaxplus}/spool/bin/faxgetty -q "${cfg.spoolAreaPath}" /dev/%I''; + # faxquit fails if the pipe is already gone + # (e.g. the service is already stopping) + serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}" %I''; + # disable some systemd hardening settings + serviceConfig.PrivateDevices = null; + serviceConfig.RestrictRealtime = null; + }; + + modemServices = + lib.listToAttrs (mapModems mkFaxgettyService); + +in + +{ + config.systemd = mkIf cfg.enable { + inherit sockets timers paths; + services = lib.mapAttrs (lib.const hardenService) (services // modemServices); + }; +} diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix index 3afafaf3fed58..0e9b354cfcaff 100644 --- a/nixos/modules/services/networking/i2pd.nix +++ b/nixos/modules/services/networking/i2pd.nix @@ -8,6 +8,17 @@ let homeDir = "/var/lib/i2pd"; + strOpt = k: v: k + " = " + v; + boolOpt = k: v: k + " = " + boolToString v; + intOpt = k: v: k + " = " + toString v; + lstOpt = k: xs: k + " = " + concatStringsSep "," xs; + optionalNullString = o: s: optional (! isNull s) (strOpt o s); + optionalNullBool = o: b: optional (! isNull b) (boolOpt o b); + optionalNullInt = o: i: optional (! isNull i) (intOpt o i); + optionalEmptyList = o: l: optional ([] != l) (lstOpt o l); + + mkEnableTrueOption = name: mkEnableOption name // { default = true; }; + mkEndpointOpt = name: addr: port: { enable = mkEnableOption name; name = mkOption { @@ -18,42 +29,54 @@ let address = mkOption { type = types.str; default = addr; - description = "Bind address for ${name} endpoint. Default: " + addr; + description = "Bind address for ${name} endpoint."; }; port = mkOption { type = types.int; default = port; - description = "Bind port for ${name} endoint. Default: " + toString port; + description = "Bind port for ${name} endoint."; }; }; - mkKeyedEndpointOpt = name: addr: port: keyFile: + i2cpOpts = name: { + length = mkOption { + type = types.int; + description = "Guaranteed minimum hops for ${name} tunnels."; + default = 3; + }; + quantity = mkOption { + type = types.int; + description = "Number of simultaneous ${name} tunnels."; + default = 5; + }; + }; + + mkKeyedEndpointOpt = name: addr: port: keyloc: (mkEndpointOpt name addr port) // { keys = mkOption { - type = types.str; - default = ""; + type = with types; nullOr str; + default = keyloc; description = '' File to persist ${lib.toUpper name} keys. ''; }; - }; - - commonTunOpts = let - i2cpOpts = { - length = mkOption { - type = types.int; - description = "Guaranteed minimum hops."; - default = 3; + inbound = i2cpOpts name; + outbound = i2cpOpts name; + latency.min = mkOption { + type = with types; nullOr int; + description = "Min latency for tunnels."; + default = null; }; - quantity = mkOption { - type = types.int; - description = "Number of simultaneous tunnels."; - default = 5; + latency.max = mkOption { + type = with types; nullOr int; + description = "Max latency for tunnels."; + default = null; }; }; - in name: { - outbound = i2cpOpts; - inbound = i2cpOpts; + + commonTunOpts = name: { + outbound = i2cpOpts name; + inbound = i2cpOpts name; crypto.tagsToSend = mkOption { type = types.int; description = "Number of ElGamal/AES tags to send."; @@ -70,94 +93,142 @@ let }; } // mkEndpointOpt name "127.0.0.1" 0; - i2pdConf = pkgs.writeText "i2pd.conf" '' - # DO NOT EDIT -- this file has been generated automatically. - loglevel = ${cfg.logLevel} - - ipv4 = ${boolToString cfg.enableIPv4} - ipv6 = ${boolToString cfg.enableIPv6} - notransit = ${boolToString cfg.notransit} - floodfill = ${boolToString cfg.floodfill} - netid = ${toString cfg.netid} - ${if isNull cfg.bandwidth then "" else "bandwidth = ${toString cfg.bandwidth}" } - ${if isNull cfg.port then "" else "port = ${toString cfg.port}"} - - [limits] - transittunnels = ${toString cfg.limits.transittunnels} - - [upnp] - enabled = ${boolToString cfg.upnp.enable} - name = ${cfg.upnp.name} - - [precomputation] - elgamal = ${boolToString cfg.precomputation.elgamal} - - [reseed] - verify = ${boolToString cfg.reseed.verify} - file = ${cfg.reseed.file} - urls = ${builtins.concatStringsSep "," cfg.reseed.urls} - - [addressbook] - defaulturl = ${cfg.addressbook.defaulturl} - subscriptions = ${builtins.concatStringsSep "," cfg.addressbook.subscriptions} - - ${flip concatMapStrings + sec = name: "\n[" + name + "]"; + notice = "# DO NOT EDIT -- this file has been generated automatically."; + i2pdConf = let + opts = [ + notice + (strOpt "loglevel" cfg.logLevel) + (boolOpt "logclftime" cfg.logCLFTime) + (boolOpt "ipv4" cfg.enableIPv4) + (boolOpt "ipv6" cfg.enableIPv6) + (boolOpt "notransit" cfg.notransit) + (boolOpt "floodfill" cfg.floodfill) + (intOpt "netid" cfg.netid) + ] ++ (optionalNullInt "bandwidth" cfg.bandwidth) + ++ (optionalNullInt "port" cfg.port) + ++ (optionalNullString "family" cfg.family) + ++ (optionalNullString "datadir" cfg.dataDir) + ++ (optionalNullInt "share" cfg.share) + ++ (optionalNullBool "ssu" cfg.ssu) + ++ (optionalNullBool "ntcp" cfg.ntcp) + ++ (optionalNullString "ntcpproxy" cfg.ntcpProxy) + ++ (optionalNullString "ifname" cfg.ifname) + ++ (optionalNullString "ifname4" cfg.ifname4) + ++ (optionalNullString "ifname6" cfg.ifname6) + ++ [ + (sec "limits") + (intOpt "transittunnels" cfg.limits.transittunnels) + (intOpt "coresize" cfg.limits.coreSize) + (intOpt "openfiles" cfg.limits.openFiles) + (intOpt "ntcphard" cfg.limits.ntcpHard) + (intOpt "ntcpsoft" cfg.limits.ntcpSoft) + (intOpt "ntcpthreads" cfg.limits.ntcpThreads) + (sec "upnp") + (boolOpt "enabled" cfg.upnp.enable) + (sec "precomputation") + (boolOpt "elgamal" cfg.precomputation.elgamal) + (sec "reseed") + (boolOpt "verify" cfg.reseed.verify) + ] ++ (optionalNullString "file" cfg.reseed.file) + ++ (optionalEmptyList "urls" cfg.reseed.urls) + ++ (optionalNullString "floodfill" cfg.reseed.floodfill) + ++ (optionalNullString "zipfile" cfg.reseed.zipfile) + ++ (optionalNullString "proxy" cfg.reseed.proxy) + ++ [ + (sec "trust") + (boolOpt "enabled" cfg.trust.enable) + (boolOpt "hidden" cfg.trust.hidden) + ] ++ (optionalEmptyList "routers" cfg.trust.routers) + ++ (optionalNullString "family" cfg.trust.family) + ++ [ + (sec "websockets") + (boolOpt "enabled" cfg.websocket.enable) + (strOpt "address" cfg.websocket.address) + (intOpt "port" cfg.websocket.port) + (sec "exploratory") + (intOpt "inbound.length" cfg.exploratory.inbound.length) + (intOpt "inbound.quantity" cfg.exploratory.inbound.quantity) + (intOpt "outbound.length" cfg.exploratory.outbound.length) + (intOpt "outbound.quantity" cfg.exploratory.outbound.quantity) + (sec "ntcp2") + (boolOpt "enabled" cfg.ntcp2.enable) + (boolOpt "published" cfg.ntcp2.published) + (intOpt "port" cfg.ntcp2.port) + (sec "addressbook") + (strOpt "defaulturl" cfg.addressbook.defaulturl) + ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions) + ++ (flip map (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto) - (proto: '' - [${proto.name}] - enabled = ${boolToString proto.enable} - address = ${proto.address} - port = ${toString proto.port} - ${if proto ? keys then "keys = ${proto.keys}" else ""} - ${if proto ? auth then "auth = ${boolToString proto.auth}" else ""} - ${if proto ? user then "user = ${proto.user}" else ""} - ${if proto ? pass then "pass = ${proto.pass}" else ""} - ${if proto ? outproxy then "outproxy = ${proto.outproxy}" else ""} - ${if proto ? outproxyPort then "outproxyport = ${toString proto.outproxyPort}" else ""} - '') - } - ''; - - i2pdTunnelConf = pkgs.writeText "i2pd-tunnels.conf" '' - # DO NOT EDIT -- this file has been generated automatically. - ${flip concatMapStrings + (proto: let protoOpts = [ + (sec proto.name) + (boolOpt "enabled" proto.enable) + (strOpt "address" proto.address) + (intOpt "port" proto.port) + ] ++ (if proto ? keys then optionalNullString "keys" proto.keys else []) + ++ (if proto ? auth then optionalNullBool "auth" proto.auth else []) + ++ (if proto ? user then optionalNullString "user" proto.user else []) + ++ (if proto ? pass then optionalNullString "pass" proto.pass else []) + ++ (if proto ? strictHeaders then optionalNullBool "strictheaders" proto.strictHeaders else []) + ++ (if proto ? hostname then optionalNullString "hostname" proto.hostname else []) + ++ (if proto ? outproxy then optionalNullString "outproxy" proto.outproxy else []) + ++ (if proto ? outproxyPort then optionalNullInt "outproxyport" proto.outproxyPort else []) + ++ (if proto ? outproxyEnable then optionalNullBool "outproxy.enabled" proto.outproxyEnable else []); + in (concatStringsSep "\n" protoOpts) + )); + in + pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts); + + tunnelConf = let opts = [ + notice + (flip map (collect (tun: tun ? port && tun ? destination) cfg.outTunnels) - (tun: '' - [${tun.name}] - type = client - destination = ${tun.destination} - destinationport = ${toString tun.destinationPort} - keys = ${tun.keys} - address = ${tun.address} - port = ${toString tun.port} - inbound.length = ${toString tun.inbound.length} - outbound.length = ${toString tun.outbound.length} - inbound.quantity = ${toString tun.inbound.quantity} - outbound.quantity = ${toString tun.outbound.quantity} - crypto.tagsToSend = ${toString tun.crypto.tagsToSend} - '') - } - ${flip concatMapStrings + (tun: let outTunOpts = [ + (sec tun.name) + "type = client" + (intOpt "port" tun.port) + (strOpt "destination" tun.destination) + ] ++ (if tun ? destinationPort then optionalNullInt "destinationport" tun.destinationPort else []) + ++ (if tun ? keys then + optionalNullString "keys" tun.keys else []) + ++ (if tun ? address then + optionalNullString "address" tun.address else []) + ++ (if tun ? inbound.length then + optionalNullInt "inbound.length" tun.inbound.length else []) + ++ (if tun ? inbound.quantity then + optionalNullInt "inbound.quantity" tun.inbound.quantity else []) + ++ (if tun ? outbound.length then + optionalNullInt "outbound.length" tun.outbound.length else []) + ++ (if tun ? outbound.quantity then + optionalNullInt "outbound.quantity" tun.outbound.quantity else []) + ++ (if tun ? crypto.tagsToSend then + optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend else []); + in concatStringsSep "\n" outTunOpts)) + (flip map (collect (tun: tun ? port && tun ? address) cfg.inTunnels) - (tun: '' - [${tun.name}] - type = server - destination = ${tun.destination} - keys = ${tun.keys} - host = ${tun.address} - port = ${toString tun.port} - inport = ${toString tun.inPort} - accesslist = ${builtins.concatStringsSep "," tun.accessList} - '') - } - ''; + (tun: let inTunOpts = [ + (sec tun.name) + "type = server" + (intOpt "port" tun.port) + (strOpt "host" tun.address) + ] ++ (if tun ? destination then + optionalNullString "destination" tun.destination else []) + ++ (if tun ? keys then + optionalNullString "keys" tun.keys else []) + ++ (if tun ? inPort then + optionalNullInt "inport" tun.inPort else []) + ++ (if tun ? accessList then + optionalEmptyList "accesslist" tun.accessList else []); + in concatStringsSep "\n" inTunOpts))]; + in pkgs.writeText "i2pd-tunnels.conf" opts; i2pdSh = pkgs.writeScriptBin "i2pd" '' #!/bin/sh exec ${pkgs.i2pd}/bin/i2pd \ ${if isNull cfg.address then "" else "--host="+cfg.address} \ + --service \ --conf=${i2pdConf} \ - --tunconf=${i2pdTunnelConf} + --tunconf=${tunnelConf} ''; in @@ -170,9 +241,7 @@ in services.i2pd = { - enable = mkOption { - type = types.bool; - default = false; + enable = mkEnableOption "I2Pd daemon" // { description = '' Enables I2Pd as a running service upon activation. Please read http://i2pd.readthedocs.io/en/latest/ for further @@ -192,6 +261,8 @@ in ''; }; + logCLFTime = mkEnableOption "Full CLF-formatted date and time to log"; + address = mkOption { type = with types; nullOr str; default = null; @@ -200,17 +271,72 @@ in ''; }; - notransit = mkOption { - type = types.bool; - default = false; + family = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Specify a family the router belongs to. + ''; + }; + + dataDir = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Alternative path to storage of i2pd data (RI, keys, peer profiles, ...) + ''; + }; + + share = mkOption { + type = types.int; + default = 100; + description = '' + Limit of transit traffic from max bandwidth in percents. + ''; + }; + + ifname = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Network interface to bind to. + ''; + }; + + ifname4 = mkOption { + type = with types; nullOr str; + default = null; + description = '' + IPv4 interface to bind to. + ''; + }; + + ifname6 = mkOption { + type = with types; nullOr str; + default = null; + description = '' + IPv6 interface to bind to. + ''; + }; + + ntcpProxy = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Proxy URL for NTCP transport. + ''; + }; + + ntcp = mkEnableTrueOption "ntcp"; + ssu = mkEnableTrueOption "ssu"; + + notransit = mkEnableOption "notransit" // { description = '' Tells the router to not accept transit tunnels during startup. ''; }; - floodfill = mkOption { - type = types.bool; - default = false; + floodfill = mkEnableOption "floodfill" // { description = '' If the router is declared to be unreachable and needs introduction nodes. ''; @@ -241,131 +367,178 @@ in ''; }; - enableIPv4 = mkOption { - type = types.bool; - default = true; + enableIPv4 = mkEnableTrueOption "IPv4 connectivity"; + enableIPv6 = mkEnableOption "IPv6 connectivity"; + nat = mkEnableTrueOption "NAT bypass"; + + upnp.enable = mkEnableOption "UPnP service discovery"; + upnp.name = mkOption { + type = types.str; + default = "I2Pd"; description = '' - Enables IPv4 connectivity. Enabled by default. + Name i2pd appears in UPnP forwardings list. ''; }; - enableIPv6 = mkOption { - type = types.bool; - default = false; + precomputation.elgamal = mkEnableTrueOption "Precomputed ElGamal tables" // { description = '' - Enables IPv6 connectivity. Disabled by default. + Whenever to use precomputated tables for ElGamal. + <command>i2pd</command> defaults to <literal>false</literal> + to save 64M of memory (and looses some performance). + + We default to <literal>true</literal> as that is what most + users want anyway. ''; }; - nat = mkOption { - type = types.bool; - default = true; + reseed.verify = mkEnableOption "SU3 signature verification"; + + reseed.file = mkOption { + type = with types; nullOr str; + default = null; description = '' - Assume router is NATed. Enabled by default. + Full path to SU3 file to reseed from. ''; }; - upnp = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - Enables UPnP. - ''; - }; + reseed.urls = mkOption { + type = with types; listOf str; + default = []; + description = '' + Reseed URLs. + ''; + }; - name = mkOption { - type = types.str; - default = "I2Pd"; - description = '' - Name i2pd appears in UPnP forwardings list. - ''; - }; + reseed.floodfill = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Path to router info of floodfill to reseed from. + ''; }; - precomputation.elgamal = mkOption { - type = types.bool; - default = true; + reseed.zipfile = mkOption { + type = with types; nullOr str; + default = null; description = '' - Whenever to use precomputated tables for ElGamal. - <command>i2pd</command> defaults to <literal>false</literal> - to save 64M of memory (and looses some performance). + Path to local .zip file to reseed from. + ''; + }; - We default to <literal>true</literal> as that is what most - users want anyway. + reseed.proxy = mkOption { + type = with types; nullOr str; + default = null; + description = '' + URL for reseed proxy, supports http/socks. ''; }; - reseed = { - verify = mkOption { - type = types.bool; - default = false; - description = '' - Request SU3 signature verification - ''; - }; + addressbook.defaulturl = mkOption { + type = types.str; + default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"; + description = '' + AddressBook subscription URL for initial setup + ''; + }; + addressbook.subscriptions = mkOption { + type = with types; listOf str; + default = [ + "http://inr.i2p/export/alive-hosts.txt" + "http://i2p-projekt.i2p/hosts.txt" + "http://stats.i2p/cgi-bin/newhosts.txt" + ]; + description = '' + AddressBook subscription URLs + ''; + }; - file = mkOption { - type = types.str; - default = ""; - description = '' - Full path to SU3 file to reseed from - ''; - }; + trust.enable = mkEnableOption "Explicit trust options"; - urls = mkOption { - type = with types; listOf str; - default = [ - "https://reseed.i2p-project.de/" - "https://i2p.mooo.com/netDb/" - "https://netdb.i2p2.no/" - "https://us.reseed.i2p2.no:444/" - "https://uk.reseed.i2p2.no:444/" - "https://i2p.manas.ca:8443/" - ]; - description = '' - Reseed URLs - ''; - }; + trust.family = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Router Familiy to trust for first hops. + ''; }; - addressbook = { - defaulturl = mkOption { - type = types.str; - default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"; - description = '' - AddressBook subscription URL for initial setup - ''; - }; - subscriptions = mkOption { - type = with types; listOf str; - default = [ - "http://inr.i2p/export/alive-hosts.txt" - "http://i2p-projekt.i2p/hosts.txt" - "http://stats.i2p/cgi-bin/newhosts.txt" - ]; - description = '' - AddressBook subscription URLs - ''; - }; + trust.routers = mkOption { + type = with types; listOf str; + default = []; + description = '' + Only connect to the listed routers. + ''; + }; + + trust.hidden = mkEnableOption "Router concealment."; + + websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666; + + exploratory.inbound = i2cpOpts "exploratory"; + exploratory.outbound = i2cpOpts "exploratory"; + + ntcp2.enable = mkEnableTrueOption "NTCP2."; + ntcp2.published = mkEnableOption "NTCP2 publication."; + ntcp2.port = mkOption { + type = types.int; + default = 0; + description = '' + Port to listen for incoming NTCP2 connections (0=auto). + ''; }; limits.transittunnels = mkOption { type = types.int; default = 2500; description = '' - Maximum number of active transit sessions + Maximum number of active transit sessions. + ''; + }; + + limits.coreSize = mkOption { + type = types.int; + default = 0; + description = '' + Maximum size of corefile in Kb (0 - use system limit). + ''; + }; + + limits.openFiles = mkOption { + type = types.int; + default = 0; + description = '' + Maximum number of open files (0 - use system default). + ''; + }; + + limits.ntcpHard = mkOption { + type = types.int; + default = 0; + description = '' + Maximum number of active transit sessions. + ''; + }; + + limits.ntcpSoft = mkOption { + type = types.int; + default = 0; + description = '' + Threshold to start probabalistic backoff with ntcp sessions (default: use system limit). + ''; + }; + + limits.ntcpThreads = mkOption { + type = types.int; + default = 1; + description = '' + Maximum number of threads used by NTCP DH worker. ''; }; proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // { - auth = mkOption { - type = types.bool; - default = false; - description = '' - Enable authentication for webconsole. - ''; - }; + + auth = mkEnableOption "Webconsole authentication"; + user = mkOption { type = types.str; default = "i2pd"; @@ -373,6 +546,7 @@ in Username for webconsole access ''; }; + pass = mkOption { type = types.str; default = "i2pd"; @@ -380,11 +554,35 @@ in Password for webconsole access. ''; }; + + strictHeaders = mkOption { + type = with types; nullOr bool; + default = null; + description = '' + Enable strict host checking on WebUI. + ''; + }; + + hostname = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Expected hostname for WebUI. + ''; + }; }; - proto.httpProxy = mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 ""; - proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "") + proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat") + // { + outproxy = mkOption { + type = with types; nullOr str; + default = null; + description = "Upstream outproxy bind address."; + }; + }; + proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat") // { + outproxyEnable = mkEnableOption "SOCKS outproxy"; outproxy = mkOption { type = types.str; default = "127.0.0.1"; @@ -408,8 +606,8 @@ in { name, ... }: { options = { destinationPort = mkOption { - type = types.int; - default = 0; + type = with types; nullOr int; + default = null; description = "Connect to particular port at destination."; }; } // commonTunOpts name; diff --git a/nixos/modules/services/networking/iperf3.nix b/nixos/modules/services/networking/iperf3.nix new file mode 100644 index 0000000000000..742404a5692f4 --- /dev/null +++ b/nixos/modules/services/networking/iperf3.nix @@ -0,0 +1,87 @@ +{ config, lib, pkgs, ... }: with lib; +let + cfg = config.services.iperf3; + + api = { + enable = mkEnableOption "iperf3 network throughput testing server"; + port = mkOption { + type = types.ints.u16; + default = 5201; + description = "Server port to listen on for iperf3 client requsts."; + }; + affinity = mkOption { + type = types.nullOr types.ints.unsigned; + default = null; + description = "CPU affinity for the process."; + }; + bind = mkOption { + type = types.nullOr types.str; + default = null; + description = "Bind to the specific interface associated with the given address."; + }; + verbose = mkOption { + type = types.bool; + default = false; + description = "Give more detailed output."; + }; + forceFlush = mkOption { + type = types.bool; + default = false; + description = "Force flushing output at every interval."; + }; + debug = mkOption { + type = types.bool; + default = false; + description = "Emit debugging output."; + }; + rsaPrivateKey = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to the RSA private key (not password-protected) used to decrypt authentication credentials from the client."; + }; + authorizedUsersFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to the configuration file containing authorized users credentials to run iperf tests."; + }; + extraFlags = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Extra flags to pass to iperf3(1)."; + }; + }; + + imp = { + systemd.services.iperf3 = { + description = "iperf3 daemon"; + unitConfig.Documentation = "man:iperf3(1) https://iperf.fr/iperf-doc.php"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + Restart = "on-failure"; + RestartSec = 2; + DynamicUser = true; + PrivateDevices = true; + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + ExecStart = '' + ${pkgs.iperf3}/bin/iperf \ + --server \ + --port ${toString cfg.port} \ + ${optionalString (cfg.affinity != null) "--affinity ${toString cfg.affinity}"} \ + ${optionalString (cfg.bind != null) "--bind ${cfg.bind}"} \ + ${optionalString (cfg.rsaPrivateKey != null) "--rsa-private-key-path ${cfg.rsaPrivateKey}"} \ + ${optionalString (cfg.authorizedUsersFile != null) "--authorized-users-path ${cfg.authorizedUsersFile}"} \ + ${optionalString cfg.verbose "--verbose"} \ + ${optionalString cfg.debug "--debug"} \ + ${optionalString cfg.forceFlush "--forceflush"} \ + ${escapeShellArgs cfg.extraFlags} + ''; + }; + }; + }; +in { + options.services.iperf3 = api; + config = mkIf cfg.enable imp; +} diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix index d5af4648e8f91..2d76e0676b243 100644 --- a/nixos/modules/services/networking/networkmanager.nix +++ b/nixos/modules/services/networking/networkmanager.nix @@ -406,25 +406,25 @@ in { { source = configFile; target = "NetworkManager/NetworkManager.conf"; } - { source = "${networkmanager-openvpn}/etc/NetworkManager/VPN/nm-openvpn-service.name"; + { source = "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name"; target = "NetworkManager/VPN/nm-openvpn-service.name"; } - { source = "${networkmanager-vpnc}/etc/NetworkManager/VPN/nm-vpnc-service.name"; + { source = "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name"; target = "NetworkManager/VPN/nm-vpnc-service.name"; } - { source = "${networkmanager-openconnect}/etc/NetworkManager/VPN/nm-openconnect-service.name"; + { source = "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name"; target = "NetworkManager/VPN/nm-openconnect-service.name"; } - { source = "${networkmanager-fortisslvpn}/etc/NetworkManager/VPN/nm-fortisslvpn-service.name"; + { source = "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name"; target = "NetworkManager/VPN/nm-fortisslvpn-service.name"; } - { source = "${networkmanager-l2tp}/etc/NetworkManager/VPN/nm-l2tp-service.name"; + { source = "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name"; target = "NetworkManager/VPN/nm-l2tp-service.name"; } - { source = "${networkmanager_strongswan}/etc/NetworkManager/VPN/nm-strongswan-service.name"; + { source = "${networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name"; target = "NetworkManager/VPN/nm-strongswan-service.name"; } - { source = "${networkmanager-iodine}/etc/NetworkManager/VPN/nm-iodine-service.name"; + { source = "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name"; target = "NetworkManager/VPN/nm-iodine-service.name"; } ] ++ optional (cfg.appendNameservers == [] || cfg.insertNameservers == []) diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix index 2377cb2c8f119..8b60799891ca3 100644 --- a/nixos/modules/services/networking/zeronet.nix +++ b/nixos/modules/services/networking/zeronet.nix @@ -12,6 +12,8 @@ let log_dir = ${cfg.logDir} '' + lib.optionalString (cfg.port != null) '' ui_port = ${toString cfg.port} + '' + lib.optionalString (cfg.torAlways) '' + tor = always '' + cfg.extraConfig; }; in with lib; { @@ -35,13 +37,19 @@ in with lib; { port = mkOption { type = types.nullOr types.int; default = null; - example = 15441; - description = "Optional zeronet port."; + example = 43110; + description = "Optional zeronet web UI port."; }; tor = mkOption { type = types.bool; default = false; + description = "Use TOR for zeronet traffic where possible."; + }; + + torAlways = mkOption { + type = types.bool; + default = false; description = "Use TOR for all zeronet traffic."; }; @@ -60,9 +68,13 @@ in with lib; { services.tor = mkIf cfg.tor { enable = true; controlPort = 9051; - extraConfig = "CookieAuthentication 1"; + extraConfig = '' + CacheDirectoryGroupReadable 1 + CookieAuthentication 1 + CookieAuthFileGroupReadable 1 + ''; }; - + systemd.services.zeronet = { description = "zeronet"; after = [ "network.target" (optionalString cfg.tor "tor.service") ]; diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix index 62308428f3266..9f0261038d5be 100644 --- a/nixos/modules/services/security/sks.nix +++ b/nixos/modules/services/security/sks.nix @@ -3,78 +3,112 @@ with lib; let - cfg = config.services.sks; - sksPkg = cfg.package; -in - -{ +in { + meta.maintainers = with maintainers; [ primeos calbrecht jcumming ]; options = { services.sks = { - enable = mkEnableOption "sks"; + enable = mkEnableOption '' + SKS (synchronizing key server for OpenPGP) and start the database + server. You need to create "''${dataDir}/dump/*.gpg" for the initial + import''; package = mkOption { default = pkgs.sks; defaultText = "pkgs.sks"; type = types.package; - description = " - Which sks derivation to use. - "; + description = "Which SKS derivation to use."; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/db/sks"; + example = "/var/lib/sks"; + # TODO: The default might change to "/var/lib/sks" as this is more + # common. There's also https://github.com/NixOS/nixpkgs/issues/26256 + # and "/var/db" is not FHS compliant (seems to come from BSD). + description = '' + Data directory (-basedir) for SKS, where the database and all + configuration files are located (e.g. KDB, PTree, membership and + sksconf). + ''; }; hkpAddress = mkOption { default = [ "127.0.0.1" "::1" ]; type = types.listOf types.str; - description = " - Wich ip addresses the sks-keyserver is listening on. - "; + description = '' + Domain names, IPv4 and/or IPv6 addresses to listen on for HKP + requests. + ''; }; hkpPort = mkOption { default = 11371; - type = types.int; - description = " - Which port the sks-keyserver is listening on. - "; + type = types.ints.u16; + description = "HKP port to listen on."; + }; + + webroot = mkOption { + type = types.nullOr types.path; + default = "${sksPkg.webSamples}/OpenPKG"; + defaultText = "\${pkgs.sks.webSamples}/OpenPKG"; + description = '' + Source directory (will be symlinked, if not null) for the files the + built-in webserver should serve. SKS (''${pkgs.sks.webSamples}) + provides the following examples: "HTML5", "OpenPKG", and "XHTML+ES". + The index file can be named index.html, index.htm, index.xhtm, or + index.xhtml. Files with the extensions .css, .es, .js, .jpg, .jpeg, + .png, or .gif are supported. Subdirectories and filenames with + anything other than alphanumeric characters and the '.' character + will be ignored. + ''; }; }; }; config = mkIf cfg.enable { - environment.systemPackages = [ sksPkg ]; - - users.users.sks = { - createHome = true; - home = "/var/db/sks"; - isSystemUser = true; - shell = "${pkgs.coreutils}/bin/true"; + users = { + users.sks = { + isSystemUser = true; + description = "SKS user"; + home = cfg.dataDir; + createHome = true; + group = "sks"; + useDefaultShell = true; + packages = [ sksPkg pkgs.db ]; + }; + groups.sks = { }; }; systemd.services = let hkpAddress = "'" + (builtins.concatStringsSep " " cfg.hkpAddress) + "'" ; hkpPort = builtins.toString cfg.hkpPort; - home = config.users.users.sks.home; - user = config.users.users.sks.name; in { - sks-keyserver = { + "sks-db" = { + description = "SKS database server"; + after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; preStart = '' - mkdir -p ${home}/dump - ${pkgs.sks}/bin/sks build ${home}/dump/*.gpg -n 10 -cache 100 || true #*/ - ${pkgs.sks}/bin/sks cleandb || true - ${pkgs.sks}/bin/sks pbuild -cache 20 -ptree_cache 70 || true + ${lib.optionalString (cfg.webroot != null) + "ln -sfT \"${cfg.webroot}\" web"} + mkdir -p dump + ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/ + ${sksPkg}/bin/sks cleandb || true + ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true ''; serviceConfig = { - WorkingDirectory = home; - User = user; + WorkingDirectory = "~"; + User = "sks"; + Group = "sks"; Restart = "always"; - ExecStart = "${pkgs.sks}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}"; + ExecStart = "${sksPkg}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}"; }; }; }; diff --git a/nixos/modules/services/system/kerberos.nix b/nixos/modules/services/system/kerberos.nix index d151385d2f9be..e2c45ed64ac0f 100644 --- a/nixos/modules/services/system/kerberos.nix +++ b/nixos/modules/services/system/kerberos.nix @@ -42,7 +42,7 @@ in protocol = "tcp"; user = "root"; server = "${pkgs.tcp_wrappers}/bin/tcpd"; - serverArgs = "${pkgs.heimdalFull}/bin/kadmind"; + serverArgs = "${pkgs.heimdalFull}/libexec/heimdal/kadmind"; }; systemd.services.kdc = { @@ -51,13 +51,13 @@ in preStart = '' mkdir -m 0755 -p ${stateDir} ''; - script = "${heimdalFull}/bin/kdc"; + script = "${heimdalFull}/libexec/heimdal/kdc"; }; systemd.services.kpasswdd = { description = "Kerberos Password Changing daemon"; wantedBy = [ "multi-user.target" ]; - script = "${heimdalFull}/bin/kpasswdd"; + script = "${heimdalFull}/libexec/heimdal/kpasswdd"; }; }; diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix index 6fa3ec3b92550..04e380b61530e 100644 --- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix +++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix @@ -66,7 +66,7 @@ in ''; }]; - security.wrappers = (import (builtins.toPath "${e.enlightenment}/e-wrappers.nix")).security.wrappers; + security.wrappers = (import "${e.enlightenment}/e-wrappers.nix").security.wrappers; environment.etc = singleton { source = xcfg.xkbDir; diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix index faf5214130db9..eb86f7b53bb63 100644 --- a/nixos/modules/services/x11/desktop-managers/gnome3.nix +++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix @@ -110,6 +110,7 @@ in { services.gnome3.gnome-terminal-server.enable = mkDefault true; services.gnome3.gnome-user-share.enable = mkDefault true; services.gnome3.gvfs.enable = true; + services.gnome3.rygel.enable = mkDefault true; services.gnome3.seahorse.enable = mkDefault true; services.gnome3.sushi.enable = mkDefault true; services.gnome3.tracker.enable = mkDefault true; diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index b3fe6caf62dc9..c3e469e4b8a12 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -419,7 +419,7 @@ while (my $f = <$listActiveUsers>) { my ($uid, $name) = ($+{uid}, $+{user}); print STDERR "reloading user units for $name...\n"; - system("su", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user daemon-reload"); + system("su", "-s", "@shell@", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user daemon-reload"); } close $listActiveUsers; diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index fff88e2c2bf34..9797ef641e4fc 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -115,6 +115,7 @@ let inherit (pkgs) utillinux coreutils; systemd = config.systemd.package; + inherit (pkgs.stdenv) shell; inherit children; kernelParams = config.boot.kernelParams; diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index 4bacf0f126a4b..63a6f7fbe0997 100644 --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -208,7 +208,6 @@ let "InitialCongestionWindow" "InitialAdvertisedReceiveWindow" "QuickAck" "MTUBytes" ]) - (assertHasField "Gateway") ]; checkDhcp = checkUnitConfig "DHCP" [ @@ -249,13 +248,14 @@ let # .network files have a [Link] section with different options than in .netlink files checkNetworkLink = checkUnitConfig "Link" [ (assertOnlyFields [ - "MACAddress" "MTUBytes" "ARP" "Unmanaged" "RequiredForOnline" + "MACAddress" "MTUBytes" "ARP" "Multicast" "Unmanaged" "RequiredForOnline" ]) (assertMacAddress "MACAddress") (assertByteFormat "MTUBytes") (assertValueOneOf "ARP" boolValues) + (assertValueOneOf "Multicast" boolValues) (assertValueOneOf "Unmanaged" boolValues) - (assertValueOneOf "RquiredForOnline" boolValues) + (assertValueOneOf "RequiredForOnline" boolValues) ]; diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 20a740ce1f0ca..815523093dde3 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -341,7 +341,7 @@ in You should try to make this ID unique among your machines. You can generate a random 32-bit ID using the following commands: - <literal>cksum /etc/machine-id | while read c rest; do printf "%x" $c; done</literal> + <literal>head -c 8 /etc/machine-id</literal> (this derives it from the machine-id that systemd generates) or |