Fix videos list user NSFW policy
[oweals/peertube.git] / server / initializers / checker.ts
1 import * as config from 'config'
2 import { promisify0, isProdInstance } from '../helpers/core-utils'
3 import { UserModel } from '../models/account/user'
4 import { ApplicationModel } from '../models/application/application'
5 import { OAuthClientModel } from '../models/oauth/oauth-client'
6 import { parse } from 'url'
7 import { CONFIG } from './constants'
8 import { logger } from '../helpers/logger'
9 import { getServerActor } from '../helpers/utils'
10 import { RecentlyAddedStrategy, VideosRedundancy } from '../../shared/models/redundancy'
11 import { isArray } from '../helpers/custom-validators/misc'
12 import { uniq } from 'lodash'
13
14 async function checkActivityPubUrls () {
15   const actor = await getServerActor()
16
17   const parsed = parse(actor.url)
18   if (CONFIG.WEBSERVER.HOST !== parsed.host) {
19     const NODE_ENV = config.util.getEnv('NODE_ENV')
20     const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR')
21
22     logger.warn(
23       'It seems PeerTube was started (and created some data) with another domain name. ' +
24       'This means you will not be able to federate! ' +
25       'Please use %s %s npm run update-host to fix this.',
26       NODE_CONFIG_DIR ? `NODE_CONFIG_DIR=${NODE_CONFIG_DIR}` : '',
27       NODE_ENV ? `NODE_ENV=${NODE_ENV}` : ''
28     )
29   }
30 }
31
32 // Some checks on configuration files
33 // Return an error message, or null if everything is okay
34 function checkConfig () {
35   const defaultNSFWPolicy = config.get<string>('instance.default_nsfw_policy')
36
37   // NSFW policy
38   if ([ 'do_not_list', 'blur', 'display' ].indexOf(defaultNSFWPolicy) === -1) {
39     return 'NSFW policy setting should be "do_not_list" or "blur" or "display" instead of ' + defaultNSFWPolicy
40   }
41
42   // Redundancies
43   const redundancyVideos = config.get<VideosRedundancy[]>('redundancy.videos.strategies')
44   if (isArray(redundancyVideos)) {
45     for (const r of redundancyVideos) {
46       if ([ 'most-views', 'trending', 'recently-added' ].indexOf(r.strategy) === -1) {
47         return 'Redundancy video entries should have "most-views" strategy instead of ' + r.strategy
48       }
49     }
50
51     const filtered = uniq(redundancyVideos.map(r => r.strategy))
52     if (filtered.length !== redundancyVideos.length) {
53       return 'Redundancy video entries should have unique strategies'
54     }
55
56     const recentlyAddedStrategy = redundancyVideos.find(r => r.strategy === 'recently-added') as RecentlyAddedStrategy
57     if (recentlyAddedStrategy && isNaN(recentlyAddedStrategy.minViews)) {
58       return 'Min views in recently added strategy is not a number'
59     }
60   }
61
62   if (isProdInstance()) {
63     const configStorage = config.get('storage')
64     for (const key of Object.keys(configStorage)) {
65       if (configStorage[key].startsWith('storage/')) {
66         logger.warn(
67           'Directory of %s should not be in the production directory of PeerTube. Please check your production configuration file.',
68           key
69         )
70       }
71     }
72   }
73
74   return null
75 }
76
77 // Check the config files
78 function checkMissedConfig () {
79   const required = [ 'listen.port', 'listen.hostname',
80     'webserver.https', 'webserver.hostname', 'webserver.port',
81     'trust_proxy',
82     'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max',
83     'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
84     'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
85     'log.level',
86     'user.video_quota', 'user.video_quota_daily',
87     'cache.previews.size', 'admin.email',
88     'signup.enabled', 'signup.limit', 'signup.requires_email_verification',
89     'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
90     'redundancy.videos.strategies', 'redundancy.videos.check_interval',
91     'transcoding.enabled', 'transcoding.threads',
92     'import.videos.http.enabled', 'import.videos.torrent.enabled',
93     'trending.videos.interval_days',
94     'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
95     'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
96     'services.twitter.username', 'services.twitter.whitelisted'
97   ]
98   const requiredAlternatives = [
99     [ // set
100       ['redis.hostname', 'redis.port'], // alternative
101       ['redis.socket']
102     ]
103   ]
104   const miss: string[] = []
105
106   for (const key of required) {
107     if (!config.has(key)) {
108       miss.push(key)
109     }
110   }
111
112   const missingAlternatives = requiredAlternatives.filter(
113     set => !set.find(alternative => !alternative.find(key => !config.has(key)))
114   )
115
116   missingAlternatives
117     .forEach(set => set[0].forEach(key => miss.push(key)))
118
119   return miss
120 }
121
122 // Check the available codecs
123 // We get CONFIG by param to not import it in this file (import orders)
124 async function checkFFmpeg (CONFIG: { TRANSCODING: { ENABLED: boolean } }) {
125   const Ffmpeg = require('fluent-ffmpeg')
126   const getAvailableCodecsPromise = promisify0(Ffmpeg.getAvailableCodecs)
127   const codecs = await getAvailableCodecsPromise()
128   const canEncode = [ 'libx264' ]
129
130   if (CONFIG.TRANSCODING.ENABLED === false) return undefined
131
132   for (const codec of canEncode) {
133     if (codecs[codec] === undefined) {
134       throw new Error('Unknown codec ' + codec + ' in FFmpeg.')
135     }
136
137     if (codecs[codec].canEncode !== true) {
138       throw new Error('Unavailable encode codec ' + codec + ' in FFmpeg')
139     }
140   }
141
142   checkFFmpegEncoders()
143 }
144
145 // Optional encoders, if present, can be used to improve transcoding
146 // Here we ask ffmpeg if it detects their presence on the system, so that we can later use them
147 let supportedOptionalEncoders: Map<string, boolean>
148 async function checkFFmpegEncoders (): Promise<Map<string, boolean>> {
149   if (supportedOptionalEncoders !== undefined) {
150     return supportedOptionalEncoders
151   }
152
153   const Ffmpeg = require('fluent-ffmpeg')
154   const getAvailableEncodersPromise = promisify0(Ffmpeg.getAvailableEncoders)
155   const encoders = await getAvailableEncodersPromise()
156   const optionalEncoders = [ 'libfdk_aac' ]
157   supportedOptionalEncoders = new Map<string, boolean>()
158
159   for (const encoder of optionalEncoders) {
160     supportedOptionalEncoders.set(encoder,
161       encoders[encoder] !== undefined
162     )
163   }
164 }
165
166 // We get db by param to not import it in this file (import orders)
167 async function clientsExist () {
168   const totalClients = await OAuthClientModel.countTotal()
169
170   return totalClients !== 0
171 }
172
173 // We get db by param to not import it in this file (import orders)
174 async function usersExist () {
175   const totalUsers = await UserModel.countTotal()
176
177   return totalUsers !== 0
178 }
179
180 // We get db by param to not import it in this file (import orders)
181 async function applicationExist () {
182   const totalApplication = await ApplicationModel.countTotal()
183
184   return totalApplication !== 0
185 }
186
187 // ---------------------------------------------------------------------------
188
189 export {
190   checkConfig,
191   checkFFmpeg,
192   checkFFmpegEncoders,
193   checkMissedConfig,
194   clientsExist,
195   usersExist,
196   applicationExist,
197   checkActivityPubUrls
198 }