1 import config from 'config'
2 import { URL } from 'url'
3 import { getFFmpegVersion } from '@server/helpers/ffmpeg'
4 import { uniqify } from '@shared/core-utils'
5 import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type'
6 import { RecentlyAddedStrategy } from '../../shared/models/redundancy'
7 import { isProdInstance, parseSemVersion } from '../helpers/core-utils'
8 import { isArray } from '../helpers/custom-validators/misc'
9 import { logger } from '../helpers/logger'
10 import { ApplicationModel, getServerActor } from '../models/application/application'
11 import { OAuthClientModel } from '../models/oauth/oauth-client'
12 import { UserModel } from '../models/user/user'
13 import { CONFIG, isEmailEnabled } from './config'
14 import { WEBSERVER } from './constants'
16 async function checkActivityPubUrls () {
17 const actor = await getServerActor()
19 const parsed = new URL(actor.url)
20 if (WEBSERVER.HOST !== parsed.host) {
21 const NODE_ENV = config.util.getEnv('NODE_ENV')
22 const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR')
25 'It seems PeerTube was started (and created some data) with another domain name. ' +
26 'This means you will not be able to federate! ' +
27 'Please use %s %s npm run update-host to fix this.',
28 NODE_CONFIG_DIR ? `NODE_CONFIG_DIR=${NODE_CONFIG_DIR}` : '',
29 NODE_ENV ? `NODE_ENV=${NODE_ENV}` : ''
34 // Some checks on configuration files or throw if there is an error
35 function checkConfig () {
37 const configFiles = config.util.getConfigSources().map(s => s.name).join(' -> ')
38 logger.info('Using following configuration file hierarchy: %s.', configFiles)
40 // Moved configuration keys
41 if (config.has('services.csp-logger')) {
42 logger.warn('services.csp-logger configuration has been renamed to csp.report_uri. Please update your configuration file.')
47 checkNSFWPolicyConfig()
48 checkLocalRedundancyConfig()
49 checkRemoteRedundancyConfig()
51 checkTranscodingConfig()
53 checkBroadcastMessageConfig()
56 checkObjectStorageConfig()
57 checkVideoStudioConfig()
60 // We get db by param to not import it in this file (import orders)
61 async function clientsExist () {
62 const totalClients = await OAuthClientModel.countTotal()
64 return totalClients !== 0
67 // We get db by param to not import it in this file (import orders)
68 async function usersExist () {
69 const totalUsers = await UserModel.countTotal()
71 return totalUsers !== 0
74 // We get db by param to not import it in this file (import orders)
75 async function applicationExist () {
76 const totalApplication = await ApplicationModel.countTotal()
78 return totalApplication !== 0
81 async function checkFFmpegVersion () {
82 const version = await getFFmpegVersion()
83 const { major, minor, patch } = parseSemVersion(version)
85 if (major < 4 || (major === 4 && minor < 1)) {
86 logger.warn('Your ffmpeg version (%s) is outdated. PeerTube supports ffmpeg >= 4.1. Please upgrade ffmpeg.', version)
89 if (major === 4 && minor === 4 && patch === 0) {
90 logger.warn('There is a bug in ffmpeg 4.4.0 with HLS videos. Please upgrade ffmpeg.')
94 // ---------------------------------------------------------------------------
105 // ---------------------------------------------------------------------------
107 function checkSecretsConfig () {
108 if (!CONFIG.SECRETS.PEERTUBE) {
109 throw new Error('secrets.peertube is missing in config. Generate one using `openssl rand -hex 32`')
113 function checkEmailConfig () {
114 if (!isEmailEnabled()) {
115 if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
116 throw new Error('Emailer is disabled but you require signup email verification.')
119 if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_APPROVAL) {
120 // eslint-disable-next-line max-len
121 logger.warn('Emailer is disabled but signup approval is enabled: PeerTube will not be able to send an email to the user upon acceptance/rejection of the registration request')
124 if (CONFIG.CONTACT_FORM.ENABLED) {
125 logger.warn('Emailer is disabled so the contact form will not work.')
130 function checkNSFWPolicyConfig () {
131 const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY
133 const available = [ 'do_not_list', 'blur', 'display' ]
134 if (available.includes(defaultNSFWPolicy) === false) {
135 throw new Error('NSFW policy setting should be ' + available.join(' or ') + ' instead of ' + defaultNSFWPolicy)
139 function checkLocalRedundancyConfig () {
140 const redundancyVideos = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES
142 if (isArray(redundancyVideos)) {
143 const available = [ 'most-views', 'trending', 'recently-added' ]
145 for (const r of redundancyVideos) {
146 if (available.includes(r.strategy) === false) {
147 throw new Error('Videos redundancy should have ' + available.join(' or ') + ' strategy instead of ' + r.strategy)
150 // Lifetime should not be < 10 hours
151 if (isProdInstance() && r.minLifetime < 1000 * 3600 * 10) {
152 throw new Error('Video redundancy minimum lifetime should be >= 10 hours for strategy ' + r.strategy)
156 const filtered = uniqify(redundancyVideos.map(r => r.strategy))
157 if (filtered.length !== redundancyVideos.length) {
158 throw new Error('Redundancy video entries should have unique strategies')
161 const recentlyAddedStrategy = redundancyVideos.find(r => r.strategy === 'recently-added') as RecentlyAddedStrategy
162 if (recentlyAddedStrategy && isNaN(recentlyAddedStrategy.minViews)) {
163 throw new Error('Min views in recently added strategy is not a number')
166 throw new Error('Videos redundancy should be an array (you must uncomment lines containing - too)')
170 function checkRemoteRedundancyConfig () {
171 const acceptFrom = CONFIG.REMOTE_REDUNDANCY.VIDEOS.ACCEPT_FROM
172 const acceptFromValues = new Set<VideoRedundancyConfigFilter>([ 'nobody', 'anybody', 'followings' ])
174 if (acceptFromValues.has(acceptFrom) === false) {
175 throw new Error('remote_redundancy.videos.accept_from has an incorrect value')
179 function checkStorageConfig () {
180 // Check storage directory locations
181 if (isProdInstance()) {
182 const configStorage = config.get<{ [ name: string ]: string }>('storage')
184 for (const key of Object.keys(configStorage)) {
185 if (configStorage[key].startsWith('storage/')) {
187 'Directory of %s should not be in the production directory of PeerTube. Please check your production configuration file.',
194 if (CONFIG.STORAGE.VIDEOS_DIR === CONFIG.STORAGE.REDUNDANCY_DIR) {
195 logger.warn('Redundancy directory should be different than the videos folder.')
199 function checkTranscodingConfig () {
200 if (CONFIG.TRANSCODING.ENABLED) {
201 if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false && CONFIG.TRANSCODING.HLS.ENABLED === false) {
202 throw new Error('You need to enable at least WebTorrent transcoding or HLS transcoding.')
205 if (CONFIG.TRANSCODING.CONCURRENCY <= 0) {
206 throw new Error('Transcoding concurrency should be > 0')
210 if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED || CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED) {
211 if (CONFIG.IMPORT.VIDEOS.CONCURRENCY <= 0) {
212 throw new Error('Video import concurrency should be > 0')
217 function checkImportConfig () {
218 if (CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.ENABLED && !CONFIG.IMPORT.VIDEOS.HTTP) {
219 throw new Error('You need to enable HTTP import to allow synchronization')
223 function checkBroadcastMessageConfig () {
224 if (CONFIG.BROADCAST_MESSAGE.ENABLED) {
225 const currentLevel = CONFIG.BROADCAST_MESSAGE.LEVEL
226 const available = [ 'info', 'warning', 'error' ]
228 if (available.includes(currentLevel) === false) {
229 throw new Error('Broadcast message level should be ' + available.join(' or ') + ' instead of ' + currentLevel)
234 function checkSearchConfig () {
235 if (CONFIG.SEARCH.SEARCH_INDEX.ENABLED === true) {
236 if (CONFIG.SEARCH.REMOTE_URI.USERS === false) {
237 throw new Error('You cannot enable search index without enabling remote URI search for users.')
242 function checkLiveConfig () {
243 if (CONFIG.LIVE.ENABLED === true) {
244 if (CONFIG.LIVE.ALLOW_REPLAY === true && CONFIG.TRANSCODING.ENABLED === false) {
245 throw new Error('Live allow replay cannot be enabled if transcoding is not enabled.')
248 if (CONFIG.LIVE.RTMP.ENABLED === false && CONFIG.LIVE.RTMPS.ENABLED === false) {
249 throw new Error('You must enable at least RTMP or RTMPS')
252 if (CONFIG.LIVE.RTMPS.ENABLED) {
253 if (!CONFIG.LIVE.RTMPS.KEY_FILE) {
254 throw new Error('You must specify a key file to enabled RTMPS')
257 if (!CONFIG.LIVE.RTMPS.CERT_FILE) {
258 throw new Error('You must specify a cert file to enable RTMPS')
264 function checkObjectStorageConfig () {
265 if (CONFIG.OBJECT_STORAGE.ENABLED === true) {
267 if (!CONFIG.OBJECT_STORAGE.VIDEOS.BUCKET_NAME) {
268 throw new Error('videos_bucket should be set when object storage support is enabled.')
271 if (!CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.BUCKET_NAME) {
272 throw new Error('streaming_playlists_bucket should be set when object storage support is enabled.')
276 CONFIG.OBJECT_STORAGE.VIDEOS.BUCKET_NAME === CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.BUCKET_NAME &&
277 CONFIG.OBJECT_STORAGE.VIDEOS.PREFIX === CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.PREFIX
279 if (CONFIG.OBJECT_STORAGE.VIDEOS.PREFIX === '') {
280 throw new Error('Object storage bucket prefixes should be set when the same bucket is used for both types of video.')
284 'Object storage bucket prefixes should be set to different values when the same bucket is used for both types of video.'
290 function checkVideoStudioConfig () {
291 if (CONFIG.VIDEO_STUDIO.ENABLED === true && CONFIG.TRANSCODING.ENABLED === false) {
292 throw new Error('Video studio cannot be enabled if transcoding is disabled')