diff options
Diffstat (limited to 'nixos/modules')
37 files changed, 1190 insertions, 206 deletions
diff --git a/nixos/modules/config/terminfo.nix b/nixos/modules/config/terminfo.nix index 1ae8e82c471..d1dbc4e0d05 100644 --- a/nixos/modules/config/terminfo.nix +++ b/nixos/modules/config/terminfo.nix @@ -6,12 +6,26 @@ with lib; { - options.environment.enableAllTerminfo = with lib; mkOption { - default = false; - type = types.bool; - description = lib.mdDoc '' - Whether to install all terminfo outputs - ''; + options = with lib; { + environment.enableAllTerminfo = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to install all terminfo outputs + ''; + }; + + security.sudo.keepTerminfo = mkOption { + default = config.security.sudo.package.pname != "sudo-rs"; + defaultText = literalMD '' + `true` unless using `sudo-rs` + ''; + type = types.bool; + description = lib.mdDoc '' + Whether to preserve the `TERMINFO` and `TERMINFO_DIRS` + environment variables, for `root` and the `wheel` group. + ''; + }; }; config = { @@ -54,7 +68,7 @@ with lib; export TERM=$TERM ''; - security.sudo.extraConfig = '' + security.sudo.extraConfig = mkIf config.security.sudo.keepTerminfo '' # Keep terminfo database for root and %wheel. Defaults:root,%wheel env_keep+=TERMINFO_DIRS diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix index 5158974c27b..785084209b0 100644 --- a/nixos/modules/config/users-groups.nix +++ b/nixos/modules/config/users-groups.nix @@ -700,6 +700,7 @@ in { environment.profiles = [ "$HOME/.nix-profile" + "\${XDG_STATE_HOME:-$HOME/.local/state}/nix/profile" "/etc/profiles/per-user/$USER" ]; diff --git a/nixos/modules/hardware/cpu/amd-sev.nix b/nixos/modules/hardware/cpu/amd-sev.nix index 28ee07f005b..08e1de49638 100644 --- a/nixos/modules/hardware/cpu/amd-sev.nix +++ b/nixos/modules/hardware/cpu/amd-sev.nix @@ -1,37 +1,43 @@ -{ config, lib, ... }: +{ config, options, lib, ... }: with lib; let - cfg = config.hardware.cpu.amd.sev; - defaultGroup = "sev"; -in - with lib; { - options.hardware.cpu.amd.sev = { - enable = mkEnableOption (lib.mdDoc "access to the AMD SEV device"); - user = mkOption { - description = lib.mdDoc "Owner to assign to the SEV device."; - type = types.str; - default = "root"; - }; - group = mkOption { - description = lib.mdDoc "Group to assign to the SEV device."; - type = types.str; - default = defaultGroup; - }; - mode = mkOption { - description = lib.mdDoc "Mode to set for the SEV device."; - type = types.str; - default = "0660"; - }; + cfgSev = config.hardware.cpu.amd.sev; + cfgSevGuest = config.hardware.cpu.amd.sevGuest; + + optionsFor = device: group: { + enable = mkEnableOption (lib.mdDoc "access to the AMD ${device} device"); + user = mkOption { + description = lib.mdDoc "Owner to assign to the ${device} device."; + type = types.str; + default = "root"; + }; + group = mkOption { + description = lib.mdDoc "Group to assign to the ${device} device."; + type = types.str; + default = group; }; + mode = mkOption { + description = lib.mdDoc "Mode to set for the ${device} device."; + type = types.str; + default = "0660"; + }; + }; +in +with lib; { + options.hardware.cpu.amd.sev = optionsFor "SEV" "sev"; + + options.hardware.cpu.amd.sevGuest = optionsFor "SEV guest" "sev-guest"; - config = mkIf cfg.enable { + config = mkMerge [ + # /dev/sev + (mkIf cfgSev.enable { assertions = [ { - assertion = hasAttr cfg.user config.users.users; + assertion = hasAttr cfgSev.user config.users.users; message = "Given user does not exist"; } { - assertion = (cfg.group == defaultGroup) || (hasAttr cfg.group config.users.groups); + assertion = (cfgSev.group == options.hardware.cpu.amd.sev.group.default) || (hasAttr cfgSev.group config.users.groups); message = "Given group does not exist"; } ]; @@ -40,12 +46,35 @@ in options kvm_amd sev=1 ''; - users.groups = optionalAttrs (cfg.group == defaultGroup) { - "${cfg.group}" = {}; + users.groups = optionalAttrs (cfgSev.group == options.hardware.cpu.amd.sev.group.default) { + "${cfgSev.group}" = { }; }; - services.udev.extraRules = with cfg; '' + services.udev.extraRules = with cfgSev; '' KERNEL=="sev", OWNER="${user}", GROUP="${group}", MODE="${mode}" ''; - }; - } + }) + + # /dev/sev-guest + (mkIf cfgSevGuest.enable { + assertions = [ + { + assertion = hasAttr cfgSevGuest.user config.users.users; + message = "Given user does not exist"; + } + { + assertion = (cfgSevGuest.group == options.hardware.cpu.amd.sevGuest.group.default) || (hasAttr cfgSevGuest.group config.users.groups); + message = "Given group does not exist"; + } + ]; + + users.groups = optionalAttrs (cfgSevGuest.group == options.hardware.cpu.amd.sevGuest.group.default) { + "${cfgSevGuest.group}" = { }; + }; + + services.udev.extraRules = with cfgSevGuest; '' + KERNEL=="sev-guest", OWNER="${user}", GROUP="${group}", MODE="${mode}" + ''; + }) + ]; +} diff --git a/nixos/modules/hardware/glasgow.nix b/nixos/modules/hardware/glasgow.nix new file mode 100644 index 00000000000..f8ebb772c47 --- /dev/null +++ b/nixos/modules/hardware/glasgow.nix @@ -0,0 +1,23 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.hardware.glasgow; + +in +{ + options.hardware.glasgow = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = lib.mdDoc '' + Enables Glasgow udev rules and ensures 'plugdev' group exists. + This is a prerequisite to using Glasgow without being root. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.udev.packages = [ pkgs.glasgow ]; + users.groups.plugdev = { }; + }; +} diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix index 6564b583464..78bcbbe2db5 100644 --- a/nixos/modules/installer/tools/tools.nix +++ b/nixos/modules/installer/tools/tools.nix @@ -134,8 +134,8 @@ in system.nixos-generate-config.configuration = mkDefault '' # Edit this configuration file to define what should be installed on - # your system. Help is available in the configuration.nix(5) man page - # and in the NixOS manual (accessible by running `nixos-help`). + # your system. Help is available in the configuration.nix(5) man page, on + # https://search.nixos.org/options and in the NixOS manual (`nixos-help`). { config, lib, pkgs, ... }: diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 811a46563fb..206d5eaf75d 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -61,6 +61,7 @@ ./hardware/flipperzero.nix ./hardware/flirc.nix ./hardware/gkraken.nix + ./hardware/glasgow.nix ./hardware/gpgsmartcards.nix ./hardware/hackrf.nix ./hardware/i2c.nix @@ -310,6 +311,7 @@ ./security/rngd.nix ./security/rtkit.nix ./security/sudo.nix + ./security/sudo-rs.nix ./security/systemd-confinement.nix ./security/tpm2.nix ./security/wrappers/default.nix @@ -497,6 +499,7 @@ ./services/games/quake3-server.nix ./services/games/teeworlds.nix ./services/games/terraria.nix + ./services/games/xonotic.nix ./services/hardware/acpid.nix ./services/hardware/actkbd.nix ./services/hardware/argonone.nix @@ -1483,6 +1486,7 @@ ./virtualisation/nixos-containers.nix ./virtualisation/oci-containers.nix ./virtualisation/openstack-options.nix + ./virtualisation/oci-options.nix ./virtualisation/openvswitch.nix ./virtualisation/parallels-guest.nix ./virtualisation/podman/default.nix diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix index c85097f45e9..b500b8f24b2 100644 --- a/nixos/modules/programs/fish.nix +++ b/nixos/modules/programs/fish.nix @@ -258,16 +258,13 @@ in preferLocalBuild = true; allowSubstitutes = false; }; - generateCompletions = package: pkgs.runCommand - "${package.name}_fish-completions" - ( - { - inherit package; - preferLocalBuild = true; - allowSubstitutes = false; - } - // optionalAttrs (package ? meta.priority) { meta.priority = package.meta.priority; } - ) + generateCompletions = package: pkgs.runCommandLocal + ( with lib.strings; let + storeLength = stringLength storeDir + 34; # Nix' StorePath::HashLen + 2 for the separating slash and dash + pathName = substring storeLength (stringLength package - storeLength) package; + in (package.name or pathName) + "_fish-completions") + ( { inherit package; } // + optionalAttrs (package ? meta.priority) { meta.priority = package.meta.priority; }) '' mkdir -p $out if [ -d $package/share/man ]; then diff --git a/nixos/modules/security/sudo-rs.nix b/nixos/modules/security/sudo-rs.nix new file mode 100644 index 00000000000..6b8f09a8d3d --- /dev/null +++ b/nixos/modules/security/sudo-rs.nix @@ -0,0 +1,296 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + inherit (pkgs) sudo sudo-rs; + + cfg = config.security.sudo-rs; + + enableSSHAgentAuth = + with config.security; + pam.enableSSHAgentAuth && pam.sudo.sshAgentAuth; + + usingMillersSudo = cfg.package.pname == sudo.pname; + usingSudoRs = cfg.package.pname == sudo-rs.pname; + + toUserString = user: if (isInt user) then "#${toString user}" else "${user}"; + toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}"; + + toCommandOptionsString = options: + "${concatStringsSep ":" options}${optionalString (length options != 0) ":"} "; + + toCommandsString = commands: + concatStringsSep ", " ( + map (command: + if (isString command) then + command + else + "${toCommandOptionsString command.options}${command.command}" + ) commands + ); + +in + +{ + + ###### interface + + options.security.sudo-rs = { + + defaultOptions = mkOption { + type = with types; listOf str; + default = optional usingMillersSudo "SETENV"; + defaultText = literalMD '' + `[ "SETENV" ]` if using the default `sudo` implementation + ''; + description = mdDoc '' + Options used for the default rules, granting `root` and the + `wheel` group permission to run any command as any user. + ''; + }; + + enable = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Whether to enable the {command}`sudo` command, which + allows non-root users to execute commands as root. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.sudo-rs; + defaultText = literalExpression "pkgs.sudo-rs"; + description = mdDoc '' + Which package to use for `sudo`. + ''; + }; + + wheelNeedsPassword = mkOption { + type = types.bool; + default = true; + description = mdDoc '' + Whether users of the `wheel` group must + provide a password to run commands as super user via {command}`sudo`. + ''; + }; + + execWheelOnly = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Only allow members of the `wheel` group to execute sudo by + setting the executable's permissions accordingly. + This prevents users that are not members of `wheel` from + exploiting vulnerabilities in sudo such as CVE-2021-3156. + ''; + }; + + configFile = mkOption { + type = types.lines; + # Note: if syntax errors are detected in this file, the NixOS + # configuration will fail to build. + description = mdDoc '' + This string contains the contents of the + {file}`sudoers` file. + ''; + }; + + extraRules = mkOption { + description = mdDoc '' + Define specific rules to be in the {file}`sudoers` file. + More specific rules should come after more general ones in order to + yield the expected behavior. You can use mkBefore/mkAfter to ensure + this is the case when configuration options are merged. + ''; + default = []; + example = literalExpression '' + [ + # Allow execution of any command by all users in group sudo, + # requiring a password. + { groups = [ "sudo" ]; commands = [ "ALL" ]; } + + # Allow execution of "/home/root/secret.sh" by user `backup`, `database` + # and the group with GID `1006` without a password. + { users = [ "backup" "database" ]; groups = [ 1006 ]; + commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; } + + # Allow all users of group `bar` to run two executables as user `foo` + # with arguments being pre-set. + { groups = [ "bar" ]; runAs = "foo"; + commands = + [ "/home/baz/cmd1.sh hello-sudo" + { command = '''/home/baz/cmd2.sh ""'''; options = [ "SETENV" ]; } ]; } + ] + ''; + type = with types; listOf (submodule { + options = { + users = mkOption { + type = with types; listOf (either str int); + description = mdDoc '' + The usernames / UIDs this rule should apply for. + ''; + default = []; + }; + + groups = mkOption { + type = with types; listOf (either str int); + description = mdDoc '' + The groups / GIDs this rule should apply for. + ''; + default = []; + }; + + host = mkOption { + type = types.str; + default = "ALL"; + description = mdDoc '' + For what host this rule should apply. + ''; + }; + + runAs = mkOption { + type = with types; str; + default = "ALL:ALL"; + description = mdDoc '' + Under which user/group the specified command is allowed to run. + + A user can be specified using just the username: `"foo"`. + It is also possible to specify a user/group combination using `"foo:bar"` + or to only allow running as a specific group with `":bar"`. + ''; + }; + + commands = mkOption { + description = mdDoc '' + The commands for which the rule should apply. + ''; + type = with types; listOf (either str (submodule { + + options = { + command = mkOption { + type = with types; str; + description = mdDoc '' + A command being either just a path to a binary to allow any arguments, + the full command with arguments pre-set or with `""` used as the argument, + not allowing arguments to the command at all. + ''; + }; + + options = mkOption { + type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]); + description = mdDoc '' + Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/man/1.7.10/sudoers.man.html). + ''; + default = []; + }; + }; + + })); + }; + }; + }); + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = mdDoc '' + Extra configuration text appended to {file}`sudoers`. + ''; + }; + }; + + + ###### implementation + + config = mkIf cfg.enable { + security.sudo-rs.extraRules = + let + defaultRule = { users ? [], groups ? [], opts ? [] }: [ { + inherit users groups; + commands = [ { + command = "ALL"; + options = opts ++ cfg.defaultOptions; + } ]; + } ]; + in mkMerge [ + # This is ordered before users' `mkBefore` rules, + # so as not to introduce unexpected changes. + (mkOrder 400 (defaultRule { users = [ "root" ]; })) + + # This is ordered to show before (most) other rules, but + # late-enough for a user to `mkBefore` it. + (mkOrder 600 (defaultRule { + groups = [ "wheel" ]; + opts = (optional (!cfg.wheelNeedsPassword) "NOPASSWD"); + })) + ]; + + security.sudo-rs.configFile = concatStringsSep "\n" (filter (s: s != "") [ + '' + # Don't edit this file. Set the NixOS options ‘security.sudo-rs.configFile’ + # or ‘security.sudo-rs.extraRules’ instead. + '' + (optionalString enableSSHAgentAuth '' + # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic. + Defaults env_keep+=SSH_AUTH_SOCK + '') + (concatStringsSep "\n" ( + lists.flatten ( + map ( + rule: optionals (length rule.commands != 0) [ + (map (user: "${toUserString user} ${rule.host}=(${rule.runAs}) ${toCommandsString rule.commands}") rule.users) + (map (group: "${toGroupString group} ${rule.host}=(${rule.runAs}) ${toCommandsString rule.commands}") rule.groups) + ] + ) cfg.extraRules + ) + ) + "\n") + (optionalString (cfg.extraConfig != "") '' + # extraConfig + ${cfg.extraConfig} + '') + ]); + + security.wrappers = let + owner = "root"; + group = if cfg.execWheelOnly then "wheel" else "root"; + setuid = true; + permissions = if cfg.execWheelOnly then "u+rx,g+x" else "u+rx,g+x,o+x"; + in { + sudo = { + source = "${cfg.package.out}/bin/sudo"; + inherit owner group setuid permissions; + }; + # sudo-rs does not yet ship a sudoedit (as of v0.2.0) + sudoedit = mkIf usingMillersSudo { + source = "${cfg.package.out}/bin/sudoedit"; + inherit owner group setuid permissions; + }; + }; + + environment.systemPackages = [ sudo ]; + + security.pam.services.sudo = { sshAgentAuth = true; usshAuth = true; }; + security.pam.services.sudo-i = mkIf usingSudoRs + { sshAgentAuth = true; usshAuth = true; }; + + environment.etc.sudoers = + { source = + pkgs.runCommand "sudoers" + { + src = pkgs.writeText "sudoers-in" cfg.configFile; + preferLocalBuild = true; + } + "${pkgs.buildPackages."${cfg.package.pname}"}/bin/visudo -f $src -c && cp $src $out"; + mode = "0440"; + }; + + }; + + meta.maintainers = [ lib.maintainers.nicoo ]; + +} diff --git a/nixos/modules/services/games/xonotic.nix b/nixos/modules/services/games/xonotic.nix new file mode 100644 index 00000000000..c84347ddc98 --- /dev/null +++ b/nixos/modules/services/games/xonotic.nix @@ -0,0 +1,198 @@ +{ config +, pkgs +, lib +, ... +}: + +let + cfg = config.services.xonotic; + + serverCfg = pkgs.writeText "xonotic-server.cfg" ( + toString cfg.prependConfig + + "\n" + + builtins.concatStringsSep "\n" ( + lib.mapAttrsToList (key: option: + let + escape = s: lib.escape [ "\"" ] s; + quote = s: "\"${s}\""; + + toValue = x: quote (escape (toString x)); + + value = (if lib.isList option then + builtins.concatStringsSep + " " + (builtins.map (x: toValue x) option) + else + toValue option + ); + in + "${key} ${value}" + ) cfg.settings + ) + + "\n" + + toString cfg.appendConfig + ); +in + +{ + options.services.xonotic = { + enable = lib.mkEnableOption (lib.mdDoc "Xonotic dedicated server"); + + package = lib.mkPackageOption pkgs "xonotic-dedicated" {}; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = lib.mdDoc '' + Open the firewall for TCP and UDP on the specified port. + ''; + }; + + dataDir = lib.mkOption { + type = lib.types.path; + readOnly = true; + default = "/var/lib/xonotic"; + description = lib.mdDoc '' + Data directory. + ''; + }; + + settings = lib.mkOption { + description = lib.mdDoc '' + Generates the `server.cfg` file. Refer to [upstream's example][0] for + details. + + [0]: https://gitlab.com/xonotic/xonotic/-/blob/master/server/server.cfg + ''; + default = {}; + type = lib.types.submodule { + freeformType = with lib.types; let + scalars = oneOf [ singleLineStr int float ]; + in + attrsOf (oneOf [ scalars (nonEmptyListOf scalars) ]); + + options.sv_public = lib.mkOption { + type = lib.types.int; + default = 0; + example = [ (-1) 1 ]; + description = lib.mdDoc '' + Controls whether the server will be publicly listed. + ''; + }; + + options.hostname = lib.mkOption { + type = lib.types.singleLineStr; + default = "Xonotic $g_xonoticversion Server"; + description = lib.mdDoc '' + The name that will appear in the server list. `$g_xonoticversion` + gets replaced with the current version. + ''; + }; + + options.sv_motd = lib.mkOption { + type = lib.types.singleLineStr; + default = ""; + description = lib.mdDoc '' + Text displayed when players join the server. + ''; + }; + + options.sv_termsofservice_url = lib.mkOption { + type = lib.types.singleLineStr; + default = ""; + description = lib.mdDoc '' + URL for the Terms of Service for playing on your server. + ''; + }; + + options.maxplayers = lib.mkOption { + type = lib.types.int; + default = 16; + description = lib.mdDoc '' + Number of player slots on the server, including spectators. + ''; + }; + + options.net_address = lib.mkOption { + type = lib.types.singleLineStr; + default = "0.0.0.0"; + description = lib.mdDoc '' + The address Xonotic will listen on. + ''; + }; + + options.port = lib.mkOption { + type = lib.types.port; + default = 26000; + description = lib.mdDoc '' + The port Xonotic will listen on. + ''; + }; + }; + }; + + # Still useful even though we're using RFC 42 settings because *some* keys + # can be repeated. + appendConfig = lib.mkOption { + type = with lib.types; nullOr lines; + default = null; + description = lib.mdDoc '' + Literal text to insert at the end of `server.cfg`. + ''; + }; + + # Certain changes need to happen at the beginning of the file. + prependConfig = lib.mkOption { + type = with lib.types; nullOr lines; + default = null; + description = lib.mdDoc '' + Literal text to insert at the start of `server.cfg`. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.xonotic = { + description = "Xonotic server"; + wantedBy = [ "multi-user.target" ]; + + environment = { + # Required or else it tries to write the lock file into the nix store + HOME = cfg.dataDir; + }; + + serviceConfig = { + DynamicUser = true; + User = "xonotic"; + StateDirectory = "xonotic"; + ExecStart = "${cfg.package}/bin/xonotic-dedicated"; + + # Symlink the configuration from the nix store to where Xonotic actually + # looks for it + ExecStartPre = [ + "${pkgs.coreutils}/bin/mkdir -p ${cfg.dataDir}/.xonotic/data" + '' + ${pkgs.coreutils}/bin/ln -sf ${serverCfg} \ + ${cfg.dataDir}/.xonotic/data/server.cfg + '' + ]; + + # Cargo-culted from search results about writing Xonotic systemd units + ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID"; + + Restart = "on-failure"; + RestartSec = 10; + StartLimitBurst = 5; + }; + }; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ + cfg.settings.port + ]; + networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [ + cfg.settings.port + ]; + }; + + meta.maintainers = with lib.maintainers; [ CobaltCause ]; +} diff --git a/nixos/modules/services/matrix/synapse.nix b/nixos/modules/services/matrix/synapse.nix index 554e9ca2ecc..1354a8cb58b 100644 --- a/nixos/modules/services/matrix/synapse.nix +++ b/nixos/modules/services/matrix/synapse.nix @@ -15,26 +15,26 @@ let usePostgresql && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ])); hasWorkers = cfg.workers != { }; + listenerSupportsResource = resource: listener: + lib.any ({ names, ... }: builtins.elem resource names) listener.resources; + + clientListener = findFirst + (listenerSupportsResource "client") + null + (cfg.settings.listeners + ++ concatMap ({ worker_listeners, ... }: worker_listeners) (attrValues cfg.workers)); + registerNewMatrixUser = let - isIpv6 = x: lib.length (lib.splitString ":" x) > 1; - listener = - lib.findFirst ( - listener: lib.any ( - resource: lib.any ( - name: name == "client" - ) resource.names - ) listener.resources - ) (lib.last cfg.settings.listeners) cfg.settings.listeners; - # FIXME: Handle cases with missing client listener properly, - # don't rely on lib.last, this will not work. + isIpv6 = hasInfix ":"; # add a tail, so that without any bind_addresses we still have a useable address - bindAddress = head (listener.bind_addresses ++ [ "127.0.0.1" ]); - listenerProtocol = if listener.tls + bindAddress = head (clientListener.bind_addresses ++ [ "127.0.0.1" ]); + listenerProtocol = if clientListener.tls then "https" else "http"; in + assert assertMsg (clientListener != null) "No client listener found in synapse or one of its workers"; pkgs.writeShellScriptBin "matrix-synapse-register_new_matrix_user" '' exec ${cfg.package}/bin/register_new_matrix_user \ $@ \ @@ -44,7 +44,7 @@ let "[${bindAddress}]" else "${bindAddress}" - }:${builtins.toString listener.port}/" + }:${builtins.toString clientListener.port}/" ''; defaultExtras = [ @@ -938,6 +938,13 @@ in { config = mkIf cfg.enable { assertions = [ { + assertion = clientListener != null; + message = '' + At least one listener which serves the `client` resource via HTTP is required + by synapse in `services.matrix-synapse.settings.listeners` or in one of the workers! + ''; + } + { assertion = hasLocalPostgresDB -> config.services.postgresql.enable; message = '' Cannot deploy matrix-synapse with a configuration for a local postgresql database @@ -969,13 +976,13 @@ in { ( listener: listener.port == main.port - && (lib.any (resource: builtins.elem "replication" resource.names) listener.resources) + && listenerSupportsResource "replication" listener && (lib.any (bind: bind == main.host || bind == "0.0.0.0" || bind == "::") listener.bind_addresses) ) null cfg.settings.listeners; in - hasWorkers -> (listener != null); + hasWorkers -> (cfg.settings.instance_map ? main && listener != null); message = '' Workers for matrix-synapse require setting `services.matrix-synapse.settings.instance_map.main` to any listener configured in `services.matrix-synapse.settings.listeners` with a `"replication"` @@ -1015,7 +1022,7 @@ in { systemd.targets.matrix-synapse = lib.mkIf hasWorkers { description = "Synapse Matrix parent target"; - after = [ "network.target" ] ++ optional hasLocalPostgresDB "postgresql.service"; + after = [ "network-online.target" ] ++ optional hasLocalPostgresDB "postgresql.service"; wantedBy = [ "multi-user.target" ]; }; @@ -1029,7 +1036,7 @@ in { unitConfig.ReloadPropagatedFrom = "matrix-synapse.target"; } else { - after = [ "network.target" ] ++ optional hasLocalPostgresDB "postgresql.service"; + after = [ "network-online.target" ] ++ optional hasLocalPostgresDB "postgresql.service"; wantedBy = [ "multi-user.target" ]; }; baseServiceConfig = { diff --git a/nixos/modules/services/misc/mbpfan.nix b/nixos/modules/services/misc/mbpfan.nix index e75c3525414..8f64fb2d9c5 100644 --- a/nixos/modules/services/misc/mbpfan.nix +++ b/nixos/modules/services/misc/mbpfan.nix @@ -26,7 +26,7 @@ in { aggressive = mkOption { type = types.bool; - default = false; + default = true; description = lib.mdDoc "If true, favors higher default fan speeds."; }; @@ -38,17 +38,20 @@ in { options.general.low_temp = mkOption { type = types.int; - default = 63; + default = (if cfg.aggressive then 55 else 63); + defaultText = literalExpression "55"; description = lib.mdDoc "If temperature is below this, fans will run at minimum speed."; }; options.general.high_temp = mkOption { type = types.int; - default = 66; + default = (if cfg.aggressive then 58 else 66); + defaultText = literalExpression "58"; description = lib.mdDoc "If temperature is above this, fan speed will gradually increase."; }; options.general.max_temp = mkOption { type = types.int; - default = 86; + default = (if cfg.aggressive then 78 else 86); + defaultText = literalExpression "78"; description = lib.mdDoc "If temperature is above this, fans will run at maximum speed."; }; options.general.polling_interval = mkOption { @@ -70,13 +73,6 @@ in { ]; config = mkIf cfg.enable { - services.mbpfan.settings = mkIf cfg.aggressive { - general.min_fan1_speed = mkDefault 2000; - general.low_temp = mkDefault 55; - general.high_temp = mkDefault 58; - general.max_temp = mkDefault 70; - }; - boot.kernelModules = [ "coretemp" "applesmc" ]; environment.systemPackages = [ cfg.package ]; environment.etc."mbpfan.conf".source = settingsFile; @@ -86,6 +82,7 @@ in { wantedBy = [ "sysinit.target" ]; after = [ "syslog.target" "sysinit.target" ]; restartTriggers = [ config.environment.etc."mbpfan.conf".source ]; + serviceConfig = { Type = "simple"; ExecStart = "${cfg.package}/bin/mbpfan -f${verbose}"; diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix index 8bb017894ee..1d06893bf1d 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -68,6 +68,7 @@ let "redis" "rspamd" "rtl_433" + "sabnzbd" "scaphandre" "script" "shelly" @@ -304,6 +305,14 @@ in 'services.mysql.enable' is set to false. ''; } { + assertion = cfg.nextcloud.enable -> ( + (cfg.nextcloud.passwordFile == null) != (cfg.nextcloud.tokenFile == null) + ); + message = '' + Please specify either 'services.prometheus.exporters.nextcloud.passwordFile' or + 'services.prometheus.exporters.nextcloud.tokenFile' + ''; + } { assertion = cfg.sql.enable -> ( (cfg.sql.configFile == null) != (cfg.sql.configuration == null) ); diff --git a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix index 66eaed51d2e..407bff1d62d 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix @@ -21,7 +21,7 @@ let throw "${logPrefix}: configuration file must not reside within /tmp - it won't be visible to the systemd service." else - true; + file; checkConfig = file: pkgs.runCommand "checked-blackbox-exporter.conf" { preferLocalBuild = true; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix b/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix index 28add020f5c..28a3eb6a134 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix @@ -23,10 +23,12 @@ in description = lib.mdDoc '' Username for connecting to Nextcloud. Note that this account needs to have admin privileges in Nextcloud. + Unused when using token authentication. ''; }; passwordFile = mkOption { - type = types.path; + type = types.nullOr types.path; + default = null; example = "/path/to/password-file"; description = lib.mdDoc '' File containing the password for connecting to Nextcloud. @@ -34,9 +36,9 @@ in ''; }; tokenFile = mkOption { - type = types.path; + type = types.nullOr types.path; + default = null; example = "/path/to/token-file"; - default = ""; description = lib.mdDoc '' File containing the token for connecting to Nextcloud. Make sure that this file is readable by the exporter user. @@ -58,12 +60,13 @@ in --addr ${cfg.listenAddress}:${toString cfg.port} \ --timeout ${cfg.timeout} \ --server ${cfg.url} \ - ${if cfg.tokenFile == "" then '' + ${if cfg.passwordFile != null then '' --username ${cfg.username} \ --password ${escapeShellArg "@${cfg.passwordFile}"} \ - '' else '' + '' else '' --auth-token ${escapeShellArg "@${cfg.tokenFile}"} \ - ''} ${concatStringsSep " \\\n " cfg.extraFlags}''; + ''} \ + ${concatStringsSep " \\\n " cfg.extraFlags}''; }; }; } diff --git a/nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix b/nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix new file mode 100644 index 00000000000..41127749401 --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix @@ -0,0 +1,47 @@ +{ config, lib, pkgs, options }: + +let + inherit (lib) mkOption types; + cfg = config.services.prometheus.exporters.sabnzbd; +in +{ + port = 9387; + + extraOpts = { + servers = mkOption { + description = "List of sabnzbd servers to connect to."; + type = types.listOf (types.submodule { + options = { + baseUrl = mkOption { + type = types.str; + description = "Base URL of the sabnzbd server."; + example = "http://localhost:8080/sabnzbd"; + }; + apiKeyFile = mkOption { + type = types.str; + description = "File containing the API key."; + example = "/run/secrets/sabnzbd_apikey"; + }; + }; + }); + }; + }; + + serviceOpts = + let + servers = lib.zipAttrs cfg.servers; + apiKeys = lib.concatStringsSep "," (builtins.map (file: "$(cat ${file})") servers.apiKeyFile); + in + { + environment = { + METRICS_PORT = toString cfg.port; + METRICS_ADDR = cfg.listenAddress; + SABNZBD_BASEURLS = lib.concatStringsSep "," servers.baseUrl; + }; + + script = '' + export SABNZBD_APIKEYS="${apiKeys}" + exec ${lib.getExe pkgs.prometheus-sabnzbd-exporter} + ''; + }; +} diff --git a/nixos/modules/services/networking/frp.nix b/nixos/modules/services/networking/frp.nix index 09d2b773630..e4f9a220b5e 100644 --- a/nixos/modules/services/networking/frp.nix +++ b/nixos/modules/services/networking/frp.nix @@ -31,8 +31,8 @@ in default = { }; description = mdDoc '' Frp configuration, for configuration options - see the example of [client](https://github.com/fatedier/frp/blob/dev/conf/frpc_full.ini) - or [server](https://github.com/fatedier/frp/blob/dev/conf/frps_full.ini) on github. + see the example of [client](https://github.com/fatedier/frp/blob/dev/conf/frpc_legacy_full.ini) + or [server](https://github.com/fatedier/frp/blob/dev/conf/frps_legacy_full.ini) on github. ''; example = literalExpression '' { diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix index e97195d8291..d98c0ce25bf 100644 --- a/nixos/modules/services/networking/knot.nix +++ b/nixos/modules/services/networking/knot.nix @@ -5,10 +5,110 @@ with lib; let cfg = config.services.knot; - configFile = pkgs.writeTextFile { + yamlConfig = let + result = assert secsCheck; nix2yaml cfg.settings; + + secAllow = n: hasPrefix "mod-" n || elem n [ + "module" + "server" "xdp" "control" + "log" + "statistics" "database" + "keystore" "key" "remote" "remotes" "acl" "submission" "policy" + "template" + "zone" + "include" + ]; + secsCheck = let + secsBad = filter (n: !secAllow n) (attrNames cfg.settings); + in if secsBad == [] then true else throw + ("services.knot.settings contains unknown sections: " + toString secsBad); + + nix2yaml = nix_def: concatStrings ( + # We output the config section in the upstream-mandated order. + # Ordering is important due to forward-references not being allowed. + # See definition of conf_export and 'const yp_item_t conf_schema' + # upstream for reference. Last updated for 3.3. + # When changing the set of sections, also update secAllow above. + [ (sec_list_fa "id" nix_def "module") ] + ++ map (sec_plain nix_def) + [ "server" "xdp" "control" ] + ++ [ (sec_list_fa "target" nix_def "log") ] + ++ map (sec_plain nix_def) + [ "statistics" "database" ] + ++ map (sec_list_fa "id" nix_def) + [ "keystore" "key" "remote" "remotes" "acl" "submission" "policy" ] + + # Export module sections before the template section. + ++ map (sec_list_fa "id" nix_def) (filter (hasPrefix "mod-") (attrNames nix_def)) + + ++ [ (sec_list_fa "id" nix_def "template") ] + ++ [ (sec_list_fa "domain" nix_def "zone") ] + ++ [ (sec_plain nix_def "include") ] + ); + + # A plain section contains directly attributes (we don't really check that ATM). + sec_plain = nix_def: sec_name: if !hasAttr sec_name nix_def then "" else + n2y "" { ${sec_name} = nix_def.${sec_name}; }; + + # This section contains a list of attribute sets. In each of the sets + # there's an attribute (`fa_name`, typically "id") that must exist and come first. + # Alternatively we support using attribute sets instead of lists; example diff: + # -template = [ { id = "default"; /* other attributes */ } { id = "foo"; } ] + # +template = { default = { /* those attributes */ }; foo = { }; } + sec_list_fa = fa_name: nix_def: sec_name: if !hasAttr sec_name nix_def then "" else + let + elem2yaml = fa_val: other_attrs: + " - " + n2y "" { ${fa_name} = fa_val; } + + " " + n2y " " other_attrs + + "\n"; + sec = nix_def.${sec_name}; + in + sec_name + ":\n" + + (if isList sec + then flip concatMapStrings sec + (elem: elem2yaml elem.${fa_name} (removeAttrs elem [ fa_name ])) + else concatStrings (mapAttrsToList elem2yaml sec) + ); + + # This convertor doesn't care about ordering of attributes. + # TODO: it could probably be simplified even more, now that it's not + # to be used directly, but we might want some other tweaks, too. + n2y = indent: val: + if doRecurse val then concatStringsSep "\n${indent}" + (mapAttrsToList + # This is a bit wacky - set directly under a set would start on bad indent, + # so we start those on a new line, but not other types of attribute values. + (aname: aval: "${aname}:${if doRecurse aval then "\n${indent} " else " "}" + + n2y (indent + " ") aval) + val + ) + + "\n" + else + /* + if isList val && stringLength indent < 4 then concatMapStrings + (elem: "\n${indent}- " + n2y (indent + " ") elem) + val + else + */ + if isList val /* and long indent */ then + "[ " + concatMapStringsSep ", " quoteString val + " ]" else + if isBool val then (if val then "on" else "off") else + quoteString val; + + # We don't want paths like ./my-zone.txt be converted to plain strings. + quoteString = s: ''"${if builtins.typeOf s == "path" then s else toString s}"''; + # We don't want to walk the insides of derivation attributes. + doRecurse = val: isAttrs val && !isDerivation val; + + in result; + + configFile = if cfg.settingsFile != null then + assert cfg.settings == {} && cfg.keyFiles == []; + cfg.settingsFile + else pkgs.writeTextFile { name = "knot.conf"; - text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" + - cfg.extraConfig; + text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" + yamlConfig; + # TODO: maybe we could do some checks even when private keys complicate this? checkPhase = lib.optionalString (cfg.keyFiles == []) '' ${cfg.package}/bin/knotc --config=$out conf-check ''; @@ -60,11 +160,21 @@ in { ''; }; - extraConfig = mkOption { - type = types.lines; - default = ""; + settings = mkOption { + type = types.attrs; + default = {}; description = lib.mdDoc '' - Extra lines to be added verbatim to knot.conf + Extra configuration as nix values. + ''; + }; + + settingsFile = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc '' + As alternative to ``settings``, you can provide whole configuration + directly in the almost-YAML format of Knot DNS. + You might want to utilize ``writeTextFile`` for this. ''; }; @@ -78,6 +188,12 @@ in { }; }; }; + imports = [ + # Compatibility with NixOS 23.05. At least partial, as it fails assert if used with keyFiles. + (mkChangedOptionModule [ "services" "knot" "extraConfig" ] [ "services" "knot" "settingsFile" ] + (config: pkgs.writeText "knot.conf" config.services.knot.extraConfig) + ) + ]; config = mkIf config.services.knot.enable { users.groups.knot = {}; @@ -87,6 +203,8 @@ in { description = "Knot daemon user"; }; + environment.etc."knot/knot.conf".source = configFile; # just for user's convenience + systemd.services.knot = { unitConfig.Documentation = "man:knotd(8) man:knot.conf(5) man:knotc(8) https://www.knot-dns.cz/docs/${cfg.package.version}/html/"; description = cfg.package.meta.description; diff --git a/nixos/modules/services/networking/mtr-exporter.nix b/nixos/modules/services/networking/mtr-exporter.nix index 43ebbbe96d0..af694c3e736 100644 --- a/nixos/modules/services/networking/mtr-exporter.nix +++ b/nixos/modules/services/networking/mtr-exporter.nix @@ -2,63 +2,114 @@ let inherit (lib) - maintainers types mkEnableOption mkOption mkIf - literalExpression escapeShellArg escapeShellArgs; + maintainers types literalExpression + escapeShellArg escapeShellArgs + mkEnableOption mkOption mkRemovedOptionModule mkIf mdDoc + optionalString concatMapStrings concatStringsSep; + cfg = config.services.mtr-exporter; + + jobsConfig = pkgs.writeText "mtr-exporter.conf" (concatMapStrings (job: '' + ${job.name} -- ${job.schedule} -- ${concatStringsSep " " job.flags} ${job.address} + '') cfg.jobs); in { + imports = [ + (mkRemovedOptionModule [ "services" "mtr-exporter" "target" ] "Use services.mtr-exporter.jobs instead.") + (mkRemovedOptionModule [ "services" "mtr-exporter" "mtrFlags" ] "Use services.mtr-exporter.jobs.<job>.flags instead.") + ]; + options = { services = { mtr-exporter = { - enable = mkEnableOption (lib.mdDoc "a Prometheus exporter for MTR"); + enable = mkEnableOption (mdDoc "a Prometheus exporter for MTR"); - target = mkOption { + address = mkOption { type = types.str; - example = "example.org"; - description = lib.mdDoc "Target to check using MTR."; - }; - - interval = mkOption { - type = types.int; - default = 60; - description = lib.mdDoc "Interval between MTR checks in seconds."; + default = "127.0.0.1"; + description = lib.mdDoc "Listen address for MTR exporter."; }; port = mkOption { type = types.port; default = 8080; - description = lib.mdDoc "Listen port for MTR exporter."; + description = mdDoc "Listen port for MTR exporter."; }; - address = mkOption { - type = types.str; - default = "127.0.0.1"; - description = lib.mdDoc "Listen address for MTR exporter."; + extraFlags = mkOption { + type = types.listOf types.str; + default = []; + example = ["-flag.deprecatedMetrics"]; + description = mdDoc '' + Extra command line options to pass to MTR exporter. + ''; }; - mtrFlags = mkOption { - type = with types; listOf str; - default = []; - example = ["-G1"]; - description = lib.mdDoc "Additional flags to pass to MTR."; + package = mkOption { + type = types.package; + default = pkgs.mtr-exporter; + defaultText = literalExpression "pkgs.mtr-exporter"; + description = mdDoc "The MTR exporter package to use."; + }; + + mtrPackage = mkOption { + type = types.package; + default = pkgs.mtr; + defaultText = literalExpression "pkgs.mtr"; + description = mdDoc "The MTR package to use."; + }; + + jobs = mkOption { + description = mdDoc "List of MTR jobs. Will be added to /etc/mtr-exporter.conf"; + type = types.nonEmptyListOf (types.submodule { + options = { + name = mkOption { + type = types.str; + description = mdDoc "Name of ICMP pinging job."; + }; + + address = mkOption { + type = types.str; + example = "host.example.org:1234"; + description = mdDoc "Target address for MTR client."; + }; + + schedule = mkOption { + type = types.str; + default = "@every 60s"; + example = "@hourly"; + description = mdDoc "Schedule of MTR checks. Also accepts Cron format."; + }; + + flags = mkOption { + type = with types; listOf str; + default = []; + example = ["-G1"]; + description = mdDoc "Additional flags to pass to MTR."; + }; + }; + }); }; }; }; }; config = mkIf cfg.enable { + environment.etc."mtr-exporter.conf" = { + source = jobsConfig; + }; + systemd.services.mtr-exporter = { - script = '' - exec ${pkgs.mtr-exporter}/bin/mtr-exporter \ - -mtr ${pkgs.mtr}/bin/mtr \ - -schedule '@every ${toString cfg.interval}s' \ - -bind ${escapeShellArg cfg.address}:${toString cfg.port} \ - -- \ - ${escapeShellArgs (cfg.mtrFlags ++ [ cfg.target ])} - ''; wantedBy = [ "multi-user.target" ]; requires = [ "network.target" ]; after = [ "network.target" ]; serviceConfig = { + ExecStart = '' + ${cfg.package}/bin/mtr-exporter \ + -mtr '${cfg.mtrPackage}/bin/mtr' \ + -bind ${escapeShellArg "${cfg.address}:${toString cfg.port}"} \ + -jobs '${jobsConfig}' \ + ${escapeShellArgs cfg.extraFlags} + ''; Restart = "on-failure"; # Hardening CapabilityBoundingSet = [ "" ]; diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix index 6bc46a9a90e..53c847ee3ca 100644 --- a/nixos/modules/services/networking/networkmanager.nix +++ b/nixos/modules/services/networking/networkmanager.nix @@ -30,13 +30,11 @@ let configFile = pkgs.writeText "NetworkManager.conf" (lib.concatStringsSep "\n" [ (mkSection "main" { plugins = "keyfile"; - dhcp = cfg.dhcp; - dns = cfg.dns; + inherit (cfg) dhcp dns; # If resolvconf is disabled that means that resolv.conf is managed by some other module. rc-manager = if config.networking.resolvconf.enable then "resolvconf" else "unmanaged"; - firewall-backend = cfg.firewallBackend; }) (mkSection "keyfile" { unmanaged-devices = @@ -233,15 +231,6 @@ in ''; }; - firewallBackend = mkOption { - type = types.enum [ "iptables" "nftables" "none" ]; - default = "iptables"; - description = lib.mdDoc '' - Which firewall backend should be used for configuring masquerading with shared mode. - If set to none, NetworkManager doesn't manage the configuration at all. - ''; - }; - logLevel = mkOption { type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ]; default = "WARN"; @@ -340,20 +329,20 @@ in default = [ ]; example = literalExpression '' [ { - source = pkgs.writeText "upHook" ''' - - if [ "$2" != "up" ]; then - logger "exit: event $2 != up" - exit - fi - - # coreutils and iproute are in PATH too - logger "Device $DEVICE_IFACE coming up" - '''; - type = "basic"; - } ]''; + source = pkgs.writeText "upHook" ''' + if [ "$2" != "up" ]; then + logger "exit: event $2 != up" + exit + fi + + # coreutils and iproute are in PATH too + logger "Device $DEVICE_IFACE coming up" + '''; + type = "basic"; + } ] + ''; description = lib.mdDoc '' - A list of scripts which will be executed in response to network events. + A list of scripts which will be executed in response to network events. ''; }; @@ -413,6 +402,9 @@ in them via the DNS server in your network, or use environment.etc to add a file into /etc/NetworkManager/dnsmasq.d reconfiguring hostsdir. '') + (mkRemovedOptionModule [ "networking" "networkmanager" "firewallBackend" ] '' + This option was removed as NixOS is now using iptables-nftables-compat even when using iptables, therefore Networkmanager now uses the nftables backend unconditionally. + '') ]; diff --git a/nixos/modules/services/networking/nftables.nix b/nixos/modules/services/networking/nftables.nix index 47159ade328..a0afdb45275 100644 --- a/nixos/modules/services/networking/nftables.nix +++ b/nixos/modules/services/networking/nftables.nix @@ -248,7 +248,6 @@ in config = mkIf cfg.enable { boot.blacklistedKernelModules = [ "ip_tables" ]; environment.systemPackages = [ pkgs.nftables ]; - networking.networkmanager.firewallBackend = mkDefault "nftables"; # versionOlder for backportability, remove afterwards networking.nftables.flushRuleset = mkDefault (versionOlder config.system.stateVersion "23.11" || (cfg.rulesetFile != null || cfg.ruleset != "")); systemd.services.nftables = { diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix index 702423ef09c..bf2f5230c73 100644 --- a/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixos/modules/services/networking/ssh/sshd.nix @@ -27,13 +27,11 @@ let mkValueString = mkValueStringSshd; } " ";}); - configFile = settingsFormat.generate "config" cfg.settings; - sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ validationPackage ]; } '' + configFile = settingsFormat.generate "sshd.conf-settings" cfg.settings; + sshconf = pkgs.runCommand "sshd.conf-final" { } '' cat ${configFile} - >$out <<EOL ${cfg.extraConfig} EOL - - sshd -G -f $out ''; cfg = config.services.openssh; @@ -576,6 +574,21 @@ in '')} ''; + system.checks = [ + (pkgs.runCommand "check-sshd-config" + { + nativeBuildInputs = [ validationPackage ]; + } '' + ${concatMapStringsSep "\n" + (lport: "sshd -G -T -C lport=${toString lport} -f ${sshconf} > /dev/null") + cfg.ports} + ${concatMapStringsSep "\n" + (la: "sshd -G -T -C laddr=${la.addr},lport=${toString la.port} -f ${sshconf} > /dev/null") + cfg.listenAddresses} + touch $out + '') + ]; + assertions = [{ assertion = if cfg.settings.X11Forwarding then cfgc.setXAuthLocation else true; message = "cannot enable X11 forwarding without setting xauth location";} (let diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix index 34210580f53..68e0e06d046 100644 --- a/nixos/modules/services/networking/wg-quick.nix +++ b/nixos/modules/services/networking/wg-quick.nix @@ -17,6 +17,8 @@ let type = with types; nullOr str; description = lib.mdDoc '' wg-quick .conf file, describing the interface. + Using this option can be a useful means of configuring WireGuard if + one has an existing .conf file. This overrides any other configuration interface configuration options. See wg-quick manpage for more details. ''; diff --git a/nixos/modules/services/search/typesense.nix b/nixos/modules/services/search/typesense.nix index 856c3cad22d..c158d04fea2 100644 --- a/nixos/modules/services/search/typesense.nix +++ b/nixos/modules/services/search/typesense.nix @@ -83,12 +83,12 @@ in { Group = "typesense"; StateDirectory = "typesense"; - StateDirectoryMode = "0700"; + StateDirectoryMode = "0750"; # Hardening CapabilityBoundingSet = ""; LockPersonality = true; - MemoryDenyWriteExecute = true; + # MemoryDenyWriteExecute = true; needed since 0.25.1 NoNewPrivileges = true; PrivateUsers = true; PrivateTmp = true; diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix index d22e6b5b40c..0517615a4c6 100644 --- a/nixos/modules/services/security/vaultwarden/default.nix +++ b/nixos/modules/services/security/vaultwarden/default.nix @@ -60,10 +60,8 @@ in { config = mkOption { type = attrsOf (nullOr (oneOf [ bool int str ])); default = { - config = { - ROCKET_ADDRESS = "::1"; # default to localhost - ROCKET_PORT = 8222; - }; + ROCKET_ADDRESS = "::1"; # default to localhost + ROCKET_PORT = 8222; }; example = literalExpression '' { diff --git a/nixos/modules/services/web-apps/calibre-web.nix b/nixos/modules/services/web-apps/calibre-web.nix index 143decfc091..80567db10c9 100644 --- a/nixos/modules/services/web-apps/calibre-web.nix +++ b/nixos/modules/services/web-apps/calibre-web.nix @@ -10,6 +10,8 @@ in services.calibre-web = { enable = mkEnableOption (lib.mdDoc "Calibre-Web"); + package = lib.mkPackageOption pkgs "calibre-web" { }; + listen = { ip = mkOption { type = types.str; @@ -73,6 +75,8 @@ in ''; }; + enableKepubify = mkEnableOption (lib.mdDoc "kebup conversion support"); + enableBookUploading = mkOption { type = types.bool; default = false; @@ -106,7 +110,7 @@ in systemd.services.calibre-web = let appDb = "/var/lib/${cfg.dataDir}/app.db"; gdriveDb = "/var/lib/${cfg.dataDir}/gdrive.db"; - calibreWebCmd = "${pkgs.calibre-web}/bin/calibre-web -p ${appDb} -g ${gdriveDb}"; + calibreWebCmd = "${cfg.package}/bin/calibre-web -p ${appDb} -g ${gdriveDb}"; settings = concatStringsSep ", " ( [ @@ -117,6 +121,7 @@ in ] ++ optional (cfg.options.calibreLibrary != null) "config_calibre_dir = '${cfg.options.calibreLibrary}'" ++ optional cfg.options.enableBookConversion "config_converterpath = '${pkgs.calibre}/bin/ebook-convert'" + ++ optional cfg.options.enableKepubify "config_kepubifypath = '${pkgs.kepubify}/bin/kepubify'" ); in { diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix index 4b308d2ee56..e2d5cdc4f7c 100644 --- a/nixos/modules/services/web-apps/plausible.nix +++ b/nixos/modules/services/web-apps/plausible.nix @@ -248,11 +248,10 @@ in { # setup ${cfg.package}/createdb.sh ${cfg.package}/migrate.sh + export IP_GEOLOCATION_DB=${pkgs.dbip-country-lite}/share/dbip/dbip-country-lite.mmdb ${cfg.package}/bin/plausible eval "(Plausible.Release.prepare() ; Plausible.Auth.create_user(\"$ADMIN_USER_NAME\", \"$ADMIN_USER_EMAIL\", \"$ADMIN_USER_PWD\"))" ${optionalString cfg.adminUser.activate '' - if ! ${cfg.package}/init-admin.sh | grep 'already exists'; then - psql -d plausible <<< "UPDATE users SET email_verified=true;" - fi + psql -d plausible <<< "UPDATE users SET email_verified=true where email = '$ADMIN_USER_EMAIL';" ''} exec plausible start diff --git a/nixos/modules/services/web-apps/vikunja.nix b/nixos/modules/services/web-apps/vikunja.nix index 8bc8e8c2925..6b1d4da532b 100644 --- a/nixos/modules/services/web-apps/vikunja.nix +++ b/nixos/modules/services/web-apps/vikunja.nix @@ -147,5 +147,9 @@ in { }; environment.etc."vikunja/config.yaml".source = configFile; + + environment.systemPackages = [ + cfg.package-api # for admin `vikunja` CLI + ]; }; } diff --git a/nixos/modules/services/web-servers/garage.nix b/nixos/modules/services/web-servers/garage.nix index 8b5734b5a2c..80fb24fe2c5 100644 --- a/nixos/modules/services/web-servers/garage.nix +++ b/nixos/modules/services/web-servers/garage.nix @@ -23,6 +23,12 @@ in example = { RUST_BACKTRACE="yes"; }; }; + environmentFile = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc "File containing environment variables to be passed to the Garage server."; + default = null; + }; + logLevel = mkOption { type = types.enum (["info" "debug" "trace"]); default = "info"; @@ -80,7 +86,7 @@ in after = [ "network.target" "network-online.target" ]; wants = [ "network.target" "network-online.target" ]; wantedBy = [ "multi-user.target" ]; - restartTriggers = [ configFile ]; + restartTriggers = [ configFile ] ++ (lib.optional (cfg.environmentFile != null) cfg.environmentFile); serviceConfig = { ExecStart = "${cfg.package}/bin/garage server"; @@ -88,6 +94,7 @@ in DynamicUser = lib.mkDefault true; ProtectHome = true; NoNewPrivileges = true; + EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile; }; environment = { RUST_LOG = lib.mkDefault "garage=${cfg.logLevel}"; diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix index 15a510fd8f9..282a34f6b01 100644 --- a/nixos/modules/services/x11/desktop-managers/plasma5.nix +++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix @@ -172,24 +172,19 @@ in (mkIf (cfg.enable || cfg.mobile.enable || cfg.bigscreen.enable) { security.wrappers = { - kscreenlocker_greet = { - setuid = true; + kwin_wayland = { owner = "root"; group = "root"; - source = "${getBin libsForQt5.kscreenlocker}/libexec/kscreenlocker_greet"; + capabilities = "cap_sys_nice+ep"; + source = "${getBin plasma5.kwin}/bin/kwin_wayland"; }; + } // mkIf (!cfg.runUsingSystemd) { start_kdeinit = { setuid = true; owner = "root"; group = "root"; source = "${getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit"; }; - kwin_wayland = { - owner = "root"; - group = "root"; - capabilities = "cap_sys_nice+ep"; - source = "${getBin plasma5.kwin}/bin/kwin_wayland"; - }; }; environment.systemPackages = diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix index e6923bcbb56..400e5601dc5 100644 --- a/nixos/modules/services/x11/display-managers/gdm.nix +++ b/nixos/modules/services/x11/display-managers/gdm.nix @@ -97,6 +97,19 @@ in type = types.bool; }; + banner = mkOption { + type = types.nullOr types.lines; + default = null; + example = '' + foo + bar + baz + ''; + description = lib.mdDoc '' + Optional message to display on the login screen. + ''; + }; + settings = mkOption { type = settingsFormat.type; default = { }; @@ -238,6 +251,11 @@ in sleep-inactive-ac-timeout = lib.gvariant.mkInt32 0; sleep-inactive-battery-timeout = lib.gvariant.mkInt32 0; }; + }] ++ lib.optionals (cfg.gdm.banner != null) [{ + settings."org/gnome/login-screen" = { + banner-message-enable = true; + banner-message-text = cfg.gdm.banner; + }; }] ++ [ "${gdm}/share/gdm/greeter-dconf-defaults" ]; # Use AutomaticLogin if delay is zero, because it's immediate. diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 8bd450d7343..e05f89bb0fb 100755 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -74,7 +74,7 @@ if ("@localeArchive@" ne "") { if (!defined($action) || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) { print STDERR <<"EOF"; -Usage: $0 [switch|boot|test] +Usage: $0 [switch|boot|test|dry-activate] switch: make the configuration the boot default and activate now boot: make the configuration the boot default @@ -661,10 +661,20 @@ foreach my $mount_point (keys(%{$cur_fss})) { # Filesystem entry disappeared, so unmount it. $units_to_stop{$unit} = 1; } elsif ($cur->{fsType} ne $new->{fsType} || $cur->{device} ne $new->{device}) { - # Filesystem type or device changed, so unmount and mount it. - $units_to_stop{$unit} = 1; - $units_to_start{$unit} = 1; - record_unit($start_list_file, $unit); + if ($mount_point eq '/' or $mount_point eq '/nix') { + if ($cur->{options} ne $new->{options}) { + # Mount options changed, so remount it. + $units_to_reload{$unit} = 1; + record_unit($reload_list_file, $unit); + } else { + # Don't unmount / or /nix if the device changed + $units_to_skip{$unit} = 1; + } + } else { + # Filesystem type or device changed, so unmount and mount it. + $units_to_restart{$unit} = 1; + record_unit($restart_list_file, $unit); + } } elsif ($cur->{options} ne $new->{options}) { # Mount options changes, so remount it. $units_to_reload{$unit} = 1; diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix index dfa883a2c33..679567cbb73 100644 --- a/nixos/modules/tasks/network-interfaces-systemd.nix +++ b/nixos/modules/tasks/network-interfaces-systemd.nix @@ -173,6 +173,33 @@ let }]; })); + bridgeNetworks = mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: { + netdevs."40-${name}" = { + netdevConfig = { + Name = name; + Kind = "bridge"; + }; + }; + networks = listToAttrs (forEach bridge.interfaces (bi: + nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) { + DHCP = mkOverride 0 (dhcpStr false); + networkConfig.Bridge = name; + } ]))); + })); + + vlanNetworks = mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: { + netdevs."40-${name}" = { + netdevConfig = { + Name = name; + Kind = "vlan"; + }; + vlanConfig.Id = vlan.id; + }; + networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) { + vlan = [ name ]; + } ]); + })); + in { @@ -182,7 +209,15 @@ in # Note this is if initrd.network.enable, not if # initrd.systemd.network.enable. By setting the latter and not the # former, the user retains full control over the configuration. - boot.initrd.systemd.network = mkMerge [(genericDhcpNetworks true) interfaceNetworks]; + boot.initrd.systemd.network = mkMerge [ + (genericDhcpNetworks true) + interfaceNetworks + bridgeNetworks + vlanNetworks + ]; + boot.initrd.availableKernelModules = + optional (cfg.bridges != {}) "bridge" ++ + optional (cfg.vlans != {}) "8021q"; }) (mkIf cfg.useNetworkd { @@ -212,19 +247,7 @@ in } (genericDhcpNetworks false) interfaceNetworks - (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: { - netdevs."40-${name}" = { - netdevConfig = { - Name = name; - Kind = "bridge"; - }; - }; - networks = listToAttrs (forEach bridge.interfaces (bi: - nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) { - DHCP = mkOverride 0 (dhcpStr false); - networkConfig.Bridge = name; - } ]))); - }))) + bridgeNetworks (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: { netdevs."40-${name}" = { netdevConfig = { @@ -377,18 +400,7 @@ in } ]); }; }))) - (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: { - netdevs."40-${name}" = { - netdevConfig = { - Name = name; - Kind = "vlan"; - }; - vlanConfig.Id = vlan.id; - }; - networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) { - vlan = [ name ]; - } ]); - }))) + vlanNetworks ]; # We need to prefill the slaved devices with networking options diff --git a/nixos/modules/virtualisation/google-compute-config.nix b/nixos/modules/virtualisation/google-compute-config.nix index cf94ce0faf3..3c503f027d7 100644 --- a/nixos/modules/virtualisation/google-compute-config.nix +++ b/nixos/modules/virtualisation/google-compute-config.nix @@ -39,7 +39,7 @@ in # Allow root logins only using SSH keys # and disable password authentication in general services.openssh.enable = true; - services.openssh.settings.PermitRootLogin = "prohibit-password"; + services.openssh.settings.PermitRootLogin = mkDefault "prohibit-password"; services.openssh.settings.PasswordAuthentication = mkDefault false; # enable OS Login. This also requires setting enable-oslogin=TRUE metadata on diff --git a/nixos/modules/virtualisation/oci-common.nix b/nixos/modules/virtualisation/oci-common.nix new file mode 100644 index 00000000000..ac9405e3ecf --- /dev/null +++ b/nixos/modules/virtualisation/oci-common.nix @@ -0,0 +1,60 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.oci; +in +{ + imports = [ ../profiles/qemu-guest.nix ]; + + # Taken from /proc/cmdline of Ubuntu 20.04.2 LTS on OCI + boot.kernelParams = [ + "nvme.shutdown_timeout=10" + "nvme_core.shutdown_timeout=10" + "libiscsi.debug_libiscsi_eh=1" + "crash_kexec_post_notifiers" + + # VNC console + "console=tty1" + + # x86_64-linux + "console=ttyS0" + + # aarch64-linux + "console=ttyAMA0,115200" + ]; + + boot.growPartition = true; + + fileSystems."/" = { + device = "/dev/disk/by-label/nixos"; + fsType = "ext4"; + autoResize = true; + }; + + fileSystems."/boot" = lib.mkIf cfg.efi { + device = "/dev/disk/by-label/ESP"; + fsType = "vfat"; + }; + + boot.loader.efi.canTouchEfiVariables = false; + boot.loader.grub = { + device = if cfg.efi then "nodev" else "/dev/sda"; + splashImage = null; + extraConfig = '' + serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1 + terminal_input --append serial + terminal_output --append serial + ''; + efiInstallAsRemovable = cfg.efi; + efiSupport = cfg.efi; + }; + + # https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/configuringntpservice.htm#Configuring_the_Oracle_Cloud_Infrastructure_NTP_Service_for_an_Instance + networking.timeServers = [ "169.254.169.254" ]; + + services.openssh.enable = true; + + # Otherwise the instance may not have a working network-online.target, + # making the fetch-ssh-keys.service fail + networking.useNetworkd = true; +} diff --git a/nixos/modules/virtualisation/oci-config-user.nix b/nixos/modules/virtualisation/oci-config-user.nix new file mode 100644 index 00000000000..70c0b34efe7 --- /dev/null +++ b/nixos/modules/virtualisation/oci-config-user.nix @@ -0,0 +1,12 @@ +{ modulesPath, ... }: + +{ + # To build the configuration or use nix-env, you need to run + # either nixos-rebuild --upgrade or nix-channel --update + # to fetch the nixos channel. + + # This configures everything but bootstrap services, + # which only need to be run once and have already finished + # if you are able to see this comment. + imports = [ "${modulesPath}/virtualisation/oci-common.nix" ]; +} diff --git a/nixos/modules/virtualisation/oci-image.nix b/nixos/modules/virtualisation/oci-image.nix new file mode 100644 index 00000000000..d4af5016dd7 --- /dev/null +++ b/nixos/modules/virtualisation/oci-image.nix @@ -0,0 +1,50 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.oci; +in +{ + imports = [ ./oci-common.nix ]; + + config = { + system.build.OCIImage = import ../../lib/make-disk-image.nix { + inherit config lib pkgs; + name = "oci-image"; + configFile = ./oci-config-user.nix; + format = "qcow2"; + diskSize = 8192; + partitionTableType = if cfg.efi then "efi" else "legacy"; + }; + + systemd.services.fetch-ssh-keys = { + description = "Fetch authorized_keys for root user"; + + wantedBy = [ "sshd.service" ]; + before = [ "sshd.service" ]; + + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + + path = [ pkgs.coreutils pkgs.curl ]; + script = '' + mkdir -m 0700 -p /root/.ssh + if [ -f /root/.ssh/authorized_keys ]; then + echo "Authorized keys have already been downloaded" + else + echo "Downloading authorized keys from Instance Metadata Service v2" + curl -s -S -L \ + -H "Authorization: Bearer Oracle" \ + -o /root/.ssh/authorized_keys \ + http://169.254.169.254/opc/v2/instance/metadata/ssh_authorized_keys + chmod 600 /root/.ssh/authorized_keys + fi + ''; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + StandardError = "journal+console"; + StandardOutput = "journal+console"; + }; + }; + }; +} diff --git a/nixos/modules/virtualisation/oci-options.nix b/nixos/modules/virtualisation/oci-options.nix new file mode 100644 index 00000000000..0dfedc6a530 --- /dev/null +++ b/nixos/modules/virtualisation/oci-options.nix @@ -0,0 +1,14 @@ +{ config, lib, pkgs, ... }: +{ + options = { + oci = { + efi = lib.mkOption { + default = true; + internal = true; + description = '' + Whether the OCI instance is using EFI. + ''; + }; + }; + }; +} |