Loading nixos/modules/module-list.nix +1 −0 Original line number Diff line number Diff line Loading @@ -1644,6 +1644,7 @@ ./services/web-apps/part-db.nix ./services/web-apps/pds.nix ./services/web-apps/peering-manager.nix ./services/web-apps/peertube-runner.nix ./services/web-apps/peertube.nix ./services/web-apps/pgpkeyserver-lite.nix ./services/web-apps/photoprism.nix Loading nixos/modules/services/web-apps/peertube-runner.nix 0 → 100644 +256 −0 Original line number Diff line number Diff line { lib, pkgs, config, ... }: let cfg = config.services.peertube-runner; settingsFormat = pkgs.formats.toml { }; configFile = settingsFormat.generate "config.toml" cfg.settings; env = { NODE_ENV = "production"; XDG_CONFIG_HOME = "/var/lib/peertube-runner"; XDG_CACHE_HOME = "/var/cache/peertube-runner"; # peertube-runner makes its IPC socket in $XDG_DATA_HOME. XDG_DATA_HOME = "/run/peertube-runner"; }; in { options.services.peertube-runner = { enable = lib.mkEnableOption "peertube-runner"; package = lib.mkPackageOption pkgs [ "peertube" "runner" ] { }; user = lib.mkOption { type = lib.types.str; default = "prunner"; example = "peertube-runner"; description = "User account under which peertube-runner runs."; }; group = lib.mkOption { type = lib.types.str; default = "prunner"; example = "peertube-runner"; description = "Group under which peertube-runner runs."; }; settings = lib.mkOption { type = settingsFormat.type; default = { }; example = lib.literalExpression '' { jobs.concurrency = 4; ffmpeg = { threads = 0; # Let ffmpeg automatically choose. nice = 5; }; transcription.model = "large-v3"; } ''; description = '' Configuration for peertube-runner. See available configuration options at https://docs.joinpeertube.org/maintain/tools#configuration. ''; }; instancesToRegister = lib.mkOption { type = with lib.types; attrsOf (submodule { options = { url = lib.mkOption { type = lib.types.str; example = "https://mypeertubeinstance.com"; description = "URL of the PeerTube instance."; }; registrationTokenFile = lib.mkOption { type = lib.types.path; example = "/run/secrets/my-peertube-instance-registration-token"; description = '' Path to a file containing a registration token for the PeerTube instance. See how to generate registration tokens at https://docs.joinpeertube.org/admin/remote-runners#manage-remote-runners. ''; }; runnerName = lib.mkOption { type = lib.types.str; example = "Transcription"; description = "Runner name declared to the PeerTube instance."; }; runnerDescription = lib.mkOption { type = with lib.types; nullOr str; default = null; example = "Runner for video transcription"; description = "Runner description declared to the PeerTube instance."; }; }; }); default = { }; example = { personal = { url = "https://mypeertubeinstance.com"; registrationTokenFile = "/run/secrets/my-peertube-instance-registration-token"; runnerName = "Transcription"; runnerDescription = "Runner for video transcription"; }; }; description = "PeerTube instances to register this runner with."; }; enabledJobTypes = lib.mkOption { type = with lib.types; nonEmptyListOf str; default = [ "vod-web-video-transcoding" "vod-hls-transcoding" "vod-audio-merge-transcoding" "live-rtmp-hls-transcoding" "video-studio-transcoding" "video-transcription" ]; example = [ "video-transcription" ]; description = "Job types that this runner will execute."; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = !(cfg.settings ? registeredInstances); message = '' `services.peertube-runner.settings.registeredInstances` cannot be used. Instead, registered instances can be configured with `services.peertube-runner.instancesToRegister`. ''; } ]; warnings = lib.optional (cfg.instancesToRegister == { }) '' `services.peertube-runner.instancesToRegister` is empty. Instances cannot be manually registered using the command line. ''; services.peertube-runner.settings = { transcription = lib.mkIf (lib.elem "video-transcription" cfg.enabledJobTypes) { engine = lib.mkDefault "whisper-ctranslate2"; enginePath = lib.mkDefault (lib.getExe pkgs.whisper-ctranslate2); }; }; environment.systemPackages = [ (pkgs.writeShellScriptBin "peertube-runner" '' ${lib.concatMapAttrsStringSep "\n" (name: value: ''export ${name}="${toString value}"'') env} if [[ "$USER" == ${cfg.user} ]]; then exec ${lib.getExe' cfg.package "peertube-runner"} "$@" else echo "This has to be run with the \`${cfg.user}\` user. Ex: \`sudo -u ${cfg.user} peertube-runner\`" fi '') ]; systemd.services.peertube-runner = { description = "peertube-runner daemon"; after = [ "network.target" (lib.mkIf config.services.peertube.enable "peertube.service") ]; wantedBy = [ "multi-user.target" ]; environment = env; path = [ pkgs.ffmpeg-headless ]; script = '' config_dir=$XDG_CONFIG_HOME/peertube-runner-nodejs/default mkdir -p $config_dir config_file=$config_dir/config.toml cp -f --no-preserve=mode,ownership ${configFile} $config_file ${lib.optionalString ((lib.length (lib.attrNames cfg.instancesToRegister)) > 0) '' # Temp config directory for registration commands temp_dir=$(mktemp --directory) temp_config_dir=$temp_dir/peertube-runner-nodejs/default mkdir -p $temp_config_dir temp_config_file=$temp_config_dir/config.toml mkdir -p $STATE_DIRECTORY/runner_tokens ${lib.concatMapAttrsStringSep "\n" (instanceName: instance: '' runner_token_file=$STATE_DIRECTORY/runner_tokens/${instanceName} # Register any currenctly unregistered instances. if [ ! -f $runner_token_file ] || [[ $(cat $runner_token_file) != ptrt-* ]]; then # Server has to be running for registration. XDG_CONFIG_HOME=$temp_dir ${lib.getExe' cfg.package "peertube-runner"} server & XDG_CONFIG_HOME=$temp_dir ${lib.getExe' cfg.package "peertube-runner"} register \ --url ${lib.escapeShellArg instance.url} \ --registration-token "$(cat ${instance.registrationTokenFile})" \ --runner-name ${lib.escapeShellArg instance.runnerName} \ ${lib.optionalString ( instance.runnerDescription != null ) ''--runner-description ${lib.escapeShellArg instance.runnerDescription}''} # Kill the server kill $! ${lib.getExe pkgs.yq-go} -e ".registeredInstances[0].runnerToken" \ $temp_config_file > $runner_token_file rm $temp_config_file fi echo " [[registeredInstances]] url = \"${instance.url}\" runnerToken = \"$(cat $runner_token_file)\" runnerName = \"${instance.runnerName}\" ${lib.optionalString ( instance.runnerDescription != null ) ''runnerDescription = \"${instance.runnerDescription}\"''} " >> $config_file '') cfg.instancesToRegister} ''} # Don't allow changes that won't persist. chmod 440 $config_file systemd-notify --ready exec ${lib.getExe' cfg.package "peertube-runner"} server ${ lib.concatMapStringsSep " " (jobType: "--enable-job ${jobType}") cfg.enabledJobTypes } ''; serviceConfig = { Type = "notify"; NotifyAccess = "all"; # for systemd-notify Restart = "always"; RestartSec = 5; SyslogIdentifier = "prunner"; User = cfg.user; Group = cfg.group; StateDirectory = "peertube-runner"; StateDirectoryMode = "0700"; CacheDirectory = "peertube-runner"; CacheDirectoryMode = "0700"; RuntimeDirectory = "peertube-runner"; RuntimeDirectoryMode = "0700"; ProtectSystem = "full"; NoNewPrivileges = true; ProtectHome = true; CapabilityBoundingSet = "~CAP_SYS_ADMIN"; }; }; users.users = lib.mkIf (cfg.user == "prunner") { ${cfg.user} = { isSystemUser = true; group = cfg.group; }; }; users.groups = lib.mkIf (cfg.group == "prunner") { ${cfg.group} = { }; }; }; meta.maintainers = lib.teams.ngi.members; } nixos/tests/web-apps/peertube.nix +73 −14 Original line number Diff line number Diff line import ../make-test-python.nix ( { pkgs, ... }: { lib, pkgs, ... }: let domain = "peertube.local"; port = 9000; url = "http://${domain}:${toString port}"; password = "zw4SqYVdcsXUfRX8aaFX"; registrationTokenFile = "/etc/peertube-runner-registration-token"; in { name = "peertube"; meta.maintainers = with pkgs.lib.maintainers; [ izorkin ]; meta.maintainers = with lib.maintainers; [ izorkin ] ++ lib.teams.ngi.members; nodes = { database = { Loading Loading @@ -53,7 +60,7 @@ import ../make-test-python.nix ( environment = { etc = { "peertube/password-init-root".text = '' PT_INITIAL_ROOT_PASSWORD=zw4SqYVdcsXUfRX8aaFX PT_INITIAL_ROOT_PASSWORD=${password} ''; "peertube/secrets-peertube".text = '' 063d9c60d519597acef26003d5ecc32729083965d09181ef3949200cbe5f09ee Loading @@ -77,14 +84,14 @@ import ../make-test-python.nix ( ]; }; extraHosts = '' 192.168.2.11 peertube.local 192.168.2.11 ${domain} ''; firewall.allowedTCPPorts = [ 9000 ]; firewall.allowedTCPPorts = [ port ]; }; services.peertube = { enable = true; localDomain = "peertube.local"; localDomain = domain; enableWebHttps = false; serviceEnvironmentFile = "/etc/peertube/password-init-root"; Loading Loading @@ -132,9 +139,28 @@ import ../make-test-python.nix ( ]; }; extraHosts = '' 192.168.2.11 peertube.local 192.168.2.11 ${domain} ''; }; services.peertube-runner = { enable = true; # Don't pull in unneeded dependencies. enabledJobTypes = [ "video-studio-transcoding" ]; instancesToRegister = { testServer1 = { inherit url registrationTokenFile; runnerName = "I'm a test!!!"; }; testServer2 = { inherit url registrationTokenFile; runnerName = "I'm also a test..."; runnerDescription = "Even more testing?!?!"; }; }; }; # Will be manually started in test script. systemd.services.peertube-runner.wantedBy = lib.mkForce [ ]; }; }; Loading @@ -149,16 +175,49 @@ import ../make-test-python.nix ( database.wait_for_open_port(31638) server.wait_for_unit("peertube.service") server.wait_for_open_port(9000) server.wait_for_open_port(${toString port}) # Check if PeerTube is running client.succeed("curl --fail http://peertube.local:9000/api/v1/config/about | jq -r '.instance.name' | grep 'PeerTube\ Test\ Server'") client.succeed("curl --fail ${url}/api/v1/config/about | jq -r '.instance.name' | grep 'PeerTube Test Server'") # PeerTube CLI client.succeed('peertube-cli auth add -u "${url}" -U "root" --password "${password}"') client.succeed('peertube-cli auth list | grep "${url}"') client.succeed('peertube-cli auth del "${url}"') client.fail('peertube-cli auth list | grep "${url}"') # peertube-runner access_token = client.succeed( 'peertube-cli get-access-token --url "${url}" --username "root" --password "${password}"' ).strip() # Generate registration token. client.succeed(f"curl --fail -X POST -H 'Authorization: Bearer {access_token}' ${url}/api/v1/runners/registration-tokens/generate") # Get registration token, and put it where `registrationTokenFile` from the # peertube-runner module points to. client.succeed( f"curl --fail -H 'Authorization: Bearer {access_token}' ${url}/api/v1/runners/registration-tokens" \ " | jq --raw-output '.data[0].registrationToken'" \ " > ${registrationTokenFile}" ) client.systemctl("start peertube-runner.service") client.wait_for_unit("peertube-runner.service") runner_command = "sudo -u prunner peertube-runner" client.succeed(f'{runner_command} list-registered | grep "I\'m a test!!!"') client.succeed(f'{runner_command} list-registered | grep "I\'m also a test..."') client.succeed(f'{runner_command} list-registered | grep "Even more testing?!?!"') # Service should still work once instances are already registered. client.systemctl("restart peertube-runner.service") client.wait_for_unit("peertube-runner.service") # Check PeerTube CLI version client.succeed('peertube-cli auth add -u "http://peertube.local:9000" -U "root" --password "zw4SqYVdcsXUfRX8aaFX"') client.succeed('peertube-cli auth list | grep "http://peertube.local:9000"') client.succeed('peertube-cli auth del "http://peertube.local:9000"') client.fail('peertube-cli auth list | grep "http://peertube.local:9000"') # Cleanup client.shutdown() server.shutdown() Loading Loading
nixos/modules/module-list.nix +1 −0 Original line number Diff line number Diff line Loading @@ -1644,6 +1644,7 @@ ./services/web-apps/part-db.nix ./services/web-apps/pds.nix ./services/web-apps/peering-manager.nix ./services/web-apps/peertube-runner.nix ./services/web-apps/peertube.nix ./services/web-apps/pgpkeyserver-lite.nix ./services/web-apps/photoprism.nix Loading
nixos/modules/services/web-apps/peertube-runner.nix 0 → 100644 +256 −0 Original line number Diff line number Diff line { lib, pkgs, config, ... }: let cfg = config.services.peertube-runner; settingsFormat = pkgs.formats.toml { }; configFile = settingsFormat.generate "config.toml" cfg.settings; env = { NODE_ENV = "production"; XDG_CONFIG_HOME = "/var/lib/peertube-runner"; XDG_CACHE_HOME = "/var/cache/peertube-runner"; # peertube-runner makes its IPC socket in $XDG_DATA_HOME. XDG_DATA_HOME = "/run/peertube-runner"; }; in { options.services.peertube-runner = { enable = lib.mkEnableOption "peertube-runner"; package = lib.mkPackageOption pkgs [ "peertube" "runner" ] { }; user = lib.mkOption { type = lib.types.str; default = "prunner"; example = "peertube-runner"; description = "User account under which peertube-runner runs."; }; group = lib.mkOption { type = lib.types.str; default = "prunner"; example = "peertube-runner"; description = "Group under which peertube-runner runs."; }; settings = lib.mkOption { type = settingsFormat.type; default = { }; example = lib.literalExpression '' { jobs.concurrency = 4; ffmpeg = { threads = 0; # Let ffmpeg automatically choose. nice = 5; }; transcription.model = "large-v3"; } ''; description = '' Configuration for peertube-runner. See available configuration options at https://docs.joinpeertube.org/maintain/tools#configuration. ''; }; instancesToRegister = lib.mkOption { type = with lib.types; attrsOf (submodule { options = { url = lib.mkOption { type = lib.types.str; example = "https://mypeertubeinstance.com"; description = "URL of the PeerTube instance."; }; registrationTokenFile = lib.mkOption { type = lib.types.path; example = "/run/secrets/my-peertube-instance-registration-token"; description = '' Path to a file containing a registration token for the PeerTube instance. See how to generate registration tokens at https://docs.joinpeertube.org/admin/remote-runners#manage-remote-runners. ''; }; runnerName = lib.mkOption { type = lib.types.str; example = "Transcription"; description = "Runner name declared to the PeerTube instance."; }; runnerDescription = lib.mkOption { type = with lib.types; nullOr str; default = null; example = "Runner for video transcription"; description = "Runner description declared to the PeerTube instance."; }; }; }); default = { }; example = { personal = { url = "https://mypeertubeinstance.com"; registrationTokenFile = "/run/secrets/my-peertube-instance-registration-token"; runnerName = "Transcription"; runnerDescription = "Runner for video transcription"; }; }; description = "PeerTube instances to register this runner with."; }; enabledJobTypes = lib.mkOption { type = with lib.types; nonEmptyListOf str; default = [ "vod-web-video-transcoding" "vod-hls-transcoding" "vod-audio-merge-transcoding" "live-rtmp-hls-transcoding" "video-studio-transcoding" "video-transcription" ]; example = [ "video-transcription" ]; description = "Job types that this runner will execute."; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = !(cfg.settings ? registeredInstances); message = '' `services.peertube-runner.settings.registeredInstances` cannot be used. Instead, registered instances can be configured with `services.peertube-runner.instancesToRegister`. ''; } ]; warnings = lib.optional (cfg.instancesToRegister == { }) '' `services.peertube-runner.instancesToRegister` is empty. Instances cannot be manually registered using the command line. ''; services.peertube-runner.settings = { transcription = lib.mkIf (lib.elem "video-transcription" cfg.enabledJobTypes) { engine = lib.mkDefault "whisper-ctranslate2"; enginePath = lib.mkDefault (lib.getExe pkgs.whisper-ctranslate2); }; }; environment.systemPackages = [ (pkgs.writeShellScriptBin "peertube-runner" '' ${lib.concatMapAttrsStringSep "\n" (name: value: ''export ${name}="${toString value}"'') env} if [[ "$USER" == ${cfg.user} ]]; then exec ${lib.getExe' cfg.package "peertube-runner"} "$@" else echo "This has to be run with the \`${cfg.user}\` user. Ex: \`sudo -u ${cfg.user} peertube-runner\`" fi '') ]; systemd.services.peertube-runner = { description = "peertube-runner daemon"; after = [ "network.target" (lib.mkIf config.services.peertube.enable "peertube.service") ]; wantedBy = [ "multi-user.target" ]; environment = env; path = [ pkgs.ffmpeg-headless ]; script = '' config_dir=$XDG_CONFIG_HOME/peertube-runner-nodejs/default mkdir -p $config_dir config_file=$config_dir/config.toml cp -f --no-preserve=mode,ownership ${configFile} $config_file ${lib.optionalString ((lib.length (lib.attrNames cfg.instancesToRegister)) > 0) '' # Temp config directory for registration commands temp_dir=$(mktemp --directory) temp_config_dir=$temp_dir/peertube-runner-nodejs/default mkdir -p $temp_config_dir temp_config_file=$temp_config_dir/config.toml mkdir -p $STATE_DIRECTORY/runner_tokens ${lib.concatMapAttrsStringSep "\n" (instanceName: instance: '' runner_token_file=$STATE_DIRECTORY/runner_tokens/${instanceName} # Register any currenctly unregistered instances. if [ ! -f $runner_token_file ] || [[ $(cat $runner_token_file) != ptrt-* ]]; then # Server has to be running for registration. XDG_CONFIG_HOME=$temp_dir ${lib.getExe' cfg.package "peertube-runner"} server & XDG_CONFIG_HOME=$temp_dir ${lib.getExe' cfg.package "peertube-runner"} register \ --url ${lib.escapeShellArg instance.url} \ --registration-token "$(cat ${instance.registrationTokenFile})" \ --runner-name ${lib.escapeShellArg instance.runnerName} \ ${lib.optionalString ( instance.runnerDescription != null ) ''--runner-description ${lib.escapeShellArg instance.runnerDescription}''} # Kill the server kill $! ${lib.getExe pkgs.yq-go} -e ".registeredInstances[0].runnerToken" \ $temp_config_file > $runner_token_file rm $temp_config_file fi echo " [[registeredInstances]] url = \"${instance.url}\" runnerToken = \"$(cat $runner_token_file)\" runnerName = \"${instance.runnerName}\" ${lib.optionalString ( instance.runnerDescription != null ) ''runnerDescription = \"${instance.runnerDescription}\"''} " >> $config_file '') cfg.instancesToRegister} ''} # Don't allow changes that won't persist. chmod 440 $config_file systemd-notify --ready exec ${lib.getExe' cfg.package "peertube-runner"} server ${ lib.concatMapStringsSep " " (jobType: "--enable-job ${jobType}") cfg.enabledJobTypes } ''; serviceConfig = { Type = "notify"; NotifyAccess = "all"; # for systemd-notify Restart = "always"; RestartSec = 5; SyslogIdentifier = "prunner"; User = cfg.user; Group = cfg.group; StateDirectory = "peertube-runner"; StateDirectoryMode = "0700"; CacheDirectory = "peertube-runner"; CacheDirectoryMode = "0700"; RuntimeDirectory = "peertube-runner"; RuntimeDirectoryMode = "0700"; ProtectSystem = "full"; NoNewPrivileges = true; ProtectHome = true; CapabilityBoundingSet = "~CAP_SYS_ADMIN"; }; }; users.users = lib.mkIf (cfg.user == "prunner") { ${cfg.user} = { isSystemUser = true; group = cfg.group; }; }; users.groups = lib.mkIf (cfg.group == "prunner") { ${cfg.group} = { }; }; }; meta.maintainers = lib.teams.ngi.members; }
nixos/tests/web-apps/peertube.nix +73 −14 Original line number Diff line number Diff line import ../make-test-python.nix ( { pkgs, ... }: { lib, pkgs, ... }: let domain = "peertube.local"; port = 9000; url = "http://${domain}:${toString port}"; password = "zw4SqYVdcsXUfRX8aaFX"; registrationTokenFile = "/etc/peertube-runner-registration-token"; in { name = "peertube"; meta.maintainers = with pkgs.lib.maintainers; [ izorkin ]; meta.maintainers = with lib.maintainers; [ izorkin ] ++ lib.teams.ngi.members; nodes = { database = { Loading Loading @@ -53,7 +60,7 @@ import ../make-test-python.nix ( environment = { etc = { "peertube/password-init-root".text = '' PT_INITIAL_ROOT_PASSWORD=zw4SqYVdcsXUfRX8aaFX PT_INITIAL_ROOT_PASSWORD=${password} ''; "peertube/secrets-peertube".text = '' 063d9c60d519597acef26003d5ecc32729083965d09181ef3949200cbe5f09ee Loading @@ -77,14 +84,14 @@ import ../make-test-python.nix ( ]; }; extraHosts = '' 192.168.2.11 peertube.local 192.168.2.11 ${domain} ''; firewall.allowedTCPPorts = [ 9000 ]; firewall.allowedTCPPorts = [ port ]; }; services.peertube = { enable = true; localDomain = "peertube.local"; localDomain = domain; enableWebHttps = false; serviceEnvironmentFile = "/etc/peertube/password-init-root"; Loading Loading @@ -132,9 +139,28 @@ import ../make-test-python.nix ( ]; }; extraHosts = '' 192.168.2.11 peertube.local 192.168.2.11 ${domain} ''; }; services.peertube-runner = { enable = true; # Don't pull in unneeded dependencies. enabledJobTypes = [ "video-studio-transcoding" ]; instancesToRegister = { testServer1 = { inherit url registrationTokenFile; runnerName = "I'm a test!!!"; }; testServer2 = { inherit url registrationTokenFile; runnerName = "I'm also a test..."; runnerDescription = "Even more testing?!?!"; }; }; }; # Will be manually started in test script. systemd.services.peertube-runner.wantedBy = lib.mkForce [ ]; }; }; Loading @@ -149,16 +175,49 @@ import ../make-test-python.nix ( database.wait_for_open_port(31638) server.wait_for_unit("peertube.service") server.wait_for_open_port(9000) server.wait_for_open_port(${toString port}) # Check if PeerTube is running client.succeed("curl --fail http://peertube.local:9000/api/v1/config/about | jq -r '.instance.name' | grep 'PeerTube\ Test\ Server'") client.succeed("curl --fail ${url}/api/v1/config/about | jq -r '.instance.name' | grep 'PeerTube Test Server'") # PeerTube CLI client.succeed('peertube-cli auth add -u "${url}" -U "root" --password "${password}"') client.succeed('peertube-cli auth list | grep "${url}"') client.succeed('peertube-cli auth del "${url}"') client.fail('peertube-cli auth list | grep "${url}"') # peertube-runner access_token = client.succeed( 'peertube-cli get-access-token --url "${url}" --username "root" --password "${password}"' ).strip() # Generate registration token. client.succeed(f"curl --fail -X POST -H 'Authorization: Bearer {access_token}' ${url}/api/v1/runners/registration-tokens/generate") # Get registration token, and put it where `registrationTokenFile` from the # peertube-runner module points to. client.succeed( f"curl --fail -H 'Authorization: Bearer {access_token}' ${url}/api/v1/runners/registration-tokens" \ " | jq --raw-output '.data[0].registrationToken'" \ " > ${registrationTokenFile}" ) client.systemctl("start peertube-runner.service") client.wait_for_unit("peertube-runner.service") runner_command = "sudo -u prunner peertube-runner" client.succeed(f'{runner_command} list-registered | grep "I\'m a test!!!"') client.succeed(f'{runner_command} list-registered | grep "I\'m also a test..."') client.succeed(f'{runner_command} list-registered | grep "Even more testing?!?!"') # Service should still work once instances are already registered. client.systemctl("restart peertube-runner.service") client.wait_for_unit("peertube-runner.service") # Check PeerTube CLI version client.succeed('peertube-cli auth add -u "http://peertube.local:9000" -U "root" --password "zw4SqYVdcsXUfRX8aaFX"') client.succeed('peertube-cli auth list | grep "http://peertube.local:9000"') client.succeed('peertube-cli auth del "http://peertube.local:9000"') client.fail('peertube-cli auth list | grep "http://peertube.local:9000"') # Cleanup client.shutdown() server.shutdown() Loading