diff options
author | Jan Tojnar <jtojnar@gmail.com> | 2023-08-16 19:37:11 +0200 |
---|---|---|
committer | Jan Tojnar <jtojnar@gmail.com> | 2023-08-16 19:37:11 +0200 |
commit | 86797b2008511751bfb4e9abf085d8bdea1d547f (patch) | |
tree | fc3b05e823892f3a59d9c88df9d3612e3e4e07d9 /nixos | |
parent | 1dcd19866c8608e7fc1f54cc3d84542f19c65503 (diff) | |
parent | ec1e6d834ae803f02a353914843e096c2091d233 (diff) | |
download | nixlib-86797b2008511751bfb4e9abf085d8bdea1d547f.tar nixlib-86797b2008511751bfb4e9abf085d8bdea1d547f.tar.gz nixlib-86797b2008511751bfb4e9abf085d8bdea1d547f.tar.bz2 nixlib-86797b2008511751bfb4e9abf085d8bdea1d547f.tar.lz nixlib-86797b2008511751bfb4e9abf085d8bdea1d547f.tar.xz nixlib-86797b2008511751bfb4e9abf085d8bdea1d547f.tar.zst nixlib-86797b2008511751bfb4e9abf085d8bdea1d547f.zip |
Merge branch 'staging-next' into staging
Conflicts: - pkgs/development/libraries/qt-6/default.nix Merge a5b92645f1e6762e4b53f48652cb766184d84e77 and 0597d865ef4f763f3fed54702b29ce328d28e2b4
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/doc/manual/administration/service-mgmt.chapter.md | 30 | ||||
-rw-r--r-- | nixos/modules/services/networking/haproxy.nix | 17 | ||||
-rw-r--r-- | nixos/modules/services/security/kanidm.nix | 14 | ||||
-rw-r--r-- | nixos/modules/services/video/mediamtx.nix | 53 | ||||
-rw-r--r-- | nixos/modules/services/web-apps/invidious.nix | 64 | ||||
-rw-r--r-- | nixos/modules/services/x11/picom.nix | 6 | ||||
-rwxr-xr-x | nixos/modules/system/activation/switch-to-configuration.pl | 58 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 1 | ||||
-rw-r--r-- | nixos/tests/mediamtx.nix | 57 | ||||
-rw-r--r-- | nixos/tests/prometheus-exporters.nix | 2 | ||||
-rw-r--r-- | nixos/tests/switch-test.nix | 162 | ||||
-rw-r--r-- | nixos/tests/web-servers/agate.nix | 2 |
12 files changed, 368 insertions, 98 deletions
diff --git a/nixos/doc/manual/administration/service-mgmt.chapter.md b/nixos/doc/manual/administration/service-mgmt.chapter.md index 674c73741680..bc9bdbe3708b 100644 --- a/nixos/doc/manual/administration/service-mgmt.chapter.md +++ b/nixos/doc/manual/administration/service-mgmt.chapter.md @@ -118,3 +118,33 @@ the symlink, and this path is in `/nix/store/.../lib/systemd/user/`. Hence [garbage collection](#sec-nix-gc) will remove that file and you will wind up with a broken symlink in your systemd configuration, which in turn will not make the service / timer start on login. + +## Template units {#sect-nixos-systemd-template-units} + +systemd supports templated units where a base unit can be started multiple +times with a different parameter. The syntax to accomplish this is +`service-name@instance-name.service`. Units get the instance name passed to +them (see `systemd.unit(5)`). NixOS has support for these kinds of units and +for template-specific overrides. A service needs to be defined twice, once +for the base unit and once for the instance. All instances must include +`overrideStrategy = "asDropin"` for the change detection to work. This +example illustrates this: +```nix +{ + systemd.services = { + "base-unit@".serviceConfig = { + ExecStart = "..."; + User = "..."; + }; + "base-unit@instance-a" = { + overrideStrategy = "asDropin"; # needed for templates to work + wantedBy = [ "multi-user.target" ]; # causes NixOS to manage the instance + }; + "base-unit@instance-b" = { + overrideStrategy = "asDropin"; # needed for templates to work + wantedBy = [ "multi-user.target" ]; # causes NixOS to manage the instance + serviceConfig.User = "root"; # also override something for this specific instance + }; + }; +} +``` diff --git a/nixos/modules/services/networking/haproxy.nix b/nixos/modules/services/networking/haproxy.nix index e0b686434b6e..208eb356d629 100644 --- a/nixos/modules/services/networking/haproxy.nix +++ b/nixos/modules/services/networking/haproxy.nix @@ -17,14 +17,9 @@ with lib; options = { services.haproxy = { - enable = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Whether to enable HAProxy, the reliable, high performance TCP/HTTP - load balancer. - ''; - }; + enable = mkEnableOption (lib.mdDoc "HAProxy, the reliable, high performance TCP/HTTP load balancer."); + + package = mkPackageOptionMD pkgs "haproxy" { }; user = mkOption { type = types.str; @@ -70,15 +65,15 @@ with lib; ExecStartPre = [ # when the master process receives USR2, it reloads itself using exec(argv[0]), # so we create a symlink there and update it before reloading - "${pkgs.coreutils}/bin/ln -sf ${pkgs.haproxy}/sbin/haproxy /run/haproxy/haproxy" + "${pkgs.coreutils}/bin/ln -sf ${lib.getExe cfg.package} /run/haproxy/haproxy" # when running the config test, don't be quiet so we can see what goes wrong "/run/haproxy/haproxy -c -f ${haproxyCfg}" ]; ExecStart = "/run/haproxy/haproxy -Ws -f /etc/haproxy.cfg -p /run/haproxy/haproxy.pid"; # support reloading ExecReload = [ - "${pkgs.haproxy}/sbin/haproxy -c -f ${haproxyCfg}" - "${pkgs.coreutils}/bin/ln -sf ${pkgs.haproxy}/sbin/haproxy /run/haproxy/haproxy" + "${lib.getExe cfg.package} -c -f ${haproxyCfg}" + "${pkgs.coreutils}/bin/ln -sf ${lib.getExe cfg.package} /run/haproxy/haproxy" "${pkgs.coreutils}/bin/kill -USR2 $MAINPID" ]; KillMode = "mixed"; diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix index 6fb9f71a489e..d8a99dee59f4 100644 --- a/nixos/modules/services/security/kanidm.nix +++ b/nixos/modules/services/security/kanidm.nix @@ -69,6 +69,8 @@ in enableServer = lib.mkEnableOption (lib.mdDoc "the Kanidm server"); enablePam = lib.mkEnableOption (lib.mdDoc "the Kanidm PAM and NSS integration"); + package = lib.mkPackageOptionMD pkgs "kanidm" {}; + serverSettings = lib.mkOption { type = lib.types.submodule { freeformType = settingsFormat.type; @@ -222,7 +224,7 @@ in } ]; - environment.systemPackages = lib.mkIf cfg.enableClient [ pkgs.kanidm ]; + environment.systemPackages = lib.mkIf cfg.enableClient [ cfg.package ]; systemd.services.kanidm = lib.mkIf cfg.enableServer { description = "kanidm identity management daemon"; @@ -237,7 +239,7 @@ in StateDirectory = "kanidm"; StateDirectoryMode = "0700"; RuntimeDirectory = "kanidmd"; - ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}"; + ExecStart = "${cfg.package}/bin/kanidmd server -c ${serverConfigFile}"; User = "kanidm"; Group = "kanidm"; @@ -270,7 +272,7 @@ in CacheDirectory = "kanidm-unixd"; CacheDirectoryMode = "0700"; RuntimeDirectory = "kanidm-unixd"; - ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd"; + ExecStart = "${cfg.package}/bin/kanidm_unixd"; User = "kanidm-unixd"; Group = "kanidm-unixd"; @@ -302,7 +304,7 @@ in partOf = [ "kanidm-unixd.service" ]; restartTriggers = [ unixConfigFile clientConfigFile ]; serviceConfig = { - ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd_tasks"; + ExecStart = "${cfg.package}/bin/kanidm_unixd_tasks"; BindReadOnlyPaths = [ "/nix/store" @@ -346,7 +348,7 @@ in }) ]; - system.nssModules = lib.mkIf cfg.enablePam [ pkgs.kanidm ]; + system.nssModules = lib.mkIf cfg.enablePam [ cfg.package ]; system.nssDatabases.group = lib.optional cfg.enablePam "kanidm"; system.nssDatabases.passwd = lib.optional cfg.enablePam "kanidm"; @@ -365,7 +367,7 @@ in description = "Kanidm server"; isSystemUser = true; group = "kanidm"; - packages = with pkgs; [ kanidm ]; + packages = [ cfg.package ]; }; }) (lib.mkIf cfg.enablePam { diff --git a/nixos/modules/services/video/mediamtx.nix b/nixos/modules/services/video/mediamtx.nix index 18a9e3d5fe30..c3abd9cdcc5c 100644 --- a/nixos/modules/services/video/mediamtx.nix +++ b/nixos/modules/services/video/mediamtx.nix @@ -1,79 +1,66 @@ { config, lib, pkgs, ... }: -with lib; - let cfg = config.services.mediamtx; - package = pkgs.mediamtx; format = pkgs.formats.yaml {}; in { + meta.maintainers = with lib.maintainers; [ fpletz ]; + options = { services.mediamtx = { - enable = mkEnableOption (lib.mdDoc "MediaMTX"); + enable = lib.mkEnableOption (lib.mdDoc "MediaMTX"); - settings = mkOption { + package = lib.mkPackageOptionMD pkgs "mediamtx" { }; + + settings = lib.mkOption { description = lib.mdDoc '' - Settings for MediaMTX. - Read more at <https://github.com/aler9/mediamtx/blob/main/mediamtx.yml> + Settings for MediaMTX. Refer to the defaults at + <https://github.com/bluenviron/mediamtx/blob/main/mediamtx.yml>. ''; type = format.type; - - default = { - logLevel = "info"; - logDestinations = [ - "stdout" - ]; - # we set this so when the user uses it, it just works (see LogsDirectory below). but it's not used by default. - logFile = "/var/log/mediamtx/mediamtx.log"; - }; - + default = {}; example = { paths = { cam = { - runOnInit = "ffmpeg -f v4l2 -i /dev/video0 -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH"; + runOnInit = "\${lib.getExe pkgs.ffmpeg} -f v4l2 -i /dev/video0 -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH"; runOnInitRestart = true; }; }; }; }; - env = mkOption { - type = with types; attrsOf anything; + env = lib.mkOption { + type = with lib.types; attrsOf anything; description = lib.mdDoc "Extra environment variables for MediaMTX"; default = {}; example = { MTX_CONFKEY = "mykey"; }; }; + + allowVideoAccess = lib.mkEnableOption (lib.mdDoc '' + Enable access to video devices like cameras on the system. + ''); }; }; - config = mkIf (cfg.enable) { + config = lib.mkIf cfg.enable { # NOTE: mediamtx watches this file and automatically reloads if it changes environment.etc."mediamtx.yaml".source = format.generate "mediamtx.yaml" cfg.settings; systemd.services.mediamtx = { - environment = cfg.env; - after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - path = with pkgs; [ - ffmpeg - ]; + environment = cfg.env; serviceConfig = { DynamicUser = true; User = "mediamtx"; Group = "mediamtx"; - - LogsDirectory = "mediamtx"; - - # user likely may want to stream cameras, can't hurt to add video group - SupplementaryGroups = "video"; - - ExecStart = "${package}/bin/mediamtx /etc/mediamtx.yaml"; + SupplementaryGroups = lib.mkIf cfg.allowVideoAccess "video"; + ExecStart = "${cfg.package}/bin/mediamtx /etc/mediamtx.yaml"; }; }; }; diff --git a/nixos/modules/services/web-apps/invidious.nix b/nixos/modules/services/web-apps/invidious.nix index 8823da010014..5603ef7392e8 100644 --- a/nixos/modules/services/web-apps/invidious.nix +++ b/nixos/modules/services/web-apps/invidious.nix @@ -7,6 +7,9 @@ let settingsFile = settingsFormat.generate "invidious-settings" cfg.settings; + generatedHmacKeyFile = "/var/lib/invidious/hmac_key"; + generateHmac = cfg.hmacKeyFile == null; + serviceConfig = { systemd.services.invidious = { description = "Invidious (An alternative YouTube front-end)"; @@ -14,22 +17,47 @@ let after = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; - script = - let - jqFilter = "." - + lib.optionalString (cfg.database.host != null) "[0].db.password = \"'\"'\"$(cat ${lib.escapeShellArg cfg.database.passwordFile})\"'\"'\"" - + " | .[0]" - + lib.optionalString (cfg.extraSettingsFile != null) " * .[1]"; - jqFiles = [ settingsFile ] ++ lib.optional (cfg.extraSettingsFile != null) cfg.extraSettingsFile; - in - '' - export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s "${jqFilter}" ${lib.escapeShellArgs jqFiles})" - exec ${cfg.package}/bin/invidious - ''; + preStart = lib.optionalString generateHmac '' + if [[ ! -e "${generatedHmacKeyFile}" ]]; then + ${pkgs.pwgen}/bin/pwgen 20 1 > "${generatedHmacKeyFile}" + chmod 0600 "${generatedHmacKeyFile}" + fi + ''; + + script = '' + configParts=() + '' + # autogenerated hmac_key + + lib.optionalString generateHmac '' + configParts+=("$(${pkgs.jq}/bin/jq -R '{"hmac_key":.}' <"${generatedHmacKeyFile}")") + '' + # generated settings file + + '' + configParts+=("$(< ${lib.escapeShellArg settingsFile})") + '' + # optional database password file + + lib.optionalString (cfg.database.host != null) '' + configParts+=("$(${pkgs.jq}/bin/jq -R '{"db":{"password":.}}' ${lib.escapeShellArg cfg.database.passwordFile})") + '' + # optional extra settings file + + lib.optionalString (cfg.extraSettingsFile != null) '' + configParts+=("$(< ${lib.escapeShellArg cfg.extraSettingsFile})") + '' + # explicitly specified hmac key file + + lib.optionalString (cfg.hmacKeyFile != null) '' + configParts+=("$(< ${lib.escapeShellArg cfg.hmacKeyFile})") + '' + # merge all parts into a single configuration with later elements overriding previous elements + + '' + export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s 'reduce .[] as $item ({}; . * $item)' <<<"''${configParts[*]}")" + exec ${cfg.package}/bin/invidious + ''; serviceConfig = { RestartSec = "2s"; DynamicUser = true; + StateDirectory = "invidious"; + StateDirectoryMode = "0750"; CapabilityBoundingSet = ""; PrivateDevices = true; @@ -171,6 +199,18 @@ in ''; }; + hmacKeyFile = lib.mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc '' + A path to a file containing the `hmac_key`. If `null`, a key will be generated automatically on first + start. + + If non-`null`, this option overrides any `hmac_key` specified in {option}`services.invidious.settings` or + via {option}`services.invidious.extraSettingsFile`. + ''; + }; + extraSettingsFile = lib.mkOption { type = types.nullOr types.str; default = null; diff --git a/nixos/modules/services/x11/picom.nix b/nixos/modules/services/x11/picom.nix index 1d6f3daa4022..3df0ea9e60bb 100644 --- a/nixos/modules/services/x11/picom.nix +++ b/nixos/modules/services/x11/picom.nix @@ -61,6 +61,8 @@ in { ''; }; + package = mkPackageOptionMD pkgs "picom" { }; + fade = mkOption { type = types.bool; default = false; @@ -301,13 +303,13 @@ in { }; serviceConfig = { - ExecStart = "${pkgs.picom}/bin/picom --config ${configFile}"; + ExecStart = "${getExe cfg.package} --config ${configFile}"; RestartSec = 3; Restart = "always"; }; }; - environment.systemPackages = [ pkgs.picom ]; + environment.systemPackages = [ cfg.package ]; }; meta.maintainers = with lib.maintainers; [ rnhmjoj ]; diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index cfad64039868..04d90968c4c1 100755 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -253,16 +253,24 @@ sub parse_systemd_ini { # If a directory with the same basename ending in .d exists next to the unit file, it will be # assumed to contain override files which will be parsed as well and handled properly. sub parse_unit { - my ($unit_path) = @_; + my ($unit_path, $base_unit_path) = @_; # Parse the main unit and all overrides my %unit_data; # Replace \ with \\ so glob() still works with units that have a \ in them # Valid characters in unit names are ASCII letters, digits, ":", "-", "_", ".", and "\" + $base_unit_path =~ s/\\/\\\\/gmsx; $unit_path =~ s/\\/\\\\/gmsx; - foreach (glob("${unit_path}{,.d/*.conf}")) { + + foreach (glob("${base_unit_path}{,.d/*.conf}")) { parse_systemd_ini(\%unit_data, "$_") } + # Handle drop-in template-unit instance overrides + if ($unit_path ne $base_unit_path) { + foreach (glob("${unit_path}.d/*.conf")) { + parse_systemd_ini(\%unit_data, "$_") + } + } return %unit_data; } @@ -423,7 +431,7 @@ sub compare_units { ## no critic(Subroutines::ProhibitExcessComplexity) # Called when a unit exists in both the old systemd and the new system and the units # differ. This figures out of what units are to be stopped, restarted, reloaded, started, and skipped. sub handle_modified_unit { ## no critic(Subroutines::ProhibitManyArgs, Subroutines::ProhibitExcessComplexity) - my ($unit, $base_name, $new_unit_file, $new_unit_info, $active_cur, $units_to_stop, $units_to_start, $units_to_reload, $units_to_restart, $units_to_skip) = @_; + my ($unit, $base_name, $new_unit_file, $new_base_unit_file, $new_unit_info, $active_cur, $units_to_stop, $units_to_start, $units_to_reload, $units_to_restart, $units_to_skip) = @_; if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/msx || $unit =~ /\.slice$/msx) { # Do nothing. These cannot be restarted directly. @@ -442,7 +450,7 @@ sub handle_modified_unit { ## no critic(Subroutines::ProhibitManyArgs, Subroutin # Revert of the attempt: https://github.com/NixOS/nixpkgs/pull/147609 # More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430 } else { - my %new_unit_info = $new_unit_info ? %{$new_unit_info} : parse_unit($new_unit_file); + my %new_unit_info = $new_unit_info ? %{$new_unit_info} : parse_unit($new_unit_file, $new_base_unit_file); if (parse_systemd_bool(\%new_unit_info, "Service", "X-ReloadIfChanged", 0) and not $units_to_restart->{$unit} and not $units_to_stop->{$unit}) { $units_to_reload->{$unit} = 1; record_unit($reload_list_file, $unit); @@ -538,31 +546,33 @@ my %units_to_filter; # units not shown my $active_cur = get_active_units(); while (my ($unit, $state) = each(%{$active_cur})) { - my $base_unit = $unit; + my $cur_unit_file = "/etc/systemd/system/$unit"; + my $new_unit_file = "$toplevel/etc/systemd/system/$unit"; - my $cur_unit_file = "/etc/systemd/system/$base_unit"; - my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; + my $base_unit = $unit; + my $cur_base_unit_file = $cur_unit_file; + my $new_base_unit_file = $new_unit_file; # Detect template instances. if (!-e $cur_unit_file && !-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) { $base_unit = "$1\@.$2"; - $cur_unit_file = "/etc/systemd/system/$base_unit"; - $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; + $cur_base_unit_file = "/etc/systemd/system/$base_unit"; + $new_base_unit_file = "$toplevel/etc/systemd/system/$base_unit"; } my $base_name = $base_unit; $base_name =~ s/\.[[:lower:]]*$//msx; - if (-e $cur_unit_file && ($state->{state} eq "active" || $state->{state} eq "activating")) { - if (! -e $new_unit_file || abs_path($new_unit_file) eq "/dev/null") { - my %cur_unit_info = parse_unit($cur_unit_file); + if (-e $cur_base_unit_file && ($state->{state} eq "active" || $state->{state} eq "activating")) { + if (! -e $new_base_unit_file || abs_path($new_base_unit_file) eq "/dev/null") { + my %cur_unit_info = parse_unit($cur_unit_file, $cur_base_unit_file); if (parse_systemd_bool(\%cur_unit_info, "Unit", "X-StopOnRemoval", 1)) { $units_to_stop{$unit} = 1; } } elsif ($unit =~ /\.target$/msx) { - my %new_unit_info = parse_unit($new_unit_file); + my %new_unit_info = parse_unit($new_unit_file, $new_base_unit_file); # Cause all active target units to be restarted below. # This should start most changed units we stop here as @@ -596,11 +606,11 @@ while (my ($unit, $state) = each(%{$active_cur})) { } else { - my %cur_unit_info = parse_unit($cur_unit_file); - my %new_unit_info = parse_unit($new_unit_file); + my %cur_unit_info = parse_unit($cur_unit_file, $cur_base_unit_file); + my %new_unit_info = parse_unit($new_unit_file, $new_base_unit_file); my $diff = compare_units(\%cur_unit_info, \%new_unit_info); if ($diff == 1) { - handle_modified_unit($unit, $base_name, $new_unit_file, \%new_unit_info, $active_cur, \%units_to_stop, \%units_to_start, \%units_to_reload, \%units_to_restart, \%units_to_skip); + handle_modified_unit($unit, $base_name, $new_unit_file, $new_base_unit_file, \%new_unit_info, $active_cur, \%units_to_stop, \%units_to_start, \%units_to_reload, \%units_to_restart, \%units_to_skip); } elsif ($diff == 2 and not $units_to_restart{$unit}) { $units_to_reload{$unit} = 1; record_unit($reload_list_file, $unit); @@ -710,13 +720,14 @@ if ($action eq "dry-activate") { # Handle the activation script requesting the restart or reload of a unit. foreach (split(/\n/msx, read_file($dry_restart_by_activation_file, err_mode => "quiet") // "")) { my $unit = $_; + my $new_unit_file = "$toplevel/etc/systemd/system/$unit"; my $base_unit = $unit; - my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; + my $new_base_unit_file = $new_unit_file; # Detect template instances. if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) { $base_unit = "$1\@.$2"; - $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; + $new_base_unit_file = "$toplevel/etc/systemd/system/$base_unit"; } my $base_name = $base_unit; @@ -728,7 +739,7 @@ if ($action eq "dry-activate") { next; } - handle_modified_unit($unit, $base_name, $new_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip); + handle_modified_unit($unit, $base_name, $new_unit_file, $new_base_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip); } unlink($dry_restart_by_activation_file); @@ -782,13 +793,14 @@ system("$out/activate", "$out") == 0 or $res = 2; # Handle the activation script requesting the restart or reload of a unit. foreach (split(/\n/msx, read_file($restart_by_activation_file, err_mode => "quiet") // "")) { my $unit = $_; + my $new_unit_file = "$toplevel/etc/systemd/system/$unit"; my $base_unit = $unit; - my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; + my $new_base_unit_file = $new_unit_file; # Detect template instances. if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) { $base_unit = "$1\@.$2"; - $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; + $new_base_unit_file = "$toplevel/etc/systemd/system/$base_unit"; } my $base_name = $base_unit; @@ -801,7 +813,7 @@ foreach (split(/\n/msx, read_file($restart_by_activation_file, err_mode => "quie next; } - handle_modified_unit($unit, $base_name, $new_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip); + handle_modified_unit($unit, $base_name, $new_unit_file, $new_base_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip); } # We can remove the file now because it has been propagated to the other restart/reload files unlink($restart_by_activation_file); @@ -859,7 +871,7 @@ if (scalar(keys(%units_to_reload)) > 0) { for my $unit (keys(%units_to_reload)) { if (!unit_is_active($unit)) { # Figure out if we need to start the unit - my %unit_info = parse_unit("$toplevel/etc/systemd/system/$unit"); + my %unit_info = parse_unit("$toplevel/etc/systemd/system/$unit", "$toplevel/etc/systemd/system/$unit"); if (!(parse_systemd_bool(\%unit_info, "Unit", "RefuseManualStart", 0) || parse_systemd_bool(\%unit_info, "Unit", "X-OnlyManualStart", 0))) { $units_to_start{$unit} = 1; record_unit($start_list_file, $unit); diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 530447b99786..a54047433bcd 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -463,6 +463,7 @@ in { matrix-conduit = handleTest ./matrix/conduit.nix {}; matrix-synapse = handleTest ./matrix/synapse.nix {}; mattermost = handleTest ./mattermost.nix {}; + mediamtx = handleTest ./mediamtx.nix {}; mediatomb = handleTest ./mediatomb.nix {}; mediawiki = handleTest ./mediawiki.nix {}; meilisearch = handleTest ./meilisearch.nix {}; diff --git a/nixos/tests/mediamtx.nix b/nixos/tests/mediamtx.nix new file mode 100644 index 000000000000..8cacd02631d9 --- /dev/null +++ b/nixos/tests/mediamtx.nix @@ -0,0 +1,57 @@ +import ./make-test-python.nix ({ pkgs, lib, ...} : + +{ + name = "mediamtx"; + meta.maintainers = with lib.maintainers; [ fpletz ]; + + nodes = { + machine = { config, ... }: { + services.mediamtx = { + enable = true; + settings = { + metrics = true; + paths.all.source = "publisher"; + }; + }; + + systemd.services.rtmp-publish = { + description = "Publish an RTMP stream to mediamtx"; + after = [ "mediamtx.service" ]; + bindsTo = [ "mediamtx.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + DynamicUser = true; + Restart = "on-failure"; + RestartSec = "1s"; + TimeoutStartSec = "10s"; + ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -re -f lavfi -i smptebars=size=800x600:rate=10 -c libx264 -f flv rtmp://localhost:1935/test"; + }; + }; + + systemd.services.rtmp-receive = { + description = "Receive an RTMP stream from mediamtx"; + after = [ "rtmp-publish.service" ]; + bindsTo = [ "rtmp-publish.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + DynamicUser = true; + Restart = "on-failure"; + RestartSec = "1s"; + TimeoutStartSec = "10s"; + ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -y -re -i rtmp://localhost:1935/test -f flv /dev/null"; + }; + }; + }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("mediamtx.service") + machine.wait_for_unit("rtmp-publish.service") + machine.wait_for_unit("rtmp-receive.service") + machine.wait_for_open_port(9998) + machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"publish\".*1$'") + machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"read\".*1$'") + ''; +}) diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix index 64e2811beb06..d86f8ac634e8 100644 --- a/nixos/tests/prometheus-exporters.nix +++ b/nixos/tests/prometheus-exporters.nix @@ -1200,7 +1200,7 @@ let }; exporterTest = '' wait_until_succeeds( - 'journalctl -eu prometheus-smartctl-exporter.service -o cat | grep "Device unavailable"' + 'journalctl -eu prometheus-smartctl-exporter.service -o cat | grep "Unable to detect device type"' ) ''; }; diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index f44dede7fef4..53595ae7d3e2 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -1,6 +1,6 @@ # Test configuration switching. -import ./make-test-python.nix ({ pkgs, ...} : let +import ./make-test-python.nix ({ lib, pkgs, ...} : let # Simple service that can either be socket-activated or that will # listen on port 1234 if not socket-activated. @@ -279,6 +279,28 @@ in { systemd.services.test-service.unitConfig.RefuseManualStart = true; }; + unitWithTemplate.configuration = { + systemd.services."instantiated@".serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${pkgs.coreutils}/bin/true"; + ExecReload = "${pkgs.coreutils}/bin/true"; + }; + systemd.services."instantiated@one" = { + wantedBy = [ "multi-user.target" ]; + overrideStrategy = "asDropin"; + }; + systemd.services."instantiated@two" = { + wantedBy = [ "multi-user.target" ]; + overrideStrategy = "asDropin"; + }; + }; + + unitWithTemplateModified.configuration = { + imports = [ unitWithTemplate.configuration ]; + systemd.services."instantiated@".serviceConfig.X-Test = "test"; + }; + restart-and-reload-by-activation-script.configuration = { systemd.services = rec { simple-service = { @@ -290,29 +312,50 @@ in { ExecReload = "${pkgs.coreutils}/bin/true"; }; }; + "templated-simple-service@" = simple-service; + "templated-simple-service@instance".overrideStrategy = "asDropin"; simple-restart-service = simple-service // { stopIfChanged = false; }; + "templated-simple-restart-service@" = simple-restart-service; + "templated-simple-restart-service@instance".overrideStrategy = "asDropin"; simple-reload-service = simple-service // { reloadIfChanged = true; }; + "templated-simple-reload-service@" = simple-reload-service; + "templated-simple-reload-service@instance".overrideStrategy = "asDropin"; no-restart-service = simple-service // { restartIfChanged = false; }; + "templated-no-restart-service@" = no-restart-service; + "templated-no-restart-service@instance".overrideStrategy = "asDropin"; reload-triggers = simple-service // { wantedBy = [ "multi-user.target" ]; }; + "templated-reload-triggers@" = simple-service; + "templated-reload-triggers@instance" = { + overrideStrategy = "asDropin"; + wantedBy = [ "multi-user.target" ]; + }; reload-triggers-and-restart-by-as = simple-service; + "templated-reload-triggers-and-restart-by-as@" = reload-triggers-and-restart-by-as; + "templated-reload-triggers-and-restart-by-as@instance".overrideStrategy = "asDropin"; reload-triggers-and-restart = simple-service // { stopIfChanged = false; # easier to check for this wantedBy = [ "multi-user.target" ]; }; + "templated-reload-triggers-and-restart@" = simple-service; + "templated-reload-triggers-and-restart@instance" = { + overrideStrategy = "asDropin"; + stopIfChanged = false; # easier to check for this + wantedBy = [ "multi-user.target" ]; + }; }; system.activationScripts.restart-and-reload-test = { @@ -332,12 +375,20 @@ in { simple-reload-service.service no-restart-service.service reload-triggers-and-restart-by-as.service + templated-simple-service@instance.service + templated-simple-restart-service@instance.service + templated-simple-reload-service@instance.service + templated-no-restart-service@instance.service + templated-reload-triggers-and-restart-by-as@instance.service EOF cat <<EOF >> "$g" reload-triggers.service reload-triggers-and-restart-by-as.service reload-triggers-and-restart.service + templated-reload-triggers@instance.service + templated-reload-triggers-and-restart-by-as@instance.service + templated-reload-triggers-and-restart@instance.service EOF ''; }; @@ -346,6 +397,10 @@ in { restart-and-reload-by-activation-script-modified.configuration = { imports = [ restart-and-reload-by-activation-script.configuration ]; systemd.services.reload-triggers-and-restart.serviceConfig.X-Modified = "test"; + systemd.services."templated-reload-triggers-and-restart@instance" = { + overrideStrategy = "asDropin"; + serviceConfig.X-Modified = "test"; + }; }; simple-socket.configuration = { @@ -507,6 +562,10 @@ in { set -o pipefail exec env -i "$@" | tee /dev/stderr ''; + + # Returns a comma separated representation of the given list in sorted + # order, that matches the output format of switch-to-configuration.pl + sortedUnits = xs: lib.concatStringsSep ", " (builtins.sort builtins.lessThan xs); in /* python */ '' def switch_to_specialisation(system, name, action="test", fail=False): if name == "": @@ -733,6 +792,16 @@ in { assert_contains(out, "\nstarting the following units: required-service.service\n") assert_lacks(out, "the following new units were started:") + # Ensure templated units are restarted when the base unit changes + switch_to_specialisation("${machine}", "unitWithTemplate") + out = switch_to_specialisation("${machine}", "unitWithTemplateModified") + assert_contains(out, "stopping the following units: instantiated@one.service, instantiated@two.service\n") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_lacks(out, "\nrestarting the following units:") + assert_contains(out, "\nstarting the following units: instantiated@one.service, instantiated@two.service\n") + assert_lacks(out, "the following new units were started:") + with subtest("failing units"): # Let the simple service fail switch_to_specialisation("${machine}", "simpleServiceModified") @@ -896,15 +965,62 @@ in { assert_lacks(out, "NOT restarting the following changed units:") assert_lacks(out, "reloading the following units:") assert_lacks(out, "restarting the following units:") - assert_contains(out, "\nstarting the following units: no-restart-service.service, reload-triggers-and-restart-by-as.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n") - assert_contains(out, "the following new units were started: no-restart-service.service, reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, reload-triggers.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n") + assert_contains(out, "\nstarting the following units: ${sortedUnits [ + "no-restart-service.service" + "reload-triggers-and-restart-by-as.service" + "simple-reload-service.service" + "simple-restart-service.service" + "simple-service.service" + "templated-no-restart-service@instance.service" + "templated-reload-triggers-and-restart-by-as@instance.service" + "templated-simple-reload-service@instance.service" + "templated-simple-restart-service@instance.service" + "templated-simple-service@instance.service" + ]}\n") + assert_contains(out, "the following new units were started: ${sortedUnits [ + "no-restart-service.service" + "reload-triggers-and-restart-by-as.service" + "reload-triggers-and-restart.service" + "reload-triggers.service" + "simple-reload-service.service" + "simple-restart-service.service" + "simple-service.service" + "system-templated\\\\x2dno\\\\x2drestart\\\\x2dservice.slice" + "system-templated\\\\x2dreload\\\\x2dtriggers.slice" + "system-templated\\\\x2dreload\\\\x2dtriggers\\\\x2dand\\\\x2drestart.slice" + "system-templated\\\\x2dreload\\\\x2dtriggers\\\\x2dand\\\\x2drestart\\\\x2dby\\\\x2das.slice" + "system-templated\\\\x2dsimple\\\\x2dreload\\\\x2dservice.slice" + "system-templated\\\\x2dsimple\\\\x2drestart\\\\x2dservice.slice" + "system-templated\\\\x2dsimple\\\\x2dservice.slice" + "templated-no-restart-service@instance.service" + "templated-reload-triggers-and-restart-by-as@instance.service" + "templated-reload-triggers-and-restart@instance.service" + "templated-reload-triggers@instance.service" + "templated-simple-reload-service@instance.service" + "templated-simple-restart-service@instance.service" + "templated-simple-service@instance.service" + ]}\n") # Switch to the same system where the example services get restarted # and reloaded by the activation script out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script") assert_lacks(out, "stopping the following units:") assert_lacks(out, "NOT restarting the following changed units:") - assert_contains(out, "reloading the following units: reload-triggers-and-restart.service, reload-triggers.service, simple-reload-service.service\n") - assert_contains(out, "restarting the following units: reload-triggers-and-restart-by-as.service, simple-restart-service.service, simple-service.service\n") + assert_contains(out, "reloading the following units: ${sortedUnits [ + "reload-triggers-and-restart.service" + "reload-triggers.service" + "simple-reload-service.service" + "templated-reload-triggers-and-restart@instance.service" + "templated-reload-triggers@instance.service" + "templated-simple-reload-service@instance.service" + ]}\n") + assert_contains(out, "restarting the following units: ${sortedUnits [ + "reload-triggers-and-restart-by-as.service" + "simple-restart-service.service" + "simple-service.service" + "templated-reload-triggers-and-restart-by-as@instance.service" + "templated-simple-restart-service@instance.service" + "templated-simple-service@instance.service" + ]}\n") assert_lacks(out, "\nstarting the following units:") assert_lacks(out, "the following new units were started:") # Switch to the same system and see if the service gets restarted when it's modified @@ -912,16 +1028,44 @@ in { out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script-modified") assert_lacks(out, "stopping the following units:") assert_lacks(out, "NOT restarting the following changed units:") - assert_contains(out, "reloading the following units: reload-triggers.service, simple-reload-service.service\n") - assert_contains(out, "restarting the following units: reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, simple-restart-service.service, simple-service.service\n") + assert_contains(out, "reloading the following units: ${sortedUnits [ + "reload-triggers.service" + "simple-reload-service.service" + "templated-reload-triggers@instance.service" + "templated-simple-reload-service@instance.service" + ]}\n") + assert_contains(out, "restarting the following units: ${sortedUnits [ + "reload-triggers-and-restart-by-as.service" + "reload-triggers-and-restart.service" + "simple-restart-service.service" + "simple-service.service" + "templated-reload-triggers-and-restart-by-as@instance.service" + "templated-reload-triggers-and-restart@instance.service" + "templated-simple-restart-service@instance.service" + "templated-simple-service@instance.service" + ]}\n") assert_lacks(out, "\nstarting the following units:") assert_lacks(out, "the following new units were started:") # The same, but in dry mode out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script", action="dry-activate") assert_lacks(out, "would stop the following units:") assert_lacks(out, "would NOT stop the following changed units:") - assert_contains(out, "would reload the following units: reload-triggers.service, simple-reload-service.service\n") - assert_contains(out, "would restart the following units: reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, simple-restart-service.service, simple-service.service\n") + assert_contains(out, "would reload the following units: ${sortedUnits [ + "reload-triggers.service" + "simple-reload-service.service" + "templated-reload-triggers@instance.service" + "templated-simple-reload-service@instance.service" + ]}\n") + assert_contains(out, "would restart the following units: ${sortedUnits [ + "reload-triggers-and-restart-by-as.service" + "reload-triggers-and-restart.service" + "simple-restart-service.service" + "simple-service.service" + "templated-reload-triggers-and-restart-by-as@instance.service" + "templated-reload-triggers-and-restart@instance.service" + "templated-simple-restart-service@instance.service" + "templated-simple-service@instance.service" + ]}\n") assert_lacks(out, "\nwould start the following units:") with subtest("socket-activated services"): diff --git a/nixos/tests/web-servers/agate.nix b/nixos/tests/web-servers/agate.nix index e8d789a9ca44..0de27b6f7d8d 100644 --- a/nixos/tests/web-servers/agate.nix +++ b/nixos/tests/web-servers/agate.nix @@ -20,7 +20,7 @@ geminiserver.wait_for_open_port(1965) with subtest("check is serving over gemini"): - response = geminiserver.succeed("${pkgs.gmni}/bin/gmni -j once -i -N gemini://localhost:1965") + response = geminiserver.succeed("${pkgs.gemget}/bin/gemget --header -o - gemini://localhost:1965") print(response) assert "Hello NixOS!" in response ''; |