stages:
- build-and-lint
- test
- - nightly
+ - docker-nightly
-before_script:
- - 'sed -i -z "s/database:\n hostname: ''localhost''/database:\n hostname: ''postgres''/" config/test.yaml'
- - 'sed -i -z "s/redis:\n hostname: ''localhost''/redis:\n hostname: ''redis''/" config/test.yaml'
- - if [[ $CI_JOB_STAGE == "test" ]]; then psql -c "create user peertube with password 'peertube';"; fi
- - NOCLIENT=1 yarn install --pure-lockfile --cache-folder .yarn-cache
+#before_script:
+# - 'sed -i -z "s/database:\n hostname: ''localhost''/database:\n hostname: ''postgres''/" config/test.yaml'
+# - 'sed -i -z "s/redis:\n hostname: ''localhost''/redis:\n hostname: ''redis''/" config/test.yaml'
+# - if [[ $CI_JOB_STAGE == "test" ]]; then psql -c "create user peertube with password 'peertube';"; fi
+# - NOCLIENT=1 yarn install --pure-lockfile --cache-folder .yarn-cache
cache:
key: yarn
# - NODE_PENDING_JOB_WAIT=1000 npm run ci -- api-$CI_NODE_INDEX
build-nightly:
- stage: nightly
+ stage: docker-nightly
only:
- schedules
script:
- if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then ssh-add <(echo "${DEPLOYEMENT_KEY}"); fi
- if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then scp ./peertube-nightly-* ${DEPLOYEMENT_USER}@${DEPLOYEMENT_HOST}:../../web/nightly; fi
+.docker: &docker
+ stage: docker-nightly
+ image:
+ name: gcr.io/kaniko-project/executor:debug
+ entrypoint: [""]
+ before_script:
+ - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$CI_REGISTRY_AUTH\",\"email\":\"$CI_REGISTRY_EMAIL\"}}}" > /kaniko/.docker/config.json
+ script:
+ - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/support/docker/production/Dockerfile.stretch --destination $DOCKER_IMAGE_NAME
+
+build-docker-develop:
+ <<: *docker
+ only:
+ - schedules
+ variables:
+ DOCKER_IMAGE_NAME: chocobozzz/peertube:develop-stretch
+
+build-docker-tag:
+ <<: *docker
+ only:
+ - tags
+ variables:
+ DOCKER_IMAGE_NAME: chocobozzz/peertube:$CI_COMMIT_TAG-stretch
See our REST API documentation:
* OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml)
- * Spec explorer: [docs.joinpeertube.org/#/api-rest-reference.html](https://docs.joinpeertube.org/#/api-rest-reference.html)
+ * Spec explorer: [docs.joinpeertube.org/api-rest-reference.html](https://docs.joinpeertube.org/api-rest-reference.html)
See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub).
new Hotkey('g o', (event: KeyboardEvent): boolean => {
this.router.navigate([ '/videos/overview' ])
return false
- }, undefined, this.i18n('Go to the videos overview page')),
+ }, undefined, this.i18n('Go to the discover videos page')),
new Hotkey('g t', (event: KeyboardEvent): boolean => {
this.router.navigate([ '/videos/trending' ])
return false
import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
import { RegisterClientHelpers } from '../../../types/register-client-option.model'
import { PluginTranslation } from '@shared/models/plugins/plugin-translation.model'
+import { importModule } from '@app/shared/misc/utils'
interface HookStructValue extends RegisterClientHookOptions {
plugin: ServerConfigPlugin
console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
return this.zone.runOutsideAngular(() => {
- return import(/* webpackIgnore: true */ clientScript.script)
+ return importModule(clientScript.script)
.then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers }))
.then(() => this.sortHooksByPriority())
.catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
window.scroll(0, 0)
}
+// Thanks: https://github.com/uupaa/dynamic-import-polyfill
+function importModule (path: string) {
+ return new Promise((resolve, reject) => {
+ const vector = '$importModule$' + Math.random().toString(32).slice(2)
+ const script = document.createElement('script')
+
+ const destructor = () => {
+ delete window[ vector ]
+ script.onerror = null
+ script.onload = null
+ script.remove()
+ URL.revokeObjectURL(script.src)
+ script.src = ''
+ }
+
+ script.defer = true
+ script.type = 'module'
+
+ script.onerror = () => {
+ reject(new Error(`Failed to import: ${path}`))
+ destructor()
+ }
+ script.onload = () => {
+ resolve(window[ vector ])
+ destructor()
+ }
+ const absURL = (environment.apiUrl || window.location.origin) + path
+ const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module
+ const blob = new Blob([ loader ], { type: 'text/javascript' })
+ script.src = URL.createObjectURL(blob)
+
+ document.head.appendChild(script)
+ })
+}
+
export {
sortBy,
durationToString,
objectToFormData,
objectLineFeedToHtml,
removeElementFromArray,
+ importModule,
scrollToTop
}
component: VideoOverviewComponent,
data: {
meta: {
- title: 'Videos overview'
+ title: 'Discover videos'
}
}
},
$icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
-@import '_bootstrap';
@import '_variables';
@import '_mixins';
$progress-margin: 10px;
$assets-path: '../../assets/' !default;
-
-body {
- --embedForegroundColor: #{$primary-foreground-color};
-
- --embedBigPlayBackgroundColor: #{$primary-background-color};
-}
@import '_mixins';
@import './_player-variables';
+body {
+ --embedForegroundColor: #{$primary-foreground-color};
+
+ --embedBigPlayBackgroundColor: #{$primary-background-color};
+}
+
@mixin big-play-button-triangle-size($triangle-size) {
width: $triangle-size;
height: $triangle-size;
email:
body:
signature: "PeerTube"
- object:
+ subject:
prefix: "[PeerTube]"
# From the project root directory
email:
body:
signature: "PeerTube"
- object:
+ subject:
prefix: "[PeerTube]"
# From the project root directory
}
function isPluginHomepage (value: string) {
- return isUrlValid(value)
+ return exists(value) && (!value || isUrlValid(value))
+}
+
+function isPluginBugs (value: string) {
+ return exists(value) && (!value || isUrlValid(value))
}
function areStaticDirectoriesValid (staticDirs: any) {
isPluginEngineValid(packageJSON.engine) &&
isPluginHomepage(packageJSON.homepage) &&
exists(packageJSON.author) &&
- isUrlValid(packageJSON.bugs) &&
+ isPluginBugs(packageJSON.bugs) &&
(pluginType === PluginType.THEME || isSafePath(packageJSON.library)) &&
areStaticDirectoriesValid(packageJSON.staticDirs) &&
areCSSPathsValid(packageJSON.css) &&
'trust_proxy',
'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max',
'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
- 'email.body.signature', 'email.object.prefix',
+ 'email.body.signature', 'email.subject.prefix',
'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
'storage.redundancy', 'storage.tmp', 'storage.streaming_playlists', 'storage.plugins',
'log.level',
BODY: {
SIGNATURE: config.get<string>('email.body.signature')
},
- OBJECT: {
- PREFIX: config.get<string>('email.object.prefix') + ' '
+ SUBJECT: {
+ PREFIX: config.get<string>('email.subject.prefix') + ' '
}
},
STORAGE: {
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + channelName + ' just published a new video',
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + channelName + ' just published a new video',
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New follower on your channel ' + followingName,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New follower on your channel ' + followingName,
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New instance follower',
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New instance follower',
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video ${video.name} is published`,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video ${video.name} is published`,
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`,
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New comment on your video ' + video.name,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New comment on your video ' + video.name,
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Mention on video ' + video.name,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Mention on video ' + video.name,
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Received a video abuse',
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Received a video abuse',
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + 'An auto-blacklisted video is awaiting review',
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'An auto-blacklisted video is awaiting review',
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST,
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + `Video ${videoName} blacklisted`,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${videoName} blacklisted`,
text
}
const emailPayload: EmailPayload = {
to,
- subject: CONFIG.EMAIL.OBJECT.PREFIX + `Video ${video.name} unblacklisted`,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${video.name} unblacklisted`,
text
}
const emailPayload: EmailPayload = {
to: [ to ],
- subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Reset your password',
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Reset your password',
text
}
const emailPayload: EmailPayload = {
to: [ to ],
- subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Verify your email',
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Verify your email',
text
}
const to = user.email
const emailPayload: EmailPayload = {
to: [ to ],
- subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Account ' + blockedWord,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Account ' + blockedWord,
text
}
fromDisplayName: fromEmail,
replyTo: fromEmail,
to: [ CONFIG.ADMIN.EMAIL ],
- subject: CONFIG.EMAIL.OBJECT.PREFIX + subject,
+ subject: CONFIG.EMAIL.SUBJECT.PREFIX + subject,
text
}
private static instance: PeerTubeSocket
- private userNotificationSockets: { [ userId: number ]: SocketIO.Socket } = {}
+ private userNotificationSockets: { [ userId: number ]: SocketIO.Socket[] } = {}
private constructor () {}
logger.debug('User %d connected on the notification system.', userId)
- this.userNotificationSockets[userId] = socket
+ if (!this.userNotificationSockets[userId]) this.userNotificationSockets[userId] = []
+
+ this.userNotificationSockets[userId].push(socket)
socket.on('disconnect', () => {
logger.debug('User %d disconnected from SocketIO notifications.', userId)
- delete this.userNotificationSockets[userId]
+ this.userNotificationSockets[userId] = this.userNotificationSockets[userId].filter(s => s !== socket)
})
})
}
sendNotification (userId: number, notification: UserNotificationModelForApi) {
- const socket = this.userNotificationSockets[userId]
+ const sockets = this.userNotificationSockets[userId]
- if (!socket) return
+ if (!sockets) return
- socket.emit('new-notification', notification.toFormattedJSON())
+ for (const socket of sockets) {
+ socket.emit('new-notification', notification.toFormattedJSON())
+ }
}
static get Instance () {
type: string
format: binary
encoding:
- profileImage:
- # only accept png/jpeg
+ avatarfile:
contentType: image/png, image/jpeg
/videos:
get:
thumbnailfile:
description: Video thumbnail file
type: string
+ format: binary
previewfile:
description: Video preview file
type: string
+ format: binary
category:
description: Video category
type: string
format: date-time
scheduleUpdate:
$ref: '#/components/schemas/VideoScheduledUpdate'
+ encoding:
+ thumbnailfile:
+ contentType: image/jpeg
+ previewfile:
+ contentType: image/jpeg
get:
summary: Get a video by its id
tags:
thumbnailfile:
description: Video thumbnail file
type: string
+ format: binary
previewfile:
description: Video preview file
type: string
+ format: binary
privacy:
$ref: '#/components/schemas/VideoPrivacySet'
category:
- videofile
- channelId
- name
+ encoding:
+ videofile:
+ contentType: video/mp4, video/webm, video/ogg, video/avi, video/quicktime, video/x-msvideo, video/x-flv, video/x-matroska, application/octet-stream
+ thumbnailfile:
+ contentType: image/jpeg
+ previewfile:
+ contentType: image/jpeg
x-code-samples:
- lang: Shell
source: |
thumbnailfile:
description: Video thumbnail file
type: string
+ format: binary
previewfile:
description: Video preview file
type: string
+ format: binary
privacy:
$ref: '#/components/schemas/VideoPrivacySet'
category:
required:
- channelId
- name
+ encoding:
+ torrentfile:
+ contentType: application/x-bittorrent
+ thumbnailfile:
+ contentType: image/jpeg
+ previewfile:
+ contentType: image/jpeg
/videos/abuse:
get:
summary: Get list of reported video abuses
description: The file to upload.
type: string
format: binary
+ encoding:
+ captionfile:
+ contentType: text/vtt, application/x-subrip
responses:
'204':
$ref: '#/paths/~1users~1me/put/responses/204'
description: 'Video file size in bytes'
torrentUrl:
type: string
- torrentDownaloadUrl:
+ torrentDownloadUrl:
type: string
fileUrl:
type: string
- [peertube-import-videos.js](#peertube-import-videosjs)
- [peertube-upload.js](#peertube-uploadjs)
- [peertube-watch.js](#peertube-watchjs)
+ - [peertube-plugins.js](#peertube-pluginsjs)
- [Server tools](#server-tools)
- [parse-log](#parse-log)
- [create-transcoding-job.js](#create-transcoding-jobjs)
- [optimize-old-videos.js](#optimize-old-videosjs)
- [update-host.js](#update-hostjs)
- [reset-password.js](#reset-passwordjs)
+ - [plugin install/uninstall](#plugin-installuninstall)
- [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop)
- [.help](#help)
- [Lodash example](#lodash-example)
- chromecast
+#### peertube-plugins.js
+
+Install/update/uninstall or list local or NPM PeerTube plugins:
+
+```
+$ cd ${CLONE}
+$ node dist/server/tools/peertube-plugins.js --help
+$ node dist/server/tools/peertube-plugins.js list --help
+$ node dist/server/tools/peertube-plugins.js install --help
+$ node dist/server/tools/peertube-plugins.js update --help
+$ node dist/server/tools/peertube-plugins.js uninstall --help
+
+$ node dist/server/tools/peertube-plugins.js install --path /my/plugin/path
+$ node dist/server/tools/peertube-plugins.js install --npm-name peertube-theme-example
+```
+
## Server tools
These scripts should be run on the server, in `peertube-latest` directory.
The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running.
If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server).
-To install a plugin or a theme from the disk:
+To install/update a plugin or a theme from the disk:
```
$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --plugin-path /local/plugin/path