]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/controllers/api/config.ts
Add more hints regarding live port
[github/Chocobozzz/PeerTube.git] / server / controllers / api / config.ts
1 import { Hooks } from '@server/lib/plugins/hooks'
2 import * as express from 'express'
3 import { remove, writeJSON } from 'fs-extra'
4 import { snakeCase } from 'lodash'
5 import validator from 'validator'
6 import { RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig, UserRight } from '../../../shared'
7 import { About } from '../../../shared/models/server/about.model'
8 import { CustomConfig } from '../../../shared/models/server/custom-config.model'
9 import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
10 import { objectConverter } from '../../helpers/core-utils'
11 import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
12 import { getServerCommit } from '../../helpers/utils'
13 import { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config'
14 import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants'
15 import { ClientHtml } from '../../lib/client-html'
16 import { PluginManager } from '../../lib/plugins/plugin-manager'
17 import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
18 import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
19 import { customConfigUpdateValidator } from '../../middlewares/validators/config'
20
21 const configRouter = express.Router()
22
23 const auditLogger = auditLoggerFactory('config')
24
25 configRouter.get('/about', getAbout)
26 configRouter.get('/',
27 asyncMiddleware(getConfig)
28 )
29
30 configRouter.get('/custom',
31 authenticate,
32 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
33 getCustomConfig
34 )
35 configRouter.put('/custom',
36 authenticate,
37 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
38 customConfigUpdateValidator,
39 asyncMiddleware(updateCustomConfig)
40 )
41 configRouter.delete('/custom',
42 authenticate,
43 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
44 asyncMiddleware(deleteCustomConfig)
45 )
46
47 let serverCommit: string
48
49 async function getConfig (req: express.Request, res: express.Response) {
50 const { allowed } = await Hooks.wrapPromiseFun(
51 isSignupAllowed,
52 {
53 ip: req.ip
54 },
55 'filter:api.user.signup.allowed.result'
56 )
57
58 const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
59 const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
60
61 if (serverCommit === undefined) serverCommit = await getServerCommit()
62
63 const json: ServerConfig = {
64 instance: {
65 name: CONFIG.INSTANCE.NAME,
66 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
67 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
68 isNSFW: CONFIG.INSTANCE.IS_NSFW,
69 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
70 customizations: {
71 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
72 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
73 }
74 },
75 search: {
76 remoteUri: {
77 users: CONFIG.SEARCH.REMOTE_URI.USERS,
78 anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
79 },
80 searchIndex: {
81 enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
82 url: CONFIG.SEARCH.SEARCH_INDEX.URL,
83 disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
84 isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
85 }
86 },
87 plugin: {
88 registered: getRegisteredPlugins(),
89 registeredExternalAuths: getExternalAuthsPlugins(),
90 registeredIdAndPassAuths: getIdAndPassAuthPlugins()
91 },
92 theme: {
93 registered: getRegisteredThemes(),
94 default: defaultTheme
95 },
96 email: {
97 enabled: isEmailEnabled()
98 },
99 contactForm: {
100 enabled: CONFIG.CONTACT_FORM.ENABLED
101 },
102 serverVersion: PEERTUBE_VERSION,
103 serverCommit,
104 signup: {
105 allowed,
106 allowedForCurrentIP,
107 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
108 },
109 transcoding: {
110 hls: {
111 enabled: CONFIG.TRANSCODING.HLS.ENABLED
112 },
113 webtorrent: {
114 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
115 },
116 enabledResolutions: getEnabledResolutions('vod')
117 },
118 live: {
119 enabled: CONFIG.LIVE.ENABLED,
120
121 allowReplay: CONFIG.LIVE.ALLOW_REPLAY,
122 maxDuration: CONFIG.LIVE.MAX_DURATION,
123 maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES,
124 maxUserLives: CONFIG.LIVE.MAX_USER_LIVES,
125
126 transcoding: {
127 enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
128 enabledResolutions: getEnabledResolutions('live')
129 },
130
131 rtmp: {
132 port: CONFIG.LIVE.RTMP.PORT
133 }
134 },
135 import: {
136 videos: {
137 http: {
138 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
139 },
140 torrent: {
141 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
142 }
143 }
144 },
145 autoBlacklist: {
146 videos: {
147 ofUsers: {
148 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
149 }
150 }
151 },
152 avatar: {
153 file: {
154 size: {
155 max: CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max
156 },
157 extensions: CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
158 }
159 },
160 video: {
161 image: {
162 extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
163 size: {
164 max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
165 }
166 },
167 file: {
168 extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
169 }
170 },
171 videoCaption: {
172 file: {
173 size: {
174 max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
175 },
176 extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
177 }
178 },
179 user: {
180 videoQuota: CONFIG.USER.VIDEO_QUOTA,
181 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
182 },
183 trending: {
184 videos: {
185 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
186 }
187 },
188 tracker: {
189 enabled: CONFIG.TRACKER.ENABLED
190 },
191
192 followings: {
193 instance: {
194 autoFollowIndex: {
195 indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
196 }
197 }
198 },
199
200 broadcastMessage: {
201 enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
202 message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
203 level: CONFIG.BROADCAST_MESSAGE.LEVEL,
204 dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
205 }
206 }
207
208 return res.json(json)
209 }
210
211 function getAbout (req: express.Request, res: express.Response) {
212 const about: About = {
213 instance: {
214 name: CONFIG.INSTANCE.NAME,
215 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
216 description: CONFIG.INSTANCE.DESCRIPTION,
217 terms: CONFIG.INSTANCE.TERMS,
218 codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
219
220 hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
221
222 creationReason: CONFIG.INSTANCE.CREATION_REASON,
223 moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
224 administrator: CONFIG.INSTANCE.ADMINISTRATOR,
225 maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
226 businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
227
228 languages: CONFIG.INSTANCE.LANGUAGES,
229 categories: CONFIG.INSTANCE.CATEGORIES
230 }
231 }
232
233 return res.json(about).end()
234 }
235
236 function getCustomConfig (req: express.Request, res: express.Response) {
237 const data = customConfig()
238
239 return res.json(data).end()
240 }
241
242 async function deleteCustomConfig (req: express.Request, res: express.Response) {
243 await remove(CONFIG.CUSTOM_FILE)
244
245 auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
246
247 reloadConfig()
248 ClientHtml.invalidCache()
249
250 const data = customConfig()
251
252 return res.json(data)
253 }
254
255 async function updateCustomConfig (req: express.Request, res: express.Response) {
256 const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
257
258 // camelCase to snake_case key + Force number conversion
259 const toUpdateJSON = convertCustomConfigBody(req.body)
260
261 await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
262
263 reloadConfig()
264 ClientHtml.invalidCache()
265
266 const data = customConfig()
267
268 auditLogger.update(
269 getAuditIdFromRes(res),
270 new CustomConfigAuditView(data),
271 oldCustomConfigAuditKeys
272 )
273
274 return res.json(data)
275 }
276
277 function getRegisteredThemes () {
278 return PluginManager.Instance.getRegisteredThemes()
279 .map(t => ({
280 name: t.name,
281 version: t.version,
282 description: t.description,
283 css: t.css,
284 clientScripts: t.clientScripts
285 }))
286 }
287
288 function getEnabledResolutions (type: 'vod' | 'live') {
289 const transcoding = type === 'vod'
290 ? CONFIG.TRANSCODING
291 : CONFIG.LIVE.TRANSCODING
292
293 return Object.keys(transcoding.RESOLUTIONS)
294 .filter(key => transcoding.ENABLED && transcoding.RESOLUTIONS[key] === true)
295 .map(r => parseInt(r, 10))
296 }
297
298 function getRegisteredPlugins () {
299 return PluginManager.Instance.getRegisteredPlugins()
300 .map(p => ({
301 name: p.name,
302 version: p.version,
303 description: p.description,
304 clientScripts: p.clientScripts
305 }))
306 }
307
308 function getIdAndPassAuthPlugins () {
309 const result: RegisteredIdAndPassAuthConfig[] = []
310
311 for (const p of PluginManager.Instance.getIdAndPassAuths()) {
312 for (const auth of p.idAndPassAuths) {
313 result.push({
314 npmName: p.npmName,
315 name: p.name,
316 version: p.version,
317 authName: auth.authName,
318 weight: auth.getWeight()
319 })
320 }
321 }
322
323 return result
324 }
325
326 function getExternalAuthsPlugins () {
327 const result: RegisteredExternalAuthConfig[] = []
328
329 for (const p of PluginManager.Instance.getExternalAuths()) {
330 for (const auth of p.externalAuths) {
331 result.push({
332 npmName: p.npmName,
333 name: p.name,
334 version: p.version,
335 authName: auth.authName,
336 authDisplayName: auth.authDisplayName()
337 })
338 }
339 }
340
341 return result
342 }
343
344 // ---------------------------------------------------------------------------
345
346 export {
347 configRouter,
348 getEnabledResolutions,
349 getRegisteredPlugins,
350 getRegisteredThemes
351 }
352
353 // ---------------------------------------------------------------------------
354
355 function customConfig (): CustomConfig {
356 return {
357 instance: {
358 name: CONFIG.INSTANCE.NAME,
359 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
360 description: CONFIG.INSTANCE.DESCRIPTION,
361 terms: CONFIG.INSTANCE.TERMS,
362 codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
363
364 creationReason: CONFIG.INSTANCE.CREATION_REASON,
365 moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
366 administrator: CONFIG.INSTANCE.ADMINISTRATOR,
367 maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
368 businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
369 hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
370
371 languages: CONFIG.INSTANCE.LANGUAGES,
372 categories: CONFIG.INSTANCE.CATEGORIES,
373
374 isNSFW: CONFIG.INSTANCE.IS_NSFW,
375 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
376 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
377 customizations: {
378 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
379 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
380 }
381 },
382 theme: {
383 default: CONFIG.THEME.DEFAULT
384 },
385 services: {
386 twitter: {
387 username: CONFIG.SERVICES.TWITTER.USERNAME,
388 whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
389 }
390 },
391 cache: {
392 previews: {
393 size: CONFIG.CACHE.PREVIEWS.SIZE
394 },
395 captions: {
396 size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
397 }
398 },
399 signup: {
400 enabled: CONFIG.SIGNUP.ENABLED,
401 limit: CONFIG.SIGNUP.LIMIT,
402 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
403 },
404 admin: {
405 email: CONFIG.ADMIN.EMAIL
406 },
407 contactForm: {
408 enabled: CONFIG.CONTACT_FORM.ENABLED
409 },
410 user: {
411 videoQuota: CONFIG.USER.VIDEO_QUOTA,
412 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
413 },
414 transcoding: {
415 enabled: CONFIG.TRANSCODING.ENABLED,
416 allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
417 allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
418 threads: CONFIG.TRANSCODING.THREADS,
419 resolutions: {
420 '0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
421 '240p': CONFIG.TRANSCODING.RESOLUTIONS['240p'],
422 '360p': CONFIG.TRANSCODING.RESOLUTIONS['360p'],
423 '480p': CONFIG.TRANSCODING.RESOLUTIONS['480p'],
424 '720p': CONFIG.TRANSCODING.RESOLUTIONS['720p'],
425 '1080p': CONFIG.TRANSCODING.RESOLUTIONS['1080p'],
426 '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
427 },
428 webtorrent: {
429 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
430 },
431 hls: {
432 enabled: CONFIG.TRANSCODING.HLS.ENABLED
433 }
434 },
435 live: {
436 enabled: CONFIG.LIVE.ENABLED,
437 allowReplay: CONFIG.LIVE.ALLOW_REPLAY,
438 maxDuration: CONFIG.LIVE.MAX_DURATION,
439 maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES,
440 maxUserLives: CONFIG.LIVE.MAX_USER_LIVES,
441 transcoding: {
442 enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
443 threads: CONFIG.LIVE.TRANSCODING.THREADS,
444 resolutions: {
445 '240p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['240p'],
446 '360p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['360p'],
447 '480p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['480p'],
448 '720p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['720p'],
449 '1080p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1080p'],
450 '2160p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['2160p']
451 }
452 }
453 },
454 import: {
455 videos: {
456 http: {
457 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
458 },
459 torrent: {
460 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
461 }
462 }
463 },
464 autoBlacklist: {
465 videos: {
466 ofUsers: {
467 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
468 }
469 }
470 },
471 followers: {
472 instance: {
473 enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
474 manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
475 }
476 },
477 followings: {
478 instance: {
479 autoFollowBack: {
480 enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED
481 },
482
483 autoFollowIndex: {
484 enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED,
485 indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
486 }
487 }
488 },
489 broadcastMessage: {
490 enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
491 message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
492 level: CONFIG.BROADCAST_MESSAGE.LEVEL,
493 dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
494 },
495 search: {
496 remoteUri: {
497 users: CONFIG.SEARCH.REMOTE_URI.USERS,
498 anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
499 },
500 searchIndex: {
501 enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
502 url: CONFIG.SEARCH.SEARCH_INDEX.URL,
503 disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
504 isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
505 }
506 }
507 }
508 }
509
510 function convertCustomConfigBody (body: CustomConfig) {
511 function keyConverter (k: string) {
512 // Transcoding resolutions exception
513 if (/^\d{3,4}p$/.exec(k)) return k
514 if (k === '0p') return k
515
516 return snakeCase(k)
517 }
518
519 function valueConverter (v: any) {
520 if (validator.isNumeric(v + '')) return parseInt('' + v, 10)
521
522 return v
523 }
524
525 return objectConverter(body, keyConverter, valueConverter)
526 }