// Authorized private-video E2E. No credential is accepted in argv or printed. import fs from 'node:fs'; import crypto from 'node:crypto'; import {execFileSync} from 'node:child_process'; const emit=x=>process.stdout.write(JSON.stringify(x)+'\n'); const sleep=ms=>new Promise(r=>setTimeout(r,ms)); let stage='model-registration', session, db, auth, video, result={states:[]}; try { if(JSON.parse(fs.readFileSync('/app/package.json','utf8')).version !== '7.3.0') throw Error('unreviewed server version'); const dbURL=new URL('file:///app/dist/core/initializers/database.js'); db=await import(dbURL.href); const {logger}=await import('file:///app/dist/core/helpers/logger.js');logger.silent=true; // Register the shipped models without invoking initDatabaseModels: that helper // recreates a SQL function and is outside this task's authorized server scope. const source=fs.readFileSync(dbURL,'utf8'); const names=source.match(/sequelizeTypescript\.addModels\(\[([\s\S]*?)\]\)/)[1].split(',').map(x=>x.trim()).filter(Boolean); const imports=[...source.matchAll(/import \{ (\w+) \} from '(\.\.\/models\/[^']+)';/g)]; const models=[]; for(const name of names){const item=imports.find(m=>m[1]===name);if(!item)throw Error('model not found');models.push((await import(new URL(item[2],dbURL).href))[name]);} db.sequelizeTypescript.addModels(models); await db.sequelizeTypescript.authenticate(); const {OAuthTokenModel}=await import('file:///app/dist/core/models/oauth/oauth-token.js'); const {OAuthClientModel}=await import('file:///app/dist/core/models/oauth/oauth-client.js'); const {UserModel}=await import('file:///app/dist/core/models/user/user.js'); const {RunnerJobModel}=await import('file:///app/dist/core/models/runner/runner-job.js'); const {RUNNER_JOB_STATES}=await import('file:///app/dist/core/initializers/constants.js'); const {Op}=await import('sequelize'); const user=await UserModel.findOne({where:{username:'root',role:0}}), client=await OAuthClientModel.loadFirstClient(); if(!user||!client)throw Error('admin/client absent'); stage='temporary-admin-session'; const expires=new Date(Date.now()+10*60*1000); session=await OAuthTokenModel.create({accessToken:crypto.randomBytes(32).toString('hex'),refreshToken:crypto.randomBytes(32).toString('hex'),accessTokenExpiresAt:expires,refreshTokenExpiresAt:expires,userId:user.id,oAuthClientId:client.id,loginDevice:'authorized-pt-worker-e2e',loginIP:'127.0.0.1',loginDate:new Date()}); auth={Authorization:'Bearer '+session.accessToken}; const base='http://127.0.0.1:9000/api/v1'; async function api(path,options={}) {const r=await fetch(base+path,{...options,headers:{...auth,...options.headers},signal:AbortSignal.timeout(30000)});if(!r.ok){result.http_status=r.status;throw Error('API request failed');}return r.status===204?null:r.json();} stage='admin-auth-check'; const me=await api('/users/me'); const channel=me.videoChannels?.[0];if(!channel)throw Error('root channel absent'); const baseline=await RunnerJobModel.max('id')||0; stage='private-upload'; const form=new FormData(); for(const [k,v] of Object.entries({channelId:String(channel.id),name:'Windows NVENC worker E2E 2026-10-09',privacy:'3',nsfw:'false',commentsEnabled:'false',downloadEnabled:'true',waitTranscoding:'true'}))form.set(k,v); form.set('videofile',new Blob([fs.readFileSync('/tmp/pt-worker-e2e-source.mp4')],{type:'video/mp4'}),'nvenc-e2e.mp4'); const upload=await api('/videos/upload',{method:'POST',body:form});video=upload.video; result.video={id:video.id,uuid:video.uuid,shortUUID:video.shortUUID,privacy:'private',watch_url:'https://tube.occupymedia.cc/w/'+video.shortUUID}; emit({phase:'uploaded',video:result.video}); stage='remote-job-states'; const end=Date.now()+5*60*1000, seen=new Map();let completed; while(Date.now()j.type==='vod-hls-transcoding' && RUNNER_JOB_STATES[j.state]==='Completed'))break; await sleep(100); } if(!completed)throw Error('remote job deadline'); result.job={id:completed.id,uuid:completed.uuid,type:completed.type,runner_id:completed.runnerId}; stage='result-media-api'; let details; for(let n=0;n<60;n++){details=await api('/videos/'+video.uuid);if(details.streamingPlaylists?.[0]?.files?.length)break;await sleep(500);} const playlist=details.streamingPlaylists?.[0];if(!playlist?.files?.length)throw Error('HLS result absent'); let fileToken; async function media(url){let u=new URL(url);u.protocol='http:';u.hostname='127.0.0.1';u.port='9000';if(fileToken)u.searchParams.set('videoFileToken',fileToken);let r=await fetch(u,{headers:auth,signal:AbortSignal.timeout(15000)});if(!r.ok&&!fileToken){try{const t=await api('/videos/'+video.uuid+'/token',{method:'POST'});fileToken=t.files?.token||t.token||t.videoFileToken;}catch{}if(fileToken){u.searchParams.set('videoFileToken',fileToken);r=await fetch(u,{headers:auth,signal:AbortSignal.timeout(15000)});}}if(!r.ok){result.media_http_status=r.status;throw Error('private media fetch failed');}return r;} stage='playlist-playback'; const master=await (await media(playlist.playlistUrl)).text(); const renditionLine=master.split(/\r?\n/).find(l=>l.trim()&&!l.startsWith('#')); const renditionURL=new URL(renditionLine,playlist.playlistUrl).href; const rendition=await (await media(renditionURL)).text(); const assetLine=rendition.split(/\r?\n/).find(l=>l.trim()&&!l.startsWith('#')); const assetURL=new URL(assetLine,renditionURL).href; const blob=Buffer.from(await (await media(assetURL)).arrayBuffer()); const path='/tmp/pt-worker-e2e-result.mp4';fs.writeFileSync(path,blob,{mode:0o600}); const probe=JSON.parse(execFileSync('ffprobe',['-v','error','-show_streams','-show_format','-of','json',path],{encoding:'utf8',timeout:30000})); stage='playback-decode';execFileSync('ffmpeg',['-v','error','-nostdin','-i',path,'-f','null','-'],{stdio:'pipe',timeout:30000}); const stream=probe.streams.find(s=>s.codec_type==='video'); result.playback={master_http:200,rendition_http:200,asset_http:200,decode_exit:0,bytes:blob.length,codec:stream.codec_name,width:stream.width,height:stream.height,duration:probe.format.duration}; result.success=true;emit({phase:'e2e-completed',job:result.job,playback:result.playback}); }catch(e){result.success=false;result.failed_stage=stage;result.error_type=e.name;emit({phase:'e2e-failed',stage,error_type:e.name,http_status:result.http_status,media_http_status:result.media_http_status});} finally{ if(session){ try{await fetch('http://127.0.0.1:9000/api/v1/users/revoke-token',{method:'POST',headers:auth,signal:AbortSignal.timeout(10000)});await sleep(300);}catch{} try{await session.destroy();result.temporary_admin_session_removed=true;}catch{result.temporary_admin_session_removed=false;} } fs.writeFileSync('/tmp/pt-worker-e2e-result.json',JSON.stringify(result,null,2),{mode:0o600}); emit({phase:'final-e2e-record',...result}); if(db)await db.sequelizeTypescript.close(); }