1{ 2 lib, 3 pkgs, 4 config, 5 ... 6}: 7 8let 9 cfg = config.services.peertube-runner; 10 11 settingsFormat = pkgs.formats.toml { }; 12 configFile = settingsFormat.generate "config.toml" cfg.settings; 13 14 env = { 15 NODE_ENV = "production"; 16 XDG_CONFIG_HOME = "/var/lib/peertube-runner"; 17 XDG_CACHE_HOME = "/var/cache/peertube-runner"; 18 # peertube-runner makes its IPC socket in $XDG_DATA_HOME. 19 XDG_DATA_HOME = "/run/peertube-runner"; 20 }; 21in 22{ 23 options.services.peertube-runner = { 24 enable = lib.mkEnableOption "peertube-runner"; 25 package = lib.mkPackageOption pkgs [ "peertube" "runner" ] { }; 26 27 user = lib.mkOption { 28 type = lib.types.str; 29 default = "prunner"; 30 example = "peertube-runner"; 31 description = "User account under which peertube-runner runs."; 32 }; 33 group = lib.mkOption { 34 type = lib.types.str; 35 default = "prunner"; 36 example = "peertube-runner"; 37 description = "Group under which peertube-runner runs."; 38 }; 39 40 settings = lib.mkOption { 41 type = settingsFormat.type; 42 default = { }; 43 example = lib.literalExpression '' 44 { 45 jobs.concurrency = 4; 46 ffmpeg = { 47 threads = 0; # Let ffmpeg automatically choose. 48 nice = 5; 49 }; 50 transcription.model = "large-v3"; 51 } 52 ''; 53 description = '' 54 Configuration for peertube-runner. 55 56 See available configuration options at <https://docs.joinpeertube.org/maintain/tools#configuration>. 57 ''; 58 }; 59 instancesToRegister = lib.mkOption { 60 type = 61 with lib.types; 62 attrsOf (submodule { 63 options = { 64 url = lib.mkOption { 65 type = lib.types.str; 66 example = "https://mypeertubeinstance.com"; 67 description = "URL of the PeerTube instance."; 68 }; 69 registrationTokenFile = lib.mkOption { 70 type = lib.types.path; 71 example = "/run/secrets/my-peertube-instance-registration-token"; 72 description = '' 73 Path to a file containing a registration token for the PeerTube instance. 74 75 See how to generate registration tokens at <https://docs.joinpeertube.org/admin/remote-runners#manage-remote-runners>. 76 ''; 77 }; 78 runnerName = lib.mkOption { 79 type = lib.types.str; 80 example = "Transcription"; 81 description = "Runner name declared to the PeerTube instance."; 82 }; 83 runnerDescription = lib.mkOption { 84 type = with lib.types; nullOr str; 85 default = null; 86 example = "Runner for video transcription"; 87 description = "Runner description declared to the PeerTube instance."; 88 }; 89 }; 90 }); 91 default = { }; 92 example = { 93 personal = { 94 url = "https://mypeertubeinstance.com"; 95 registrationTokenFile = "/run/secrets/my-peertube-instance-registration-token"; 96 runnerName = "Transcription"; 97 runnerDescription = "Runner for video transcription"; 98 }; 99 }; 100 description = "PeerTube instances to register this runner with."; 101 }; 102 103 enabledJobTypes = lib.mkOption { 104 type = with lib.types; nonEmptyListOf str; 105 default = [ 106 "vod-web-video-transcoding" 107 "vod-hls-transcoding" 108 "vod-audio-merge-transcoding" 109 "live-rtmp-hls-transcoding" 110 "video-studio-transcoding" 111 "video-transcription" 112 ]; 113 example = [ "video-transcription" ]; 114 description = "Job types that this runner will execute."; 115 }; 116 }; 117 118 config = lib.mkIf cfg.enable { 119 assertions = [ 120 { 121 assertion = !(cfg.settings ? registeredInstances); 122 message = '' 123 `services.peertube-runner.settings.registeredInstances` cannot be used. 124 Instead, registered instances can be configured with `services.peertube-runner.instancesToRegister`. 125 ''; 126 } 127 ]; 128 warnings = lib.optional (cfg.instancesToRegister == { }) '' 129 `services.peertube-runner.instancesToRegister` is empty. 130 Instances cannot be manually registered using the command line. 131 ''; 132 133 services.peertube-runner.settings = { 134 transcription = lib.mkIf (lib.elem "video-transcription" cfg.enabledJobTypes) { 135 engine = lib.mkDefault "whisper-ctranslate2"; 136 enginePath = lib.mkDefault (lib.getExe pkgs.whisper-ctranslate2); 137 }; 138 }; 139 140 environment.systemPackages = [ 141 (pkgs.writeShellScriptBin "peertube-runner" '' 142 ${lib.concatMapAttrsStringSep "\n" (name: value: ''export ${name}="${toString value}"'') env} 143 144 if [[ "$USER" == ${cfg.user} ]]; then 145 exec ${lib.getExe' cfg.package "peertube-runner"} "$@" 146 else 147 echo "This has to be run with the \`${cfg.user}\` user. Ex: \`sudo -u ${cfg.user} peertube-runner\`" 148 fi 149 '') 150 ]; 151 152 systemd.services.peertube-runner = { 153 description = "peertube-runner daemon"; 154 after = [ 155 "network.target" 156 (lib.mkIf config.services.peertube.enable "peertube.service") 157 ]; 158 wantedBy = [ "multi-user.target" ]; 159 160 environment = env; 161 path = [ pkgs.ffmpeg-headless ]; 162 163 script = '' 164 config_dir=$XDG_CONFIG_HOME/peertube-runner-nodejs/default 165 mkdir -p $config_dir 166 config_file=$config_dir/config.toml 167 cp -f --no-preserve=mode,ownership ${configFile} $config_file 168 169 ${lib.optionalString ((lib.length (lib.attrNames cfg.instancesToRegister)) > 0) '' 170 # Temp config directory for registration commands 171 temp_dir=$(mktemp --directory) 172 temp_config_dir=$temp_dir/peertube-runner-nodejs/default 173 mkdir -p $temp_config_dir 174 temp_config_file=$temp_config_dir/config.toml 175 176 mkdir -p $STATE_DIRECTORY/runner_tokens 177 ${lib.concatMapAttrsStringSep "\n" (instanceName: instance: '' 178 runner_token_file=$STATE_DIRECTORY/runner_tokens/${instanceName} 179 180 # Register any currenctly unregistered instances. 181 if [ ! -f $runner_token_file ] || [[ $(cat $runner_token_file) != ptrt-* ]]; then 182 # Server has to be running for registration. 183 XDG_CONFIG_HOME=$temp_dir ${lib.getExe' cfg.package "peertube-runner"} server & 184 185 XDG_CONFIG_HOME=$temp_dir ${lib.getExe' cfg.package "peertube-runner"} register \ 186 --url ${lib.escapeShellArg instance.url} \ 187 --registration-token "$(cat ${instance.registrationTokenFile})" \ 188 --runner-name ${lib.escapeShellArg instance.runnerName} \ 189 ${lib.optionalString ( 190 instance.runnerDescription != null 191 ) ''--runner-description ${lib.escapeShellArg instance.runnerDescription}''} 192 193 # Kill the server 194 kill $! 195 196 ${lib.getExe pkgs.yq-go} -e ".registeredInstances[0].runnerToken" \ 197 $temp_config_file > $runner_token_file 198 rm $temp_config_file 199 fi 200 201 echo " 202 203 [[registeredInstances]] 204 url = \"${instance.url}\" 205 runnerToken = \"$(cat $runner_token_file)\" 206 runnerName = \"${instance.runnerName}\" 207 ${lib.optionalString ( 208 instance.runnerDescription != null 209 ) ''runnerDescription = \"${instance.runnerDescription}\"''} 210 " >> $config_file 211 '') cfg.instancesToRegister} 212 ''} 213 214 # Don't allow changes that won't persist. 215 chmod 440 $config_file 216 217 systemd-notify --ready 218 exec ${lib.getExe' cfg.package "peertube-runner"} server ${ 219 lib.concatMapStringsSep " " (jobType: "--enable-job ${jobType}") cfg.enabledJobTypes 220 } 221 ''; 222 serviceConfig = { 223 Type = "notify"; 224 NotifyAccess = "all"; # for systemd-notify 225 Restart = "always"; 226 RestartSec = 5; 227 SyslogIdentifier = "prunner"; 228 User = cfg.user; 229 Group = cfg.group; 230 StateDirectory = "peertube-runner"; 231 StateDirectoryMode = "0700"; 232 CacheDirectory = "peertube-runner"; 233 CacheDirectoryMode = "0700"; 234 RuntimeDirectory = "peertube-runner"; 235 RuntimeDirectoryMode = "0700"; 236 237 ProtectSystem = "full"; 238 NoNewPrivileges = true; 239 ProtectHome = true; 240 CapabilityBoundingSet = "~CAP_SYS_ADMIN"; 241 }; 242 }; 243 244 users.users = lib.mkIf (cfg.user == "prunner") { 245 ${cfg.user} = { 246 isSystemUser = true; 247 group = cfg.group; 248 }; 249 }; 250 users.groups = lib.mkIf (cfg.group == "prunner") { 251 ${cfg.group} = { }; 252 }; 253 }; 254 255 meta.maintainers = lib.teams.ngi.members; 256}