config.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. import express from 'express'
  2. import { remove, writeJSON } from 'fs-extra/esm'
  3. import snakeCase from 'lodash-es/snakeCase.js'
  4. import validator from 'validator'
  5. import { ServerConfigManager } from '@server/lib/server-config-manager.js'
  6. import { About, CustomConfig, UserRight } from '@peertube/peertube-models'
  7. import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger.js'
  8. import { objectConverter } from '../../helpers/core-utils.js'
  9. import { CONFIG, reloadConfig } from '../../initializers/config.js'
  10. import { ClientHtml } from '../../lib/client-html.js'
  11. import { apiRateLimiter, asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares/index.js'
  12. import { customConfigUpdateValidator, ensureConfigIsEditable } from '../../middlewares/validators/config.js'
  13. const configRouter = express.Router()
  14. configRouter.use(apiRateLimiter)
  15. const auditLogger = auditLoggerFactory('config')
  16. configRouter.get('/',
  17. openapiOperationDoc({ operationId: 'getConfig' }),
  18. asyncMiddleware(getConfig)
  19. )
  20. configRouter.get('/about',
  21. openapiOperationDoc({ operationId: 'getAbout' }),
  22. getAbout
  23. )
  24. configRouter.get('/custom',
  25. openapiOperationDoc({ operationId: 'getCustomConfig' }),
  26. authenticate,
  27. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  28. getCustomConfig
  29. )
  30. configRouter.put('/custom',
  31. openapiOperationDoc({ operationId: 'putCustomConfig' }),
  32. authenticate,
  33. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  34. ensureConfigIsEditable,
  35. customConfigUpdateValidator,
  36. asyncMiddleware(updateCustomConfig)
  37. )
  38. configRouter.delete('/custom',
  39. openapiOperationDoc({ operationId: 'delCustomConfig' }),
  40. authenticate,
  41. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  42. ensureConfigIsEditable,
  43. asyncMiddleware(deleteCustomConfig)
  44. )
  45. async function getConfig (req: express.Request, res: express.Response) {
  46. const json = await ServerConfigManager.Instance.getServerConfig(req.ip)
  47. return res.json(json)
  48. }
  49. function getAbout (req: express.Request, res: express.Response) {
  50. const about: About = {
  51. instance: {
  52. name: CONFIG.INSTANCE.NAME,
  53. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  54. description: CONFIG.INSTANCE.DESCRIPTION,
  55. terms: CONFIG.INSTANCE.TERMS,
  56. codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
  57. hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
  58. creationReason: CONFIG.INSTANCE.CREATION_REASON,
  59. moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
  60. administrator: CONFIG.INSTANCE.ADMINISTRATOR,
  61. maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
  62. businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
  63. languages: CONFIG.INSTANCE.LANGUAGES,
  64. categories: CONFIG.INSTANCE.CATEGORIES
  65. }
  66. }
  67. return res.json(about)
  68. }
  69. function getCustomConfig (req: express.Request, res: express.Response) {
  70. const data = customConfig()
  71. return res.json(data)
  72. }
  73. async function deleteCustomConfig (req: express.Request, res: express.Response) {
  74. await remove(CONFIG.CUSTOM_FILE)
  75. auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
  76. await reloadConfig()
  77. ClientHtml.invalidCache()
  78. const data = customConfig()
  79. return res.json(data)
  80. }
  81. async function updateCustomConfig (req: express.Request, res: express.Response) {
  82. const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
  83. // camelCase to snake_case key + Force number conversion
  84. const toUpdateJSON = convertCustomConfigBody(req.body)
  85. await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
  86. await reloadConfig()
  87. ClientHtml.invalidCache()
  88. const data = customConfig()
  89. auditLogger.update(
  90. getAuditIdFromRes(res),
  91. new CustomConfigAuditView(data),
  92. oldCustomConfigAuditKeys
  93. )
  94. return res.json(data)
  95. }
  96. // ---------------------------------------------------------------------------
  97. export {
  98. configRouter
  99. }
  100. // ---------------------------------------------------------------------------
  101. function customConfig (): CustomConfig {
  102. return {
  103. instance: {
  104. name: CONFIG.INSTANCE.NAME,
  105. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  106. description: CONFIG.INSTANCE.DESCRIPTION,
  107. terms: CONFIG.INSTANCE.TERMS,
  108. codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
  109. creationReason: CONFIG.INSTANCE.CREATION_REASON,
  110. moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
  111. administrator: CONFIG.INSTANCE.ADMINISTRATOR,
  112. maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
  113. businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
  114. hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
  115. languages: CONFIG.INSTANCE.LANGUAGES,
  116. categories: CONFIG.INSTANCE.CATEGORIES,
  117. isNSFW: CONFIG.INSTANCE.IS_NSFW,
  118. defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
  119. defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
  120. customizations: {
  121. css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
  122. javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
  123. }
  124. },
  125. theme: {
  126. default: CONFIG.THEME.DEFAULT
  127. },
  128. services: {
  129. twitter: {
  130. username: CONFIG.SERVICES.TWITTER.USERNAME,
  131. whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
  132. }
  133. },
  134. client: {
  135. videos: {
  136. miniature: {
  137. preferAuthorDisplayName: CONFIG.CLIENT.VIDEOS.MINIATURE.PREFER_AUTHOR_DISPLAY_NAME
  138. }
  139. },
  140. menu: {
  141. login: {
  142. redirectOnSingleExternalAuth: CONFIG.CLIENT.MENU.LOGIN.REDIRECT_ON_SINGLE_EXTERNAL_AUTH
  143. }
  144. }
  145. },
  146. cache: {
  147. previews: {
  148. size: CONFIG.CACHE.PREVIEWS.SIZE
  149. },
  150. captions: {
  151. size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
  152. },
  153. torrents: {
  154. size: CONFIG.CACHE.TORRENTS.SIZE
  155. },
  156. storyboards: {
  157. size: CONFIG.CACHE.STORYBOARDS.SIZE
  158. }
  159. },
  160. signup: {
  161. enabled: CONFIG.SIGNUP.ENABLED,
  162. limit: CONFIG.SIGNUP.LIMIT,
  163. requiresApproval: CONFIG.SIGNUP.REQUIRES_APPROVAL,
  164. requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION,
  165. minimumAge: CONFIG.SIGNUP.MINIMUM_AGE
  166. },
  167. admin: {
  168. email: CONFIG.ADMIN.EMAIL
  169. },
  170. contactForm: {
  171. enabled: CONFIG.CONTACT_FORM.ENABLED
  172. },
  173. user: {
  174. history: {
  175. videos: {
  176. enabled: CONFIG.USER.HISTORY.VIDEOS.ENABLED
  177. }
  178. },
  179. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  180. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
  181. },
  182. videoChannels: {
  183. maxPerUser: CONFIG.VIDEO_CHANNELS.MAX_PER_USER
  184. },
  185. transcoding: {
  186. enabled: CONFIG.TRANSCODING.ENABLED,
  187. remoteRunners: {
  188. enabled: CONFIG.TRANSCODING.REMOTE_RUNNERS.ENABLED
  189. },
  190. allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
  191. allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
  192. threads: CONFIG.TRANSCODING.THREADS,
  193. concurrency: CONFIG.TRANSCODING.CONCURRENCY,
  194. profile: CONFIG.TRANSCODING.PROFILE,
  195. resolutions: {
  196. '0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
  197. '144p': CONFIG.TRANSCODING.RESOLUTIONS['144p'],
  198. '240p': CONFIG.TRANSCODING.RESOLUTIONS['240p'],
  199. '360p': CONFIG.TRANSCODING.RESOLUTIONS['360p'],
  200. '480p': CONFIG.TRANSCODING.RESOLUTIONS['480p'],
  201. '720p': CONFIG.TRANSCODING.RESOLUTIONS['720p'],
  202. '1080p': CONFIG.TRANSCODING.RESOLUTIONS['1080p'],
  203. '1440p': CONFIG.TRANSCODING.RESOLUTIONS['1440p'],
  204. '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
  205. },
  206. alwaysTranscodeOriginalResolution: CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION,
  207. webVideos: {
  208. enabled: CONFIG.TRANSCODING.WEB_VIDEOS.ENABLED
  209. },
  210. hls: {
  211. enabled: CONFIG.TRANSCODING.HLS.ENABLED
  212. }
  213. },
  214. live: {
  215. enabled: CONFIG.LIVE.ENABLED,
  216. allowReplay: CONFIG.LIVE.ALLOW_REPLAY,
  217. latencySetting: {
  218. enabled: CONFIG.LIVE.LATENCY_SETTING.ENABLED
  219. },
  220. maxDuration: CONFIG.LIVE.MAX_DURATION,
  221. maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES,
  222. maxUserLives: CONFIG.LIVE.MAX_USER_LIVES,
  223. transcoding: {
  224. enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
  225. remoteRunners: {
  226. enabled: CONFIG.LIVE.TRANSCODING.REMOTE_RUNNERS.ENABLED
  227. },
  228. threads: CONFIG.LIVE.TRANSCODING.THREADS,
  229. profile: CONFIG.LIVE.TRANSCODING.PROFILE,
  230. resolutions: {
  231. '144p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['144p'],
  232. '240p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['240p'],
  233. '360p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['360p'],
  234. '480p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['480p'],
  235. '720p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['720p'],
  236. '1080p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1080p'],
  237. '1440p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1440p'],
  238. '2160p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['2160p']
  239. },
  240. alwaysTranscodeOriginalResolution: CONFIG.LIVE.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION
  241. }
  242. },
  243. videoStudio: {
  244. enabled: CONFIG.VIDEO_STUDIO.ENABLED,
  245. remoteRunners: {
  246. enabled: CONFIG.VIDEO_STUDIO.REMOTE_RUNNERS.ENABLED
  247. }
  248. },
  249. videoFile: {
  250. update: {
  251. enabled: CONFIG.VIDEO_FILE.UPDATE.ENABLED
  252. }
  253. },
  254. import: {
  255. videos: {
  256. concurrency: CONFIG.IMPORT.VIDEOS.CONCURRENCY,
  257. http: {
  258. enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
  259. },
  260. torrent: {
  261. enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
  262. }
  263. },
  264. videoChannelSynchronization: {
  265. enabled: CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.ENABLED,
  266. maxPerUser: CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.MAX_PER_USER
  267. }
  268. },
  269. trending: {
  270. videos: {
  271. algorithms: {
  272. enabled: CONFIG.TRENDING.VIDEOS.ALGORITHMS.ENABLED,
  273. default: CONFIG.TRENDING.VIDEOS.ALGORITHMS.DEFAULT
  274. }
  275. }
  276. },
  277. autoBlacklist: {
  278. videos: {
  279. ofUsers: {
  280. enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
  281. }
  282. }
  283. },
  284. followers: {
  285. instance: {
  286. enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
  287. manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
  288. }
  289. },
  290. followings: {
  291. instance: {
  292. autoFollowBack: {
  293. enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED
  294. },
  295. autoFollowIndex: {
  296. enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED,
  297. indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
  298. }
  299. }
  300. },
  301. broadcastMessage: {
  302. enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
  303. message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
  304. level: CONFIG.BROADCAST_MESSAGE.LEVEL,
  305. dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
  306. },
  307. search: {
  308. remoteUri: {
  309. users: CONFIG.SEARCH.REMOTE_URI.USERS,
  310. anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
  311. },
  312. searchIndex: {
  313. enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
  314. url: CONFIG.SEARCH.SEARCH_INDEX.URL,
  315. disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
  316. isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
  317. }
  318. }
  319. }
  320. }
  321. function convertCustomConfigBody (body: CustomConfig) {
  322. function keyConverter (k: string) {
  323. // Transcoding resolutions exception
  324. if (/^\d{3,4}p$/.exec(k)) return k
  325. if (k === '0p') return k
  326. return snakeCase(k)
  327. }
  328. function valueConverter (v: any) {
  329. if (validator.default.isNumeric(v + '')) return parseInt('' + v, 10)
  330. return v
  331. }
  332. return objectConverter(body, keyConverter, valueConverter)
  333. }