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.CONTACT_FORM.ENABLED) {
120 logger.warn('Emailer is disabled so the contact form will not work.')
125 function checkNSFWPolicyConfig () {
126 const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY
128 const available = [ 'do_not_list', 'blur', 'display' ]
129 if (available.includes(defaultNSFWPolicy) === false) {
130 throw new Error('NSFW policy setting should be ' + available.join(' or ') + ' instead of ' + defaultNSFWPolicy)
134 function checkLocalRedundancyConfig () {
135 const redundancyVideos = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES
137 if (isArray(redundancyVideos)) {
138 const available = [ 'most-views', 'trending', 'recently-added' ]
140 for (const r of redundancyVideos) {
141 if (available.includes(r.strategy) === false) {
142 throw new Error('Videos redundancy should have ' + available.join(' or ') + ' strategy instead of ' + r.strategy)
145 // Lifetime should not be < 10 hours
146 if (isProdInstance() && r.minLifetime < 1000 * 3600 * 10) {
147 throw new Error('Video redundancy minimum lifetime should be >= 10 hours for strategy ' + r.strategy)
151 const filtered = uniqify(redundancyVideos.map(r => r.strategy))
152 if (filtered.length !== redundancyVideos.length) {
153 throw new Error('Redundancy video entries should have unique strategies')
156 const recentlyAddedStrategy = redundancyVideos.find(r => r.strategy === 'recently-added') as RecentlyAddedStrategy
157 if (recentlyAddedStrategy && isNaN(recentlyAddedStrategy.minViews)) {
158 throw new Error('Min views in recently added strategy is not a number')
161 throw new Error('Videos redundancy should be an array (you must uncomment lines containing - too)')
165 function checkRemoteRedundancyConfig () {
166 const acceptFrom = CONFIG.REMOTE_REDUNDANCY.VIDEOS.ACCEPT_FROM
167 const acceptFromValues = new Set<VideoRedundancyConfigFilter>([ 'nobody', 'anybody', 'followings' ])
169 if (acceptFromValues.has(acceptFrom) === false) {
170 throw new Error('remote_redundancy.videos.accept_from has an incorrect value')
174 function checkStorageConfig () {
175 // Check storage directory locations
176 if (isProdInstance()) {
177 const configStorage = config.get<{ [ name: string ]: string }>('storage')
179 for (const key of Object.keys(configStorage)) {
180 if (configStorage[key].startsWith('storage/')) {
182 'Directory of %s should not be in the production directory of PeerTube. Please check your production configuration file.',
189 if (CONFIG.STORAGE.VIDEOS_DIR === CONFIG.STORAGE.REDUNDANCY_DIR) {
190 logger.warn('Redundancy directory should be different than the videos folder.')
194 function checkTranscodingConfig () {
195 if (CONFIG.TRANSCODING.ENABLED) {
196 if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false && CONFIG.TRANSCODING.HLS.ENABLED === false) {
197 throw new Error('You need to enable at least WebTorrent transcoding or HLS transcoding.')
200 if (CONFIG.TRANSCODING.CONCURRENCY <= 0) {
201 throw new Error('Transcoding concurrency should be > 0')
205 if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED || CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED) {
206 if (CONFIG.IMPORT.VIDEOS.CONCURRENCY <= 0) {
207 throw new Error('Video import concurrency should be > 0')
212 function checkImportConfig () {
213 if (CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.ENABLED && !CONFIG.IMPORT.VIDEOS.HTTP) {
214 throw new Error('You need to enable HTTP import to allow synchronization')
218 function checkBroadcastMessageConfig () {
219 if (CONFIG.BROADCAST_MESSAGE.ENABLED) {
220 const currentLevel = CONFIG.BROADCAST_MESSAGE.LEVEL
221 const available = [ 'info', 'warning', 'error' ]
223 if (available.includes(currentLevel) === false) {
224 throw new Error('Broadcast message level should be ' + available.join(' or ') + ' instead of ' + currentLevel)
229 function checkSearchConfig () {
230 if (CONFIG.SEARCH.SEARCH_INDEX.ENABLED === true) {
231 if (CONFIG.SEARCH.REMOTE_URI.USERS === false) {
232 throw new Error('You cannot enable search index without enabling remote URI search for users.')
237 function checkLiveConfig () {
238 if (CONFIG.LIVE.ENABLED === true) {
239 if (CONFIG.LIVE.ALLOW_REPLAY === true && CONFIG.TRANSCODING.ENABLED === false) {
240 throw new Error('Live allow replay cannot be enabled if transcoding is not enabled.')
243 if (CONFIG.LIVE.RTMP.ENABLED === false && CONFIG.LIVE.RTMPS.ENABLED === false) {
244 throw new Error('You must enable at least RTMP or RTMPS')
247 if (CONFIG.LIVE.RTMPS.ENABLED) {
248 if (!CONFIG.LIVE.RTMPS.KEY_FILE) {
249 throw new Error('You must specify a key file to enabled RTMPS')
252 if (!CONFIG.LIVE.RTMPS.CERT_FILE) {
253 throw new Error('You must specify a cert file to enable RTMPS')
259 function checkObjectStorageConfig () {
260 if (CONFIG.OBJECT_STORAGE.ENABLED === true) {
262 if (!CONFIG.OBJECT_STORAGE.VIDEOS.BUCKET_NAME) {
263 throw new Error('videos_bucket should be set when object storage support is enabled.')
266 if (!CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.BUCKET_NAME) {
267 throw new Error('streaming_playlists_bucket should be set when object storage support is enabled.')
271 CONFIG.OBJECT_STORAGE.VIDEOS.BUCKET_NAME === CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.BUCKET_NAME &&
272 CONFIG.OBJECT_STORAGE.VIDEOS.PREFIX === CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.PREFIX
274 if (CONFIG.OBJECT_STORAGE.VIDEOS.PREFIX === '') {
275 throw new Error('Object storage bucket prefixes should be set when the same bucket is used for both types of video.')
279 'Object storage bucket prefixes should be set to different values when the same bucket is used for both types of video.'
283 if (!CONFIG.OBJECT_STORAGE.UPLOAD_ACL.PUBLIC) {
284 throw new Error('object_storage.upload_acl.public must be set')
287 if (!CONFIG.OBJECT_STORAGE.UPLOAD_ACL.PRIVATE) {
288 throw new Error('object_storage.upload_acl.private must be set')
293 function checkVideoStudioConfig () {
294 if (CONFIG.VIDEO_STUDIO.ENABLED === true && CONFIG.TRANSCODING.ENABLED === false) {
295 throw new Error('Video studio cannot be enabled if transcoding is disabled')