Init commit

This commit is contained in:
ShreejitPanchal
2026-05-19 19:56:02 +08:00
commit 6601501eff
4047 changed files with 2185372 additions and 0 deletions

339
scripts/benchmark.ts Normal file
View File

@@ -0,0 +1,339 @@
import { Video, VideoPrivacy } from '@peertube/peertube-models'
import {
createSingleServer,
doubleFollow,
killallServers,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
import autocannon, { printResult } from 'autocannon'
import { program } from 'commander'
import { writeJson } from 'fs-extra/esm'
let servers: PeerTubeServer[]
// First server
let server: PeerTubeServer
let video: Video
let threadId: number
program
.option('-o, --outfile [outfile]', 'Outfile')
.option('--grep [string]', 'Filter tests you want to execute')
.description('Run API REST benchmark')
.parse(process.argv)
const options = program.opts()
const outfile = options.outfile
run()
.catch(err => console.error(err))
.finally(() => {
if (servers) return killallServers(servers)
})
function buildAuthorizationHeader () {
return {
Authorization: 'Bearer ' + server.accessToken
}
}
function buildAPHeader () {
return {
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
}
}
function buildJSONHeader () {
return {
'Content-Type': 'application/json'
}
}
async function run () {
console.log('Preparing server...')
await prepare()
const tests = [
{
title: 'AP - account peertube',
path: '/accounts/peertube',
headers: buildAPHeader(),
expecter: (body, status) => {
return status === 200 && body.startsWith('{"@context":')
}
},
{
title: 'AP - video',
path: '/videos/watch/' + video.uuid,
headers: buildAPHeader(),
expecter: (body, status) => {
return status === 200 && body.startsWith('{"@context":')
}
},
{
title: 'Misc - webfinger peertube',
path: '/.well-known/webfinger?resource=acct:peertube@' + server.host,
expecter: (body, status) => {
return status === 200 && body.startsWith('{"subject":')
}
},
{
title: 'API - unread notifications',
path: '/api/v1/users/me/notifications?start=0&count=0&unread=true',
headers: buildAuthorizationHeader(),
expecter: (_body, status) => {
return status === 200
}
},
{
title: 'API - me',
path: '/api/v1/users/me',
headers: buildAuthorizationHeader(),
expecter: (body, status) => {
return status === 200 && body.startsWith('{"id":')
}
},
{
title: 'API - videos list',
path: '/api/v1/videos',
expecter: (body, status) => {
return status === 200 && body.startsWith('{"total":10')
}
},
{
title: 'API - videos list without count*',
path: '/api/v1/videos?skipCount=true',
expecter: (body, status) => {
return status === 200 && body.startsWith('{"data":[{"')
}
},
{
title: 'API - video get',
path: '/api/v1/videos/' + video.uuid,
expecter: (body, status) => {
return status === 200 && body.startsWith('{"id":')
}
},
{
title: 'API - video captions',
path: '/api/v1/videos/' + video.uuid + '/captions',
expecter: (body, status) => {
return status === 200 && body.startsWith('{"total":4')
}
},
{
title: 'API - video threads',
path: '/api/v1/videos/' + video.uuid + '/comment-threads',
expecter: (body, status) => {
return status === 200 && body.startsWith('{"total":10')
}
},
{
title: 'API - video replies',
path: '/api/v1/videos/' + video.uuid + '/comment-threads/' + threadId,
expecter: (body, status) => {
return status === 200 && body.startsWith('{"comment":{')
}
},
{
title: 'HTML - video watch',
path: '/videos/watch/' + video.uuid,
expecter: (body, status) => {
return status === 200 && body.includes('<title>my super')
}
},
{
title: 'HTML - video embed',
path: '/videos/embed/' + video.uuid,
expecter: (body, status) => {
return status === 200 && body.includes('embed')
}
},
{
title: 'HTML - homepage',
path: '/',
expecter: (_body, status) => {
return status === 200
}
},
{
title: 'API - config',
path: '/api/v1/config',
expecter: (body, status) => {
return status === 200 && body.startsWith('{"client":')
}
},
{
title: 'API - views with token',
method: 'PUT',
headers: {
...buildAuthorizationHeader(),
...buildJSONHeader()
},
body: JSON.stringify({ currentTime: 2 }),
path: '/api/v1/videos/' + video.uuid + '/views',
expecter: (body, status) => {
return status === 204
}
},
{
title: 'API - views without token',
method: 'POST',
headers: buildJSONHeader(),
body: JSON.stringify({ currentTime: 2 }),
path: '/api/v1/videos/' + video.uuid + '/views',
expecter: (body, status) => {
return status === 204
}
}
].filter(t => {
if (!options.grep) return true
return t.title.includes(options.grep)
})
const finalResult: any[] = []
for (const test of tests) {
console.log('Running against %s.', test.path)
const testResult = await runBenchmark(test)
Object.assign(testResult, { title: test.title, path: test.path })
finalResult.push(testResult)
console.log(printResult(testResult))
}
if (outfile) await writeJson(outfile, finalResult)
}
function runBenchmark (options: {
path: string
method?: string
body?: string
headers?: { [id: string]: string }
expecter: Function
}) {
const { method = 'GET', path, body, expecter, headers } = options
return new Promise((res, rej) => {
autocannon({
url: server.url + path,
method,
body,
connections: 20,
headers,
pipelining: 1,
duration: 10,
requests: [
{
onResponse: (status, body) => {
if (expecter(body, status) !== true) {
console.error('Expected result failed.', { body, status })
throw new Error('Invalid expectation')
}
}
}
]
}, (err, result) => {
if (err) return rej(err)
return res(result)
})
})
}
async function prepare () {
const config = {
rates_limit: {
api: {
max: 5_000_000
},
login: {
max: 5_000_000
},
signup: {
max: 5_000_000
},
ask_send_email: {
max: 5_000_000
},
receive_client_log: {
max: 5_000_000
},
plugins: {
max: 5_000_000
},
well_known: {
max: 5_000_000
},
feeds: {
max: 5_000_000
},
activity_pub: {
max: 5_000_000
},
client: {
max: 5_000_000
}
}
}
servers = await Promise.all([
createSingleServer(1, config, { nodeArgs: [ '--inspect' ] }),
createSingleServer(2, config),
createSingleServer(3, config)
])
server = servers[0]
await setAccessTokensToServers(servers)
await doubleFollow(servers[0], servers[1])
await doubleFollow(servers[0], servers[2])
const attributes = {
name: 'my super video',
category: 2,
nsfw: true,
licence: 6,
language: 'fr',
privacy: VideoPrivacy.PUBLIC,
support: 'please give me a coffee',
description: 'my super description\n'.repeat(10) + ' * list1\n * list 2\n * list 3',
tags: [ 'tag1', 'tag2', 'tag3' ]
}
for (let i = 0; i < 10; i++) {
await server.videos.upload({ attributes: { ...attributes, name: 'my super video ' + i } })
}
const { data } = await server.videos.list()
video = data.find(v => v.name === 'my super video 1')
for (let i = 0; i < 10; i++) {
const text = 'my super first comment'
const created = await server.comments.createThread({ videoId: video.id, text })
threadId = created.id
const text1 = 'my super answer to thread 1'
const child = await server.comments.addReply({ videoId: video.id, toCommentId: threadId, text: text1 })
const text2 = 'my super answer to answer of thread 1'
await server.comments.addReply({ videoId: video.id, toCommentId: child.id, text: text2 })
const text3 = 'my second answer to thread 1'
await server.comments.addReply({ videoId: video.id, toCommentId: threadId, text: text3 })
}
for (const caption of [ 'ar', 'fr', 'en', 'zh' ]) {
await server.captions.add({
language: caption,
videoId: video.id,
fixture: 'subtitle-good2.vtt'
})
}
await waitJobs(servers)
}

94
scripts/build/client.sh Normal file
View File

@@ -0,0 +1,94 @@
#!/bin/bash
set -eu
defaultLanguage="en-US"
# Supported languages (key:locale pairs) - Bash 3.2 compatible for macOS
languages=(
ar:ar
ca:ca-ES
cs:cs-CZ
de:de-DE
el:el-GR
en:en-US
eo:eo
es:es-ES
eu:eu-ES
fa:fa-IR
fi:fi-FI
fr:fr-FR
gd:gd
gl:gl-ES
hr:hr
hu:hu-HU
is:is
it:it-IT
ja:ja-JP
kab:kab
nb:nb-NO
nl:nl-NL
nn:nn
oc:oc
pl:pl-PL
pt:pt-BR
pt-PT:pt-PT
ru:ru-RU
sk:sk-SK
sq:sq
sv:sv-SE
th:th-TH
tok:tok
tr:tr-TR
uk:uk-UA
vi:vi-VN
zh-Hans:zh-Hans-CN
zh-Hant:zh-Hant-TW
)
rm -rf ./client/dist
npm run build:embed
cd client
# Don't build other languages if --light arg is provided
if [ -z ${1+x} ] || ([ "$1" != "--light" ] && [ "$1" != "--analyze-bundle" ]); then
additionalParams=""
if [ ! -z ${1+x} ] && [ "$1" == "--source-map" ]; then
additionalParams="--source-map=true"
fi
NODE_OPTIONS=--max_old_space_size=8192 node_modules/.bin/ng build --configuration production --output-path "dist/build" $additionalParams
for entry in "${languages[@]}"; do
key="${entry%%:*}"
lang="${entry#*:}"
mv "dist/build/browser/$key" "dist/$lang"
if [ "$lang" != "en-US" ]; then
# Do not duplicate assets
rm -r "./dist/$lang/assets"
fi
done
mv "./dist/$defaultLanguage/assets" "./dist"
rm -r "dist/build"
else
additionalParams=""
if [ ! -z ${1+x} ] && [ "$1" == "--analyze-bundle" ]; then
additionalParams="--named-chunks=true --output-hashing=none"
# For Vite
export ANALYZE_BUNDLE=true
fi
NODE_OPTIONS=--max_old_space_size=8192 node_modules/.bin/ng build --localize=false --output-path "dist/$defaultLanguage/" \
--configuration production --stats-json $additionalParams
fi
# Copy runtime locales
cp -r "./src/locale" "./dist/locale"

7
scripts/build/embed.sh Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
set -eu
(cd client/src/standalone/player && npm run build)
cd client && ./node_modules/.bin/vite -c ./src/standalone/videos/vite.config.mjs build --mode=production

12
scripts/build/index.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
set -eu
npm run build:server
# Angular does not support project references, it's the reason why we can't builds concurrently
if [ ! -z ${1+x} ]; then
npm run build:client -- $1
else
npm run build:client
fi

View File

@@ -0,0 +1,12 @@
#!/bin/bash
set -eu
cd ./apps/peertube-cli
rm -rf ./dist
../../node_modules/.bin/tsc -b --verbose
rm -rf ./dist
mkdir ./dist
node ./scripts/build.js

View File

@@ -0,0 +1,12 @@
#!/bin/bash
set -eu
cd ./apps/peertube-runner
rm -rf ./dist
../../node_modules/.bin/tsc -b --verbose
rm -rf ./dist
mkdir ./dist
node ./scripts/build.js

16
scripts/build/server.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -eu
if [ -z ${1+x} ] || [ "$1" != "--incremental" ]; then
rm -rf ./dist ./packages/*/dist
fi
npm run tsc -- -b --verbose server/tsconfig.json
npm run resolve-tspaths:server
cp -r "./server/core/static" "./server/core/assets" ./dist/core
cp -r "./server/locales" ./dist
cp "./server/scripts/upgrade.sh" "./dist/scripts"
mkdir -p ./client/dist && cp -r ./client/src/assets ./client/dist

9
scripts/build/tests.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -eu
rm -rf ./packages/tests/dist
npm run tsc -- -b --verbose ./packages/tests/tsconfig.json
npm run resolve-tspaths:server-lib
npm run resolve-tspaths:tests

162
scripts/ci.sh Normal file
View File

@@ -0,0 +1,162 @@
#!/bin/bash
set -eu
if [ $# -eq 0 ]; then
echo "Need test suite argument."
exit -1
fi
retries=3
speedFactor="${2:-1}"
runJSTest () {
jobname=$1
shift
jobs=$1
shift
files=$@
echo $files
joblog="$jobname-ci.log"
parallel -j $jobs --retries $retries \
"echo Trying {} >> $joblog; npm run mocha -- --timeout 30000 --no-config -c --exit --bail {}" \
::: $files
cat "$joblog" | sort | uniq -c
rm "$joblog"
}
findTestFiles () {
exception="-not -name index.js -not -name index.ts -not -name *.d.ts"
if [ ! -z ${2+x} ]; then
exception="$exception -not -name $2"
fi
find $1 -type f \( -name "*.js" -o -name "*.ts" \) $exception | xargs echo
}
if [ "$1" = "types-package" ]; then
npm run generate-types-package 0.0.0
# Test on in independent directory
rm -fr /tmp/types-generator
mkdir -p /tmp/types-generator
cp -r packages/types-generator/tests /tmp/types-generator/tests
cp -r packages/types-generator/dist /tmp/types-generator/dist
(cd /tmp/types-generator/dist && npm install)
(cd /tmp/types-generator/tests && npx --package typescript tsc --noEmit --module commonjs --esModuleInterop ./test.ts)
rm -r /tmp/types-generator
elif [ "$1" = "client" ]; then
npm run build
npm run build:tests
feedsFiles=$(findTestFiles ./packages/tests/dist/feeds)
clientFiles=$(findTestFiles ./packages/tests/dist/client)
miscFiles="./packages/tests/dist/misc-endpoints.js ./packages/tests/dist/nginx.js"
# Not in their own task, they need an index.html
pluginFiles="./packages/tests/dist/plugins/html-injection.js ./packages/tests/dist/api/server/plugins.js"
MOCHA_PARALLEL=true runJSTest "$1" $((2*$speedFactor)) $feedsFiles $miscFiles $pluginFiles $clientFiles
# Use TS tests directly because we import server files
helperFiles=$(findTestFiles ./packages/tests/src/server-helpers)
libFiles=$(findTestFiles ./packages/tests/src/server-lib)
npm run mocha -- --timeout 30000 -c --exit --bail $libFiles $helperFiles
elif [ "$1" = "cli-plugin" ]; then
# Simulate HTML
mkdir -p "./client/dist/en-US/"
cp "./client/src/index.html" "./client/dist/en-US/index.html"
npm run build:server
npm run build:tests
npm run build:peertube-cli
# html-injection test needs an HTML file
pluginsFiles=$(findTestFiles ./packages/tests/dist/plugins html-injection.js)
cliFiles=$(findTestFiles ./packages/tests/dist/cli)
MOCHA_PARALLEL=true runJSTest "$1" $((2*$speedFactor)) $pluginsFiles
runJSTest "$1" 1 $cliFiles
elif [ "$1" = "api-1" ]; then
npm run build:server
npm run build:tests
checkParamFiles=$(findTestFiles ./packages/tests/dist/api/check-params)
notificationsFiles=$(findTestFiles ./packages/tests/dist/api/notifications)
searchFiles=$(findTestFiles ./packages/tests/dist/api/search)
MOCHA_PARALLEL=true runJSTest "$1" $((3*$speedFactor)) $notificationsFiles $searchFiles $checkParamFiles
elif [ "$1" = "api-2" ]; then
npm run build:server
npm run build:tests
liveFiles=$(findTestFiles ./packages/tests/dist/api/live)
# plugins test needs an HTML file
serverFiles=$(findTestFiles ./packages/tests/dist/api/server plugins.js)
usersFiles=$(findTestFiles ./packages/tests/dist/api/users)
MOCHA_PARALLEL=true runJSTest "$1" $((3*$speedFactor)) $liveFiles $serverFiles $usersFiles
elif [ "$1" = "api-3" ]; then
npm run build:server
npm run build:tests
videosFiles=$(findTestFiles ./packages/tests/dist/api/videos)
viewsFiles=$(findTestFiles ./packages/tests/dist/api/views)
MOCHA_PARALLEL=true runJSTest "$1" $((3*$speedFactor)) $viewsFiles $videosFiles
elif [ "$1" = "api-4" ]; then
npm run build:server
npm run build:tests
moderationFiles=$(findTestFiles ./packages/tests/dist/api/moderation)
redundancyFiles=$(findTestFiles ./packages/tests/dist/api/redundancy)
objectStorageFiles=$(findTestFiles ./packages/tests/dist/api/object-storage)
activitypubFiles=$(findTestFiles ./packages/tests/dist/api/activitypub)
MOCHA_PARALLEL=true runJSTest "$1" $((2*$speedFactor)) $moderationFiles $redundancyFiles $activitypubFiles $objectStorageFiles
elif [ "$1" = "api-5" ]; then
npm run build:server
npm run build:tests
transcodingFiles=$(findTestFiles ./packages/tests/dist/api/transcoding)
runnersFiles=$(findTestFiles ./packages/tests/dist/api/runners)
MOCHA_PARALLEL=true runJSTest "$1" $((2*$speedFactor)) $transcodingFiles $runnersFiles
elif [ "$1" = "external-plugins" ]; then
pnpm run --filter=@peertube/tests install-dependencies:transcription
npm run build:server
npm run build:tests
npm run build:peertube-runner
externalPluginsFiles=$(findTestFiles ./packages/tests/dist/external-plugins)
peertubeRunnerFiles=$(findTestFiles ./packages/tests/dist/peertube-runner)
runJSTest "$1" 1 $externalPluginsFiles
MOCHA_PARALLEL=true runJSTest "$1" $((2*$speedFactor)) $peertubeRunnerFiles
elif [ "$1" = "lint" ]; then
npm run eslint
npm run swagger-cli -- validate support/doc/api/openapi.yaml
( cd client && npm run lint )
elif [ "$1" = "transcription" ]; then
pnpm run --filter=@peertube/tests install-dependencies:transcription
npm run build:server
npm run build:tests
transcriptionFiles=$(findTestFiles ./packages/tests/dist/transcription)
transcriptionDevToolsFiles=$(findTestFiles ./packages/tests/dist/transcription-devtools)
MOCHA_PARALLEL=true runJSTest "$1" $((3*$speedFactor)) $transcriptionFiles $transcriptionDevToolsFiles
fi

View File

@@ -0,0 +1,6 @@
#!/bin/sh
set -eu
cd client
rm -rf compiled/ dist/ dll/

View File

@@ -0,0 +1,41 @@
#!/bin/sh
set -eu
recreateDB () {
dbname="peertube_test$1"
dropdb --if-exists "$dbname" 2>&1
createdb -O peertube "$dbname"
psql -c "CREATE EXTENSION pg_trgm;" "$dbname" &
psql -c "CREATE EXTENSION unaccent;" "$dbname" &
}
removeFiles () {
rm -rf "./test$1" "./config/local-test.json" "./config/local-test-$1.json" ~/.config/PeerTube/CLI-$1
}
dropRedis () {
port=$((9000+$1))
host="127.0.0.1"
redis-cli -h "$host" KEYS "bull-127.0.0.1:$port*" | grep -v empty | xargs -r redis-cli -h "$host" DEL
redis-cli -h "$host" KEYS "redis-127.0.0.1:$port*" | grep -v empty | xargs -r redis-cli -h "$host" DEL
redis-cli -h "$host" KEYS "*redis-127.0.0.1:$port-" | grep -v empty | xargs -r redis-cli -h "$host" DEL
}
seq=$(seq 1 6)
if [ ! -z ${1+x} ]; then
seq=$1
fi
for i in $seq; do
recreateDB "$i" &
dropRedis "$i" &
removeFiles "$i" &
done
wait

View File

@@ -0,0 +1,37 @@
import { root } from '@peertube/peertube-node-utils'
import { readdir, stat } from 'fs/promises'
import { join } from 'path'
async function run () {
const result = {
app: await buildResult(join(root(), 'client', 'dist', 'en-US')),
embed: await buildResult(join(root(), 'client', 'dist', 'standalone', 'videos'))
}
console.log(JSON.stringify(result))
}
run()
.catch(err => console.error(err))
async function buildResult (path: string, root = path) {
const distFiles = await readdir(path)
let files: { name: string, size: number }[] = []
for (const file of distFiles) {
const filePath = join(path, file)
const statsResult = await stat(filePath)
if (statsResult.isDirectory()) {
files = files.concat(await buildResult(filePath, root))
}
files.push({
name: filePath.replace(new RegExp(`^${root}/`), ''),
size: statsResult.size
})
}
return files
}

8
scripts/client-report.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
set -eu
npm run concurrently -- -k \
"cd client/src/standalone/player/ && npx vite-bundle-visualizer" \
"cd client/src/standalone/videos/ && npx vite-bundle-visualizer" \
"cd client && npx esbuild-visualizer --metadata ./dist/en-US/stats.json --open"

35
scripts/dev/client.sh Normal file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
set -eu
(cd client/src/standalone/player && npm run build)
clientConfiguration="hmr"
if [ ! -z ${2+x} ] && [ "$2" = "--ar-locale" ]; then
clientConfiguration="ar-locale"
fi
embedCommand="cd client/src/standalone/player && npm run dev"
sslArgs=""
if [ "${DEV_SSL:-false}" = "true" ]; then
certPath="${DEV_SSL_CERT:-../certs/cert.pem}"
keyPath="${DEV_SSL_KEY:-../certs/key.pem}"
sslArgs="--ssl true --ssl-cert $certPath --ssl-key $keyPath"
fi
clientCommand="cd client && NODE_OPTIONS=--max_old_space_size=8192 node_modules/.bin/ng serve --proxy-config proxy.config.json --hmr --configuration $clientConfiguration --host 0.0.0.0 --port 3000 $sslArgs"
serverCommand="ANGULAR_CLIENT_ENABLED=true NODE_ENV=dev node dist/server"
if [ ! -z ${1+x} ] && [ "$1" = "--skip-server" ]; then
node_modules/.bin/concurrently -k \
"$embedCommand" \
"$clientCommand"
else
npm run build:server
node_modules/.bin/concurrently -k \
"$embedCommand" \
"$clientCommand" \
"$serverCommand"
fi

11
scripts/dev/embed.sh Normal file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -eu
npm run build:server -- --incremental
(cd client/src/standalone/player && npm run build)
npm run concurrently -- -k \
"cd client/src/standalone/player && npm run dev" \
"cd client && ./node_modules/.bin/vite -c ./src/standalone/videos/vite.config.mjs dev" \
"NODE_ENV=dev npm start"

7
scripts/dev/index.sh Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
set -eu
npm run concurrently -- -k \
"sh scripts/dev/client.sh --skip-server ${1:-}" \
"ANGULAR_CLIENT_ENABLED=true sh scripts/dev/server.sh --skip-client"

View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -eu
rm -rf ./apps/peertube-cli/dist
cd ./apps/peertube-cli
../../node_modules/.bin/concurrently -k \
"../../node_modules/.bin/tsc -w --noEmit" \
"node ./scripts/watch.js"

View File

@@ -0,0 +1,13 @@
#!/bin/bash
set -eu
rm -rf ./apps/peertube-runner/dist
cd ./apps/peertube-runner
../../node_modules/.bin/tsc -b --verbose
../../node_modules/.bin/concurrently -k \
"../../node_modules/.bin/tsc -w --noEmit" \
"node ./scripts/watch.js"

25
scripts/dev/server.sh Normal file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
set -eu
if [ ! -f "./client/dist/en-US/index.html" ]; then
if [ -z ${1+x} ] || [ "$1" != "--skip-client" ]; then
echo "client/dist/en-US/index.html does not exist, compile client files..."
npm run build:client
fi
fi
# Copy locales
mkdir -p "./client/dist"
rm -rf "./client/dist/locale"
cp -r "./client/src/locale" "./client/dist/locale"
mkdir -p "./dist/core/lib"
npm run tsc -- -b -v --incremental server/tsconfig.json
npm run resolve-tspaths:server
cp -r "./server/core/static" "./server/core/assets" ./dist/core
cp -r "./server/locales" ./dist
./node_modules/.bin/tsc-watch --build --preserveWatchOutput --verbose --onSuccess 'sh -c "npm run resolve-tspaths:server && NODE_ENV=dev node --inspect dist/server"' server/tsconfig.json

View File

@@ -0,0 +1,5 @@
#!/bin/sh
set -eu
cd client/e2e && ../node_modules/.bin/wdio run ./wdio.browserstack.conf.ts

7
scripts/e2e/local.sh Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
set -eu
cd client/e2e
../node_modules/.bin/wdio run ./wdio.local.conf.ts

View File

@@ -0,0 +1,203 @@
import { CLICommand } from '@peertube/peertube-server-commands'
run()
.then(() => process.exit(0))
.catch(err => {
console.error(err)
process.exit(-1)
})
async function run () {
const excludeList = getContributorsExcludeList()
{
let contributors = await getGitContributors()
contributors = contributors.concat(getZanataContributors())
contributors = contributors.filter(c => !excludeList.has(c.username))
console.log('# Code & Translators contributors\n')
for (const contributor of contributors) {
console.log(` * ${contributor.username}`)
}
}
{
console.log('\n\n# Design\n')
console.log(' * [Olivier Massain](https://dribbble.com/omassain)')
console.log(' * [Marie-Cécile Godwin Paccard](https://mcgodwin.com/)')
console.log(' * [La Coopérative des Internets](https://www.lacooperativedesinternets.fr/)')
console.log('\n\n# Icons\n')
console.log(' * [Feather Icons](https://feathericons.com) (MIT)')
console.log(' * [Lucide Icons](https://lucide.dev/) (ISC)')
console.log(' * `playlist add`, `history`, `subscriptions`, `miscellaneous-services.svg`, `tip` by Material UI (Apache 2.0)')
console.log(' * `support` by Chocobozzz (CC-BY)')
console.log(' * `language` by Aaron Jin (CC-BY)')
console.log(' * `video-language` by Rigel Kent (CC-BY)')
console.log(' * `peertube-x` by Solen DP (CC-BY)')
console.log(' * `flame` by Freepik (Flaticon License)')
console.log(' * `local` by Larea (CC-BY)')
}
{
console.log('\n\n# Contributors to our 2020 crowdfunding :heart:\n')
console.log(
`*We ran [a crowdfunding campaign](https://joinpeertube.org/roadmap) in 2020 to implement live streaming to the version ` +
`3.0.0 of PeerTube. Thanks to everyone who pitched in and shared the news!*\n\n`
)
}
{
console.log('\n\n# Contributors to our 2018 crowdfunding :heart:')
console.log(
`\n*We ran [a crowdfunding campaign](https://www.kisskissbankbank.com/en/projects/peertube-a-free-and-federated-video-platform) ` +
`in 2018 to pave the road to the version 1.0.0 of PeerTube, with 1,379 backers. ` +
`Thanks to everyone who pitched in and shared the news!*\n\n`
)
}
}
async function getGitContributors () {
const { stdout } = await CLICommand.exec(`git --no-pager shortlog -sn < /dev/tty | sed 's/^\\s\\+[0-9]\\+\\s\\+//g'`)
return stdout.split('\n')
.filter(l => !!l)
.map(l => ({ username: l }))
}
// Zanata is dead, don't loose the contributors name
function getZanataContributors () {
return [
{ username: 'abdhessuk', name: 'Abd Hessuk' },
{ username: 'abidin24', name: 'abidin toumi' },
{ username: 'aditoo', name: 'Lorem Ipsum' },
{ username: 'alice', name: 'Alice' },
{ username: 'anastasia', name: 'Anastasia' },
{ username: 'autom', name: 'Filip Bengtsson' },
{ username: 'balaji', name: 'Balaji' },
{ username: 'bristow', name: 'Cédric F.' },
{ username: 'butterflyoffire', name: 'ButterflyOfFire' },
{ username: 'chocobozzz', name: 'Chocobozzz' },
{ username: 'claichou', name: 'Claire Mohin' },
{ username: 'degrange', name: 'Degrange Mathieu' },
{ username: 'dibek', name: 'Giuseppe Di Bella' },
{ username: 'edu', name: 'eduardo' },
{ username: 'ehsaan', name: 'ehsaan' },
{ username: 'esoforte', name: 'Ondřej Kotas' },
{ username: 'fkohrt', name: 'Florian Kohrt' },
{ username: 'giqtaqisi', name: 'Ian Townsend' },
{ username: 'goofy', name: 'goofy' },
{ username: 'gorkaazk', name: 'Gorka Azkarate Zubiaur' },
{ username: 'gwendald', name: 'GwendalD' },
{ username: 'h3zjp', name: 'h3zjp' },
{ username: 'jfblanc', name: 'Joan Francés Blanc' },
{ username: 'jhertel', name: 'Jean Hertel' },
{ username: 'jmf', name: 'Jan-Michael Franz' },
{ username: 'jorropo', name: 'Jorropo' },
{ username: 'kairozen', name: 'Geoffrey Baudelet' },
{ username: 'kedemferre', name: 'Kédem Ferré' },
{ username: 'kousha', name: 'Kousha Zanjani' },
{ username: 'krkk', name: 'Karol Kosek' },
{ username: 'landrok', name: 'Landrok' },
{ username: 'leeroyepold48', name: 'Leeroy Epold' },
{ username: 'm4sk1n', name: 'marcin mikołajczak' },
{ username: 'matograine', name: 'tom ngr' },
{ username: 'medow', name: 'Mahir Ahmed' },
{ username: 'mhu', name: 'Max Hübner' },
{ username: 'midgard', name: 'Midgard' },
{ username: 'nbrucy', name: 'N. B.' },
{ username: 'nitai', name: 'nitai bezerra' },
{ username: 'noncommutativegeo', name: 'Andrea Panontin' },
{ username: 'nopsidy', name: 'McFlat' },
{ username: 'nvivant', name: 'Nicolas Vivant' },
{ username: 'osoitz', name: 'Osoitz' },
{ username: 'outloudvi', name: 'Outvi V' },
{ username: 'quentin', name: 'Quentí' },
{ username: 'quentind', name: 'Quentin Dupont' },
{ username: 'rafaelff', name: 'Rafael Fontenelle' },
{ username: 'rigelk', name: 'Rigel Kent' },
{ username: 's8321414', name: 'Jeff Huang' },
{ username: 'sato_ss', name: 'Satoshi Shirosaka' },
{ username: 'sercom_kc', name: 'SerCom_KC' },
{ username: 'severo', name: 'Sylvain Lesage' },
{ username: 'silkevicious', name: 'Sylke Vicious' },
{ username: 'sosha', name: 'Sosha' },
{ username: 'spla', name: 'spla' },
{ username: 'strubbl', name: 'Sven' },
{ username: 'swedneck', name: 'Tim Stahel' },
{ username: 'tagomago', name: 'Tagomago' },
{ username: 'talone', name: 'TitiAlone' },
{ username: 'thibaultmartin', name: 'Thibault Martin' },
{ username: 'tirifto', name: 'Tirifto' },
{ username: 'tuxayo', name: 'Victor Grousset/tuxayo' },
{ username: 'unextro', name: 'Ondřej Pokorný' },
{ username: 'unzarida', name: 'unzarida' },
{ username: 'vincent', name: 'Vincent Laporte' },
{ username: 'wanhua', name: 'wanhua' },
{ username: 'xinayder', name: 'Alexandre' },
{ username: 'xosem', name: 'Xosé M.' },
{ username: 'zveryok', name: 'Nikitin Stanislav' },
{ username: '6543', name: '6543' },
{ username: 'aasami', name: 'Miroslav Ďurian' },
{ username: 'alidemirtas', name: 'Ali Demirtas' },
{ username: 'alpha', name: 'Alpha' },
{ username: 'ariasuni', name: 'Mélanie Chauvel' },
{ username: 'bfonton', name: 'Baptiste Fonton' },
{ username: 'c0dr', name: 'c0dr lnx' },
{ username: 'canony', name: 'canony' },
{ username: 'cat', name: 'Cat' },
{ username: 'clerie', name: 'Clemens Riese' },
{ username: 'curupira', name: 'Curupira' },
{ username: 'dhsets', name: 'djsets' },
{ username: 'digitalkiller', name: 'Digital Killer' },
{ username: 'dwsage', name: 'd.w. sage' },
{ username: 'flauta', name: 'Andrea Primiani' },
{ username: 'frankstrater', name: 'Frank Sträter' },
{ username: 'gillux', name: 'gillux' },
{ username: 'gunchleoc', name: 'GunChleoc' },
{ username: 'jaidedtd', name: 'Jenga Phoenix' },
{ username: 'joss2lyon', name: 'Josselin' },
{ username: 'kekkotranslates', name: 'Francesco' },
{ username: 'kingu', name: 'Allan Nordhøy' },
{ username: 'kittybecca', name: 'Rivka bat Tsvi' },
{ username: 'knuxify', name: 'knuxify' },
{ username: 'lapor', name: 'Kristijan Tkalec' },
{ username: 'laufor', name: 'Lau For' },
{ username: 'lstamellos', name: 'Loukas Stamellos' },
{ username: 'lw1', name: 'Lukas Winkler' },
{ username: 'mablr', name: 'Mablr' },
{ username: 'marcinmalecki', name: 'Marcin Małecki' },
{ username: 'mayana', name: 'Mayana' },
{ username: 'mikeorlov', name: 'Michael Orlov' },
{ username: 'nin', name: 'nz' },
{ username: 'norbipeti', name: 'NorbiPeti' },
{ username: 'ppnplus', name: 'Phongpanot Phairat' },
{ username: 'predatorix', name: 'Predatorix' },
{ username: 'robin', name: 'Robin Lahtinen' },
{ username: 'rond', name: 'rondnelly nunes' },
{ username: 'secreet', name: 'Secreet' },
{ username: 'sftblw', name: 'sftblw' },
{ username: 'sporiff', name: 'Ciarán Ainsworth' },
{ username: 'tekuteku', name: 'tekuteku' },
{ username: 'thecatjustmeow', name: 'Nguyen Huynh Hung' },
{ username: 'tmota', name: 'Tiago Mota' },
{ username: 'uranix', name: 'Michal Mauser' },
{ username: 'wakutiteo', name: 'Markel' },
{ username: 'wonderingdane', name: 'Nicolai Ireneo-Larsen' },
{ username: 'zeynepeliacik', name: 'Zeynep Can' }
]
}
function getContributorsExcludeList () {
return new Set([
'Bigard Florian',
'chocobozzz',
'Rigel',
// Requested by the contributor
'Marcel Cramm',
'Chris Sakura 佐倉くりす on Youtube',
'Chris Sakura 佐倉くりす on Youtube - 日本語は第二言語やけ、間違っとったら思いっきり叩いてくださいw つたない日本語ばっかりやけど頑張りまーす♪',
'chris@famichiki.tube'
])
}

View File

@@ -0,0 +1,179 @@
import { USER_ROLE_LABELS } from '@peertube/peertube-core-utils'
import { root } from '@peertube/peertube-node-utils'
import {
ABUSE_STATES,
buildLanguages,
RUNNER_JOB_STATES,
USER_EXPORT_STATES,
USER_IMPORT_STATES,
USER_REGISTRATION_STATES,
VIDEO_CATEGORIES,
VIDEO_CHANNEL_SYNC_STATE,
VIDEO_IMPORT_STATES,
VIDEO_LICENCES,
VIDEO_PLAYLIST_PRIVACIES,
VIDEO_PLAYLIST_TYPES,
VIDEO_PRIVACIES,
VIDEO_STATES
} from '../../server/core/initializers/constants.js'
import { readJsonSync, writeJSON } from 'fs-extra/esm'
import { readdir } from 'fs/promises'
import { join } from 'path'
const videojs = readJsonSync(join(root(), 'client', 'src', 'locale', 'videojs.en-US.json'))
const playerKeys = {
'Quality': 'Quality',
'Auto': 'Auto',
'Speed': 'Speed',
'Subtitles/CC': 'Subtitles/CC',
'Peers': 'Peers',
'peers': 'peers',
'peer': 'peer',
'no peers': 'no peers',
'Go to the video page': 'Go to the video page',
'Settings': 'Settings',
'Watching this video may reveal your IP address to others.': 'Watching this video may reveal your IP address to others.',
'Copy the video URL': 'Copy the video URL',
'Copy the video URL at the current time': 'Copy the video URL at the current time',
'Copy embed code': 'Copy embed code',
'Copy magnet URI': 'Copy magnet URI',
'Total downloaded: ': 'Total downloaded: ',
'Total uploaded: ': 'Total uploaded: ',
'From servers: ': 'From servers: ',
'From peers: ': 'From peers: ',
'Normal mode': 'Normal mode',
'Stats for nerds': 'Stats for nerds',
'Theater mode': 'Theater mode',
'Video UUID': 'Video UUID',
'Viewport / Frames': 'Viewport / Frames',
'Resolution': 'Resolution',
'Volume': 'Volume',
'Codecs': 'Codecs',
'Color': 'Color',
'Go back to the live': 'Go back to the live',
'Connection Speed': 'Connection Speed',
'Network Activity': 'Network Activity',
'Total Transfered': 'Total Transfered',
'Download Breakdown': 'Download Breakdown',
'Buffer Progress': 'Buffer Progress',
'Buffer State': 'Buffer State',
'Live Latency': 'Live Latency',
'P2P': 'P2P',
'{1} seconds': '{1} seconds',
'enabled': 'enabled',
'Playlist: {1}': 'Playlist: {1}',
'disabled': 'disabled',
' off': ' off',
'Player mode': 'Player mode',
'Play in loop': 'Play in loop',
'This live is not currently streaming.': 'This live is not currently streaming.',
'This live has ended.': 'This live has ended.',
'The video failed to play, will try to fast forward.': 'The video failed to play, will try to fast forward.',
'{1} / {2} dropped of {3}': '{1} / {2} dropped of {3}',
' (muted)': ' (muted)',
'{1} from servers · {2} from peers': '{1} from servers · {2} from peers',
'Previous video': 'Previous video',
'Video page (new window)': 'Video page (new window)',
'Next video': 'Next video',
'This video is password protected': 'This video is password protected',
'You need a password to watch this video.': 'You need a password to watch this video.',
'Incorrect password, please enter a correct password': 'Incorrect password, please enter a correct password',
'Cancel': 'Cancel',
'Up Next': 'Up Next',
'Autoplay is suspended': 'Autoplay is suspended',
'{1} (from edge: {2})': '{1} (from edge: {2})',
'Disable subtitles': 'Disable subtitles',
'Enable {1} subtitle': 'Enable {1} subtitle',
'{1} (auto-generated)': '{1} (auto-generated)',
'Go back': 'Go back',
'Audio only': 'Audio only',
'Sensitive content': 'Sensitive content',
'This video contains sensitive content.': 'This video contains sensitive content.',
'This video contains sensitive content, including:': 'This video contains sensitive content, including:',
'Learn more': 'Learn more',
'Content warning': 'Content warning',
'Violence': 'Violence',
'Shocking Content': 'Shocking Content',
'Explicit Sex': 'Explicit Sex',
'Upload speed:': 'Upload speed:',
'Download speed:': 'Download speed:',
'Uploader note:': 'Uploader note:',
'Close': 'Close',
'(skipped {1} buffers) ': '(skipped {1} buffers) '
}
Object.assign(playerKeys, videojs)
// Server keys
const serverKeys: any = {}
Object.values(VIDEO_CATEGORIES)
.concat(Object.values(VIDEO_LICENCES))
.concat(Object.values(VIDEO_PRIVACIES))
.concat(Object.values(VIDEO_STATES))
.concat(Object.values(VIDEO_IMPORT_STATES))
.concat(Object.values(VIDEO_PLAYLIST_PRIVACIES))
.concat(Object.values(VIDEO_PLAYLIST_TYPES))
.concat(Object.values(USER_ROLE_LABELS))
.concat(Object.values(VIDEO_CHANNEL_SYNC_STATE))
.concat(Object.values(ABUSE_STATES))
.concat(Object.values(USER_REGISTRATION_STATES))
.concat(Object.values(RUNNER_JOB_STATES))
.concat(Object.values(USER_EXPORT_STATES))
.concat(Object.values(USER_IMPORT_STATES))
.concat([
'This video does not exist.',
'We cannot fetch the video. Please try again later.',
'Sorry',
'This video is not available because the remote instance is not responding.',
'This playlist does not exist',
'We cannot fetch the playlist. Please try again later.',
'Playlist: {1}',
'By {1}',
'Unavailable video'
])
.forEach(v => {
serverKeys[v] = v
})
// More keys
Object.assign(serverKeys, {
Unknown: 'Unknown'
})
// ISO 639 keys
const languageKeys: any = {}
const languages = buildLanguages()
Object.keys(languages).forEach(k => {
languageKeys[languages[k]] = languages[k]
})
Object.assign(serverKeys, languageKeys)
writeAll().catch(err => {
console.error(err)
process.exit(-1)
})
async function writeAll () {
const localePath = join(root(), 'client', 'src', 'locale')
await writeJSON(join(localePath, 'player.en-US.json'), playerKeys, { spaces: 4 })
await writeJSON(join(localePath, 'server.en-US.json'), serverKeys, { spaces: 4 })
for (const file of await readdir(localePath)) {
if (file.match(/^player\.[^.]+\.json$/)) {
const playerJsonPath = join(localePath, file)
const translatedPlayer = readJsonSync(playerJsonPath)
const newTranslatedPlayer = Object.assign({}, playerKeys, translatedPlayer)
await writeJSON(playerJsonPath, newTranslatedPlayer, { spaces: 4 })
}
if (file.match(/^server\.[^.]+\.json$/)) {
const serverJsonPath = join(localePath, file)
const translatedServer = readJsonSync(serverJsonPath)
const newTranslatedServer = Object.assign({}, serverKeys, translatedServer)
await writeJSON(serverJsonPath, newTranslatedServer, { spaces: 4 })
}
}
}

31
scripts/i18n/update.sh Normal file
View File

@@ -0,0 +1,31 @@
#!/bin/sh
set -eu
git fetch weblate && git merge weblate/develop
npm run build:embed
cd client
npm run ng -- extract-i18n --out-file src/locale/angular.xlf
locales=$(find src/locale -type f | grep -e 'angular\.[^.]\+\.xlf' | sed 's#^src/locale/angular.##' | sed 's/\.xlf$//' | sort -u | tr '\n' ' ')
# Merge new translations in other language files
./node_modules/.bin/xliffmerge -p ./.xliffmerge.json $locales
(
cd src/locale
for file in angular.*.xlf; do
xmllint --format $file > "$file.tmp" && mv "$file.tmp" "$file"
done
)
# Add our strings too
cd ../
npm run i18n:create-custom-files
# Generate server translations
./node_modules/.bin/i18next -c server/.i18next-parser.config.ts 'server/core/**/*.{ts,hbs}'

63
scripts/nightly.sh Normal file
View File

@@ -0,0 +1,63 @@
#!/bin/bash
set -eu
shutdown() {
# Get our process group id
# shellcheck disable=SC2009
PGID=$(ps -o pgid= $$ | grep -o "[0-9]*")
# Kill it in a new new process group
setsid kill -- -"$PGID"
exit 0
}
trap "shutdown" SIGINT SIGTERM
today=$(date '+%F')
directory_name="peertube-nightly-$today"
tar_name="peertube-nightly-$today.tar.xz"
peertube_directory=$(basename $(pwd))
npm run build -- --source-map
# Clean up declaration files
find dist/ packages/core-utils/dist/ \
packages/ffmpeg/dist/ \
packages/node-utils/dist/ \
packages/models/dist/ \
\( -name '*.d.ts' -o -name '*.d.ts.map' \) -type f -delete
nightly_version="nightly-$today"
sed -i 's/"version": "\([^"]\+\)"/"version": "\1-'"$nightly_version"'"/' ./package.json
# Creating the archives
(
# local variables
directories_to_archive=("$directory_name/CREDITS.md" "$directory_name/FAQ.md" \
"$directory_name/LICENSE" "$directory_name/README.md" \
"$directory_name/packages/core-utils/dist/" "$directory_name/packages/core-utils/package.json" \
"$directory_name/packages/ffmpeg/dist/" "$directory_name/packages/ffmpeg/package.json" \
"$directory_name/packages/node-utils/dist/" "$directory_name/packages/node-utils/package.json" \
"$directory_name/packages/models/dist/" "$directory_name/packages/models/package.json" \
"$directory_name/packages/transcription/dist/" "$directory_name/packages/transcription/package.json" \
"$directory_name/client/dist/" \
"$directory_name/client/package.json" "$directory_name/config" \
"$directory_name/dist" "$directory_name/package.json" \
"$directory_name/scripts/upgrade.sh" "$directory_name/support/doc" "$directory_name/support/freebsd" \
"$directory_name/support/init.d" "$directory_name/support/nginx" "$directory_name/support/openapi" \
"$directory_name/support/sysctl.d" "$directory_name/support/conf.d" "$directory_name/support/systemd" \
"$directory_name/support/init.d" \
"$directory_name/pnpm-patches" "$directory_name/pnpm-lock.yaml" "$directory_name/pnpm-workspace.yaml")
# temporary setup
cd ..
ln -s "$peertube_directory" "$directory_name"
XZ_OPT=-e9 tar cfJ "$peertube_directory/$tar_name" "${directories_to_archive[@]}"
# temporary setup destruction
rm "$directory_name"
)
git checkout -- ./package.json

View File

@@ -0,0 +1,85 @@
#!/bin/bash
# Required environment vars
# =========================
# API_LANGS
# A ':' delimited list of the client lib languages to be generated
# API_GIT_USER
# The user that will be used to push/pull from the APIs repos
# API_GIT_EMAIL
# The git email
# GIT_TOKEN
# A personal access token for github or gilab for pushing to repos
# !!!This is a secret and shouldn't be logged publicly!!!
# (Optional environment vars)
# ===========================
# API_COMMIT_MSG
# A message to use when committing to the lib repo
# API_PATH_PREFIX
# Will be used for building the URL to the repo and path to checkout.
# !!! End with a slash "/", otherwise the prefix will be tacked onto the language
# API_URL_USERNAME
# The username to use building the URL to the git repo.
# Default: API_GIT_USER
# API_REPO_HOST
# Whoever's hosting the repo e.g gitlab.com, github.com, etc.
# Default: framagit.org
# Unofficial bash strict mode
# https://web.archive.org/web/20190115051613/https://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail
IFS=$'\n\t '
# Set default values
API_URL_USERNAME="${API_URL_USERNAME:-$API_GIT_USER}"
API_PATH_PREFIX="${API_PATH_PREFIX:-}"
API_REPO_HOST=${API_REPO_HOST:-framagit.org}
echo "API_GIT_USER='${API_GIT_USER}'"
echo "API_URL_USERNAME='${API_URL_USERNAME}'"
echo "API_LANGS='${API_LANGS}'"
git config --global user.email "${API_GIT_EMAIL}"
git config --global user.name "${API_GIT_USER}"
for lang in ${API_LANGS//:/ } ; do
(
echo "Generating client API libs for $lang"
lang_dir="support/openapi/${lang}"
out_dir_prefix="dist/api/${API_PATH_PREFIX}"
out_dir="${out_dir_prefix}/${lang}"
git_repo_id="${API_PATH_PREFIX}${lang}"
host_path="${API_REPO_HOST}/${API_URL_USERNAME}/${git_repo_id}.git"
git_remote="https://${API_GIT_USER}:${GIT_TOKEN}@${host_path}"
if ! [ -e "$out_dir" ] ; then
# Make sure the prefix exists before cloning the repo
mkdir -p "${out_dir_prefix}"
git clone "https://${host_path}" "$out_dir"
fi
npx @openapitools/openapi-generator-cli generate \
-i support/doc/api/openapi.yaml \
-c "${lang_dir}/def.yaml" \
-t "${lang_dir}" \
-g "$lang" \
--skip-validate-spec \
--git-host "${API_REPO_HOST}" \
--git-user-id "${API_URL_USERNAME}" \
--git-repo-id "${git_repo_id}" \
-o "${out_dir}"
# Commit and push changes to the remote
cd "$out_dir"
git remote set-url origin "$git_remote"
# Make sure something has changed
if [[ $(git status -s | wc -l) = 0 ]] ; then
echo "No changes from previous version"
continue
fi
git add .
git commit -m "${API_COMMIT_MSG:-"Minor update $lang"}"
git push
)
done

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Version key/value should be on his own line
PACKAGE_VERSION=$(node -p "require('./package.json').version")
sed -i "s/\(^\s*\)version: .*/\1version: $PACKAGE_VERSION/" ./support/doc/api/openapi.yaml
# Sets the package version for libs
echo "packageVersion: $PACKAGE_VERSION" >> ./support/openapi/go.yaml
echo "artifactVersion: $PACKAGE_VERSION" >> ./support/openapi/kotlin.yaml
echo "packageVersion: $PACKAGE_VERSION" >> ./support/openapi/python.yaml

View File

@@ -0,0 +1,10 @@
#!/bin/sh
set -eu
cd client/src/standalone/embed-player-api
npm run build
npm publish --access public
rm -rf dist build node_modules

153
scripts/release.sh Normal file
View File

@@ -0,0 +1,153 @@
#!/bin/bash
set -eu
shutdown() {
# Get our process group id
# shellcheck disable=SC2009
PGID=$(ps -o pgid= $$ | grep -o "[0-9]*")
# Kill it in a new new process group
setsid kill -- -"$PGID"
exit 0
}
trap "shutdown" SIGINT SIGTERM
if [ -z "$1" ]; then
echo "Need version as argument"
exit -1
fi
if [ -z "$GITHUB_TOKEN" ]; then
echo "Need GITHUB_TOKEN env set."
exit -1
fi
maintainer_public_key=${MAINTAINER_GPG:-"583A612D890159BE"}
peertube_directory=$(basename $(pwd))
branch=$(git symbolic-ref --short -q HEAD)
if [ "$branch" != "develop" ] && [[ "$branch" != release/* ]]; then
echo "Need to be on develop or release branch."
exit -1
fi
pnpm install --frozen-lockfile
version="v$1"
github_prerelease_option=""
if [[ "$version" = *"-alpha."* ]] || [[ "$version" = *"-beta."* ]] || [[ "$version" = *"-rc."* ]]; then
echo -e "This is a pre-release.\n"
github_prerelease_option="--pre-release"
fi
directory_name="peertube-$version"
zip_name="peertube-$version.zip"
tar_name="peertube-$version.tar.xz"
changelog=$(awk -v version="$version" '/## v/ { printit = $2 == version }; printit;' CHANGELOG.md | grep -v "## $version" | sed '1{/^$/d}')
printf "Changelog will be:\\n\\n%s\\n\\n" "$changelog"
read -p "Are you sure to release? " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 0
fi
(
cd client
npm version --no-git-tag-version --no-commit-hooks "$1"
)
npm version -f --no-git-tag-version --no-commit-hooks "$1"
git commit package.json client/package.json ./support/doc/api/openapi.yaml -m "Bumped to version $version"
git tag -s -a "$version" -m "$version"
npm run build -- --source-map
rm -f "./client/dist/en-US/stats.json"
rm -f "./client/dist/embed-stats.json"
# Clean up declaration files
find dist/ packages/core-utils/dist/ \
packages/ffmpeg/dist/ \
packages/node-utils/dist/ \
packages/models/dist/ \
\( -name '*.d.ts' -o -name '*.d.ts.map' \) -type f -delete
# Creating the archives
(
# local variables
directories_to_archive=("$directory_name/CREDITS.md" "$directory_name/FAQ.md" \
"$directory_name/LICENSE" "$directory_name/README.md" \
"$directory_name/packages/core-utils/dist/" "$directory_name/packages/core-utils/package.json" \
"$directory_name/packages/ffmpeg/dist/" "$directory_name/packages/ffmpeg/package.json" \
"$directory_name/packages/node-utils/dist/" "$directory_name/packages/node-utils/package.json" \
"$directory_name/packages/models/dist/" "$directory_name/packages/models/package.json" \
"$directory_name/packages/transcription/dist/" "$directory_name/packages/transcription/package.json" \
"$directory_name/client/dist/" \
"$directory_name/client/package.json" "$directory_name/config" \
"$directory_name/dist" "$directory_name/package.json" \
"$directory_name/scripts/upgrade.sh" "$directory_name/support/doc" "$directory_name/support/freebsd" \
"$directory_name/support/init.d" "$directory_name/support/nginx" "$directory_name/support/openapi" \
"$directory_name/support/sysctl.d" "$directory_name/support/conf.d" "$directory_name/support/systemd" \
"$directory_name/pnpm-patches" "$directory_name/pnpm-lock.yaml" "$directory_name/pnpm-workspace.yaml")
# temporary setup
cd ..
ln -s "$peertube_directory" "$directory_name"
# archive creation + signing
zip -9 -r "$peertube_directory/$zip_name" "${directories_to_archive[@]}"
gpg --armor --detach-sign -u "$maintainer_public_key" "$peertube_directory/$zip_name"
XZ_OPT="-e9 -T0" tar cfJ "$peertube_directory/$tar_name" "${directories_to_archive[@]}"
gpg --armor --detach-sign -u "$maintainer_public_key" "$peertube_directory/$tar_name"
# temporary setup destruction
rm "$directory_name"
)
# Creating the release on GitHub, with the created archives
(
git push origin --tag
if [ -z "$github_prerelease_option" ]; then
github-release release --user chocobozzz --repo peertube --tag "$version" --name "$version" --description "$changelog"
else
github-release release --user chocobozzz --repo peertube --tag "$version" --name "$version" --description "$changelog" "$github_prerelease_option"
fi
# Wait for the release to be published, we had some issues when the files were not uploaded because of "unknown release" error
sleep 2
github-release upload --user chocobozzz --repo peertube --tag "$version" --name "$zip_name" --file "$zip_name"
github-release upload --user chocobozzz --repo peertube --tag "$version" --name "$zip_name.asc" --file "$zip_name.asc"
github-release upload --user chocobozzz --repo peertube --tag "$version" --name "$tar_name" --file "$tar_name"
github-release upload --user chocobozzz --repo peertube --tag "$version" --name "$tar_name.asc" --file "$tar_name.asc"
git push origin "$branch"
# Only update master if it is not a pre release
if [ -z "$github_prerelease_option" ]; then
# Update master branch
git checkout master
git merge "$branch"
git push origin master
git checkout "$branch"
# Rebuild properly the server, with the declaration files
npm run build:server
# Release types package
(
npm login
npm run generate-types-package "$version"
cd packages/types-generator/dist && npm publish --access public
)
scp "$tar_name" "$tar_name.asc" framasoft_builds_joinpeertube@builds.joinpeertube.org:/var/www/clients/client1/web30/web/release
fi
)

View File

@@ -0,0 +1,9 @@
import Bluebird from "bluebird";
import { PeerTubeServer } from "@peertube/peertube-server-commands";
module.exports = async function sendViews(options) {
const { url, videoId, viewers } = options;
const server = new PeerTubeServer({ url });
await Bluebird.map(viewers, (viewer) => {
return server.views.simulateView({ id: videoId, xForwardedFor: viewer.xForwardedFor }).catch((err) => console.error("Cannot simulate viewer", err));
}, { concurrency: 500 });
};

View File

@@ -0,0 +1,17 @@
import Bluebird from 'bluebird'
import { PeerTubeServer } from '@peertube/peertube-server-commands'
module.exports = async function sendViews (options: {
url: string
videoId: number
viewers: { xForwardedFor: string }[]
}) {
const { url, videoId, viewers } = options
const server = new PeerTubeServer({ url })
await Bluebird.map(viewers, viewer => {
return server.views.simulateView({ id: videoId, xForwardedFor: viewer.xForwardedFor })
.catch(err => console.error('Cannot simulate viewer', err))
}, { concurrency: 500 })
}

View File

@@ -0,0 +1,116 @@
import { wait } from '@peertube/peertube-core-utils'
import {
createSingleServer,
doubleFollow,
killallServers,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
import { dirname, join } from 'path'
import { Piscina } from 'piscina'
import { fileURLToPath } from 'url'
import { isMainThread } from 'worker_threads'
const THOUSAND_VIEWERS = 2
const TOTAL_THREADS = 20
let servers: PeerTubeServer[]
const viewers: { xForwardedFor: string }[] = []
let videoId: string
let pool: Piscina
if (isMainThread) {
run()
.then(() => process.exit(0))
.catch(err => console.error(err))
.finally(() => killallServers(servers))
}
// ---------------------------------------------------------------------------
async function run () {
await prepare()
while (true) {
await runViewers()
}
}
async function prepare () {
pool = new Piscina({
filename: join(dirname(fileURLToPath(import.meta.url)), 'simulate-many-viewers-worker.js'),
minThreads: 20,
maxThreads: 20
})
console.log('Preparing servers...')
const config = {
log: {
level: 'info',
log_http_requests: false
},
rates_limit: {
api: {
max: 5_000_000
}
},
views: {
videos: {
local_buffer_update_interval: '30 minutes',
view_expiration: '1 hour'
}
}
}
const env = { PRODUCTION_CONSTANTS: 'true' }
servers = await Promise.all([
createSingleServer(1, config, { env, nodeArgs: [ '--inspect' ] }),
createSingleServer(2, config, { env }),
createSingleServer(3, config, { env })
])
await setAccessTokensToServers(servers)
await doubleFollow(servers[0], servers[1])
await doubleFollow(servers[0], servers[2])
const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
videoId = uuid
await waitJobs(servers)
for (let i = 2; i < 252; i++) {
for (let j = 2; j < 6; j++) {
for (let k = 2; k < THOUSAND_VIEWERS + 2; k++) {
viewers.push({ xForwardedFor: `0.${k}.${j}.${i},127.0.0.1` })
}
}
}
console.log('Servers preparation finished.')
}
async function runViewers () {
console.log('Will run views of %d viewers.', viewers.length)
const before = new Date().getTime()
const promises: Promise<any>[] = []
for (let i = 0; i < TOTAL_THREADS; i++) {
const start = i * THOUSAND_VIEWERS * 1000 / TOTAL_THREADS
const end = (i + 1) * THOUSAND_VIEWERS * 1000 / TOTAL_THREADS
console.log(`Sending viewers ${start} to ${end}`)
promises.push(pool.run({ url: servers[0].url, viewers: viewers.slice(start, end), videoId }))
}
await Promise.all(promises)
console.log('Finished to run views in %d seconds.', (new Date().getTime() - before) / 1000)
await wait(5000)
}

15
scripts/test.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/bin/sh
set -eu
npm run ci -- types
npm run ci -- client
npm run ci -- cli-plugin
npm run ci -- api-1
npm run ci -- api-2
npm run ci -- api-3
npm run ci -- api-4
npm run ci -- api-5
npm run ci -- external-plugins
npm run ci -- lint

16
scripts/tsconfig.json Normal file
View File

@@ -0,0 +1,16 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "../dist/scripts",
"paths": {
"@server/*": [ "../server/core/*" ]
}
},
"references": [
{ "path": "../packages/core-utils" },
{ "path": "../packages/models" },
{ "path": "../packages/node-utils" },
{ "path": "../packages/server-commands" },
{ "path": "../server" }
]
}

8
scripts/upgrade.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
set -eu
# Backward path compatibility now upgrade.sh is in dist/scripts since v6
/bin/sh ../dist/scripts/upgrade.sh ${1:-/var/www/peertube}