Loading nixos/doc/manual/administration/service-mgmt.chapter.md +30 −0 Original line number Diff line number Diff line Loading @@ -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 }; }; } ``` nixos/modules/services/video/mediamtx.nix +20 −33 Original line number Diff line number Diff line { 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"; }; }; }; Loading nixos/modules/system/activation/switch-to-configuration.pl +35 −23 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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. Loading @@ -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); Loading Loading @@ -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 Loading Loading @@ -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); Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -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); Loading nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -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 {}; Loading nixos/tests/mediamtx.nix 0 → 100644 +57 −0 Original line number Diff line number Diff line 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$'") ''; }) Loading
nixos/doc/manual/administration/service-mgmt.chapter.md +30 −0 Original line number Diff line number Diff line Loading @@ -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 }; }; } ```
nixos/modules/services/video/mediamtx.nix +20 −33 Original line number Diff line number Diff line { 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"; }; }; }; Loading
nixos/modules/system/activation/switch-to-configuration.pl +35 −23 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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. Loading @@ -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); Loading Loading @@ -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 Loading Loading @@ -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); Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -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); Loading
nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -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 {}; Loading
nixos/tests/mediamtx.nix 0 → 100644 +57 −0 Original line number Diff line number Diff line 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$'") ''; })