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

Changed files
+330 -14
nixos
modules
tests
web-apps
+1
nixos/modules/module-list.nix
···
./services/web-apps/part-db.nix
./services/web-apps/pds.nix
./services/web-apps/peering-manager.nix
./services/web-apps/peertube.nix
./services/web-apps/pgpkeyserver-lite.nix
./services/web-apps/photoprism.nix
···
./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
nixos/modules/services/web-apps/peertube-runner.nix
···
···
+
{
+
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
nixos/tests/web-apps/peertube.nix
···
import ../make-test-python.nix (
-
{ pkgs, ... }:
{
name = "peertube";
-
meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
nodes = {
database = {
···
environment = {
etc = {
"peertube/password-init-root".text = ''
-
PT_INITIAL_ROOT_PASSWORD=zw4SqYVdcsXUfRX8aaFX
'';
"peertube/secrets-peertube".text = ''
063d9c60d519597acef26003d5ecc32729083965d09181ef3949200cbe5f09ee
···
];
};
extraHosts = ''
-
192.168.2.11 peertube.local
'';
-
firewall.allowedTCPPorts = [ 9000 ];
};
services.peertube = {
enable = true;
-
localDomain = "peertube.local";
enableWebHttps = false;
serviceEnvironmentFile = "/etc/peertube/password-init-root";
···
];
};
extraHosts = ''
-
192.168.2.11 peertube.local
'';
};
};
};
···
database.wait_for_open_port(31638)
server.wait_for_unit("peertube.service")
-
server.wait_for_open_port(9000)
# 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'")
-
# 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"')
client.shutdown()
server.shutdown()
···
import ../make-test-python.nix (
+
{ 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 lib.maintainers; [ izorkin ] ++ lib.teams.ngi.members;
nodes = {
database = {
···
environment = {
etc = {
"peertube/password-init-root".text = ''
+
PT_INITIAL_ROOT_PASSWORD=${password}
'';
"peertube/secrets-peertube".text = ''
063d9c60d519597acef26003d5ecc32729083965d09181ef3949200cbe5f09ee
···
];
};
extraHosts = ''
+
192.168.2.11 ${domain}
'';
+
firewall.allowedTCPPorts = [ port ];
};
services.peertube = {
enable = true;
+
localDomain = domain;
enableWebHttps = false;
serviceEnvironmentFile = "/etc/peertube/password-init-root";
···
];
};
extraHosts = ''
+
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 [ ];
};
};
···
database.wait_for_open_port(31638)
server.wait_for_unit("peertube.service")
+
server.wait_for_open_port(${toString port})
# Check if PeerTube is running
+
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")
+
+
# Cleanup
client.shutdown()
server.shutdown()