Unverified Commit 009450ee authored by Cosima Neidahl's avatar Cosima Neidahl Committed by GitHub
Browse files

nixos/peertube-runner: init module (#427007)

parents d65e4601 f200c10c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1648,6 +1648,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
+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;
}
+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 = {
@@ -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
@@ -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";
@@ -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 [ ];
      };

    };
@@ -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()