ニジカ投稿局 https://tv.nizika.tv
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

config.ts 16 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. import { About, ActorImageType, ActorImageType_Type, CustomConfig, HttpStatusCode, UserRight } from '@peertube/peertube-models'
  2. import { createReqFiles } from '@server/helpers/express-utils.js'
  3. import { MIMETYPES } from '@server/initializers/constants.js'
  4. import { deleteLocalActorImageFile, updateLocalActorImageFiles } from '@server/lib/local-actor.js'
  5. import { ServerConfigManager } from '@server/lib/server-config-manager.js'
  6. import { ActorImageModel } from '@server/models/actor/actor-image.js'
  7. import { getServerActor } from '@server/models/application/application.js'
  8. import { ModelCache } from '@server/models/shared/model-cache.js'
  9. import express from 'express'
  10. import { remove, writeJSON } from 'fs-extra/esm'
  11. import snakeCase from 'lodash-es/snakeCase.js'
  12. import validator from 'validator'
  13. import { CustomConfigAuditView, auditLoggerFactory, getAuditIdFromRes } from '../../helpers/audit-logger.js'
  14. import { objectConverter } from '../../helpers/core-utils.js'
  15. import { CONFIG, reloadConfig } from '../../initializers/config.js'
  16. import { ClientHtml } from '../../lib/html/client-html.js'
  17. import {
  18. apiRateLimiter,
  19. asyncMiddleware,
  20. authenticate,
  21. ensureUserHasRight,
  22. openapiOperationDoc,
  23. updateAvatarValidator,
  24. updateBannerValidator
  25. } from '../../middlewares/index.js'
  26. import { customConfigUpdateValidator, ensureConfigIsEditable } from '../../middlewares/validators/config.js'
  27. const configRouter = express.Router()
  28. configRouter.use(apiRateLimiter)
  29. const auditLogger = auditLoggerFactory('config')
  30. configRouter.get('/',
  31. openapiOperationDoc({ operationId: 'getConfig' }),
  32. asyncMiddleware(getConfig)
  33. )
  34. configRouter.get('/about',
  35. openapiOperationDoc({ operationId: 'getAbout' }),
  36. asyncMiddleware(getAbout)
  37. )
  38. configRouter.get('/custom',
  39. openapiOperationDoc({ operationId: 'getCustomConfig' }),
  40. authenticate,
  41. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  42. getCustomConfig
  43. )
  44. configRouter.put('/custom',
  45. openapiOperationDoc({ operationId: 'putCustomConfig' }),
  46. authenticate,
  47. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  48. ensureConfigIsEditable,
  49. customConfigUpdateValidator,
  50. asyncMiddleware(updateCustomConfig)
  51. )
  52. configRouter.delete('/custom',
  53. openapiOperationDoc({ operationId: 'delCustomConfig' }),
  54. authenticate,
  55. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  56. ensureConfigIsEditable,
  57. asyncMiddleware(deleteCustomConfig)
  58. )
  59. // ---------------------------------------------------------------------------
  60. configRouter.post('/instance-banner/pick',
  61. authenticate,
  62. createReqFiles([ 'bannerfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT),
  63. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  64. updateBannerValidator,
  65. asyncMiddleware(updateInstanceImageFactory(ActorImageType.BANNER))
  66. )
  67. configRouter.delete('/instance-banner',
  68. authenticate,
  69. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  70. asyncMiddleware(deleteInstanceImageFactory(ActorImageType.BANNER))
  71. )
  72. // ---------------------------------------------------------------------------
  73. configRouter.post('/instance-avatar/pick',
  74. authenticate,
  75. createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT),
  76. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  77. updateAvatarValidator,
  78. asyncMiddleware(updateInstanceImageFactory(ActorImageType.AVATAR))
  79. )
  80. configRouter.delete('/instance-avatar',
  81. authenticate,
  82. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  83. asyncMiddleware(deleteInstanceImageFactory(ActorImageType.AVATAR))
  84. )
  85. // ---------------------------------------------------------------------------
  86. async function getConfig (req: express.Request, res: express.Response) {
  87. const json = await ServerConfigManager.Instance.getServerConfig(req.ip)
  88. return res.json(json)
  89. }
  90. async function getAbout (req: express.Request, res: express.Response) {
  91. const serverActor = await getServerActor()
  92. const about: About = {
  93. instance: {
  94. name: CONFIG.INSTANCE.NAME,
  95. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  96. description: CONFIG.INSTANCE.DESCRIPTION,
  97. terms: CONFIG.INSTANCE.TERMS,
  98. codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
  99. hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
  100. creationReason: CONFIG.INSTANCE.CREATION_REASON,
  101. moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
  102. administrator: CONFIG.INSTANCE.ADMINISTRATOR,
  103. maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
  104. businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
  105. languages: CONFIG.INSTANCE.LANGUAGES,
  106. categories: CONFIG.INSTANCE.CATEGORIES,
  107. banners: serverActor.Banners.map(b => b.toFormattedJSON()),
  108. avatars: serverActor.Avatars.map(a => a.toFormattedJSON())
  109. }
  110. }
  111. return res.json(about)
  112. }
  113. function getCustomConfig (req: express.Request, res: express.Response) {
  114. const data = customConfig()
  115. return res.json(data)
  116. }
  117. async function deleteCustomConfig (req: express.Request, res: express.Response) {
  118. await remove(CONFIG.CUSTOM_FILE)
  119. auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
  120. await reloadConfig()
  121. ClientHtml.invalidateCache()
  122. const data = customConfig()
  123. return res.json(data)
  124. }
  125. async function updateCustomConfig (req: express.Request, res: express.Response) {
  126. const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
  127. // camelCase to snake_case key + Force number conversion
  128. const toUpdateJSON = convertCustomConfigBody(req.body)
  129. await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
  130. await reloadConfig()
  131. ClientHtml.invalidateCache()
  132. const data = customConfig()
  133. auditLogger.update(
  134. getAuditIdFromRes(res),
  135. new CustomConfigAuditView(data),
  136. oldCustomConfigAuditKeys
  137. )
  138. return res.json(data)
  139. }
  140. // ---------------------------------------------------------------------------
  141. function updateInstanceImageFactory (imageType: ActorImageType_Type) {
  142. return async (req: express.Request, res: express.Response) => {
  143. const field = imageType === ActorImageType.BANNER
  144. ? 'bannerfile'
  145. : 'avatarfile'
  146. const imagePhysicalFile = req.files[field][0]
  147. await updateLocalActorImageFiles({
  148. accountOrChannel: (await getServerActorWithUpdatedImages(imageType)).Account,
  149. imagePhysicalFile,
  150. type: imageType,
  151. sendActorUpdate: false
  152. })
  153. ClientHtml.invalidateCache()
  154. ModelCache.Instance.clearCache('server-account')
  155. return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
  156. }
  157. }
  158. function deleteInstanceImageFactory (imageType: ActorImageType_Type) {
  159. return async (req: express.Request, res: express.Response) => {
  160. await deleteLocalActorImageFile((await getServerActorWithUpdatedImages(imageType)).Account, imageType)
  161. ClientHtml.invalidateCache()
  162. ModelCache.Instance.clearCache('server-account')
  163. return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
  164. }
  165. }
  166. async function getServerActorWithUpdatedImages (imageType: ActorImageType_Type) {
  167. const serverActor = await getServerActor()
  168. const updatedImages = await ActorImageModel.listByActor(serverActor, imageType) // Reload images from DB
  169. if (imageType === ActorImageType.BANNER) serverActor.Banners = updatedImages
  170. else serverActor.Avatars = updatedImages
  171. return serverActor
  172. }
  173. // ---------------------------------------------------------------------------
  174. export {
  175. configRouter
  176. }
  177. // ---------------------------------------------------------------------------
  178. function customConfig (): CustomConfig {
  179. return {
  180. instance: {
  181. name: CONFIG.INSTANCE.NAME,
  182. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  183. description: CONFIG.INSTANCE.DESCRIPTION,
  184. terms: CONFIG.INSTANCE.TERMS,
  185. codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
  186. creationReason: CONFIG.INSTANCE.CREATION_REASON,
  187. moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
  188. administrator: CONFIG.INSTANCE.ADMINISTRATOR,
  189. maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
  190. businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
  191. hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
  192. languages: CONFIG.INSTANCE.LANGUAGES,
  193. categories: CONFIG.INSTANCE.CATEGORIES,
  194. isNSFW: CONFIG.INSTANCE.IS_NSFW,
  195. defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
  196. defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
  197. customizations: {
  198. css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
  199. javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
  200. }
  201. },
  202. theme: {
  203. default: CONFIG.THEME.DEFAULT
  204. },
  205. services: {
  206. twitter: {
  207. username: CONFIG.SERVICES.TWITTER.USERNAME
  208. }
  209. },
  210. client: {
  211. videos: {
  212. miniature: {
  213. preferAuthorDisplayName: CONFIG.CLIENT.VIDEOS.MINIATURE.PREFER_AUTHOR_DISPLAY_NAME
  214. }
  215. },
  216. menu: {
  217. login: {
  218. redirectOnSingleExternalAuth: CONFIG.CLIENT.MENU.LOGIN.REDIRECT_ON_SINGLE_EXTERNAL_AUTH
  219. }
  220. }
  221. },
  222. cache: {
  223. previews: {
  224. size: CONFIG.CACHE.PREVIEWS.SIZE
  225. },
  226. captions: {
  227. size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
  228. },
  229. torrents: {
  230. size: CONFIG.CACHE.TORRENTS.SIZE
  231. },
  232. storyboards: {
  233. size: CONFIG.CACHE.STORYBOARDS.SIZE
  234. }
  235. },
  236. signup: {
  237. enabled: CONFIG.SIGNUP.ENABLED,
  238. limit: CONFIG.SIGNUP.LIMIT,
  239. requiresApproval: CONFIG.SIGNUP.REQUIRES_APPROVAL,
  240. requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION,
  241. minimumAge: CONFIG.SIGNUP.MINIMUM_AGE
  242. },
  243. admin: {
  244. email: CONFIG.ADMIN.EMAIL
  245. },
  246. contactForm: {
  247. enabled: CONFIG.CONTACT_FORM.ENABLED
  248. },
  249. user: {
  250. history: {
  251. videos: {
  252. enabled: CONFIG.USER.HISTORY.VIDEOS.ENABLED
  253. }
  254. },
  255. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  256. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY,
  257. defaultChannelName: CONFIG.USER.DEFAULT_CHANNEL_NAME
  258. },
  259. videoChannels: {
  260. maxPerUser: CONFIG.VIDEO_CHANNELS.MAX_PER_USER
  261. },
  262. transcoding: {
  263. enabled: CONFIG.TRANSCODING.ENABLED,
  264. originalFile: {
  265. keep: CONFIG.TRANSCODING.ORIGINAL_FILE.KEEP
  266. },
  267. remoteRunners: {
  268. enabled: CONFIG.TRANSCODING.REMOTE_RUNNERS.ENABLED
  269. },
  270. allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
  271. allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
  272. threads: CONFIG.TRANSCODING.THREADS,
  273. concurrency: CONFIG.TRANSCODING.CONCURRENCY,
  274. profile: CONFIG.TRANSCODING.PROFILE,
  275. resolutions: {
  276. '0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
  277. '144p': CONFIG.TRANSCODING.RESOLUTIONS['144p'],
  278. '240p': CONFIG.TRANSCODING.RESOLUTIONS['240p'],
  279. '360p': CONFIG.TRANSCODING.RESOLUTIONS['360p'],
  280. '480p': CONFIG.TRANSCODING.RESOLUTIONS['480p'],
  281. '720p': CONFIG.TRANSCODING.RESOLUTIONS['720p'],
  282. '1080p': CONFIG.TRANSCODING.RESOLUTIONS['1080p'],
  283. '1440p': CONFIG.TRANSCODING.RESOLUTIONS['1440p'],
  284. '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
  285. },
  286. alwaysTranscodeOriginalResolution: CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION,
  287. webVideos: {
  288. enabled: CONFIG.TRANSCODING.WEB_VIDEOS.ENABLED
  289. },
  290. hls: {
  291. enabled: CONFIG.TRANSCODING.HLS.ENABLED
  292. }
  293. },
  294. live: {
  295. enabled: CONFIG.LIVE.ENABLED,
  296. allowReplay: CONFIG.LIVE.ALLOW_REPLAY,
  297. latencySetting: {
  298. enabled: CONFIG.LIVE.LATENCY_SETTING.ENABLED
  299. },
  300. maxDuration: CONFIG.LIVE.MAX_DURATION,
  301. maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES,
  302. maxUserLives: CONFIG.LIVE.MAX_USER_LIVES,
  303. transcoding: {
  304. enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
  305. remoteRunners: {
  306. enabled: CONFIG.LIVE.TRANSCODING.REMOTE_RUNNERS.ENABLED
  307. },
  308. threads: CONFIG.LIVE.TRANSCODING.THREADS,
  309. profile: CONFIG.LIVE.TRANSCODING.PROFILE,
  310. resolutions: {
  311. '144p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['144p'],
  312. '240p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['240p'],
  313. '360p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['360p'],
  314. '480p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['480p'],
  315. '720p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['720p'],
  316. '1080p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1080p'],
  317. '1440p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1440p'],
  318. '2160p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['2160p']
  319. },
  320. alwaysTranscodeOriginalResolution: CONFIG.LIVE.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION
  321. }
  322. },
  323. videoStudio: {
  324. enabled: CONFIG.VIDEO_STUDIO.ENABLED,
  325. remoteRunners: {
  326. enabled: CONFIG.VIDEO_STUDIO.REMOTE_RUNNERS.ENABLED
  327. }
  328. },
  329. videoTranscription: {
  330. enabled: CONFIG.VIDEO_TRANSCRIPTION.ENABLED,
  331. remoteRunners: {
  332. enabled: CONFIG.VIDEO_TRANSCRIPTION.REMOTE_RUNNERS.ENABLED
  333. }
  334. },
  335. videoFile: {
  336. update: {
  337. enabled: CONFIG.VIDEO_FILE.UPDATE.ENABLED
  338. }
  339. },
  340. import: {
  341. videos: {
  342. concurrency: CONFIG.IMPORT.VIDEOS.CONCURRENCY,
  343. http: {
  344. enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
  345. },
  346. torrent: {
  347. enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
  348. }
  349. },
  350. videoChannelSynchronization: {
  351. enabled: CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.ENABLED,
  352. maxPerUser: CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.MAX_PER_USER
  353. },
  354. users: {
  355. enabled: CONFIG.IMPORT.USERS.ENABLED
  356. }
  357. },
  358. export: {
  359. users: {
  360. enabled: CONFIG.EXPORT.USERS.ENABLED,
  361. exportExpiration: CONFIG.EXPORT.USERS.EXPORT_EXPIRATION,
  362. maxUserVideoQuota: CONFIG.EXPORT.USERS.MAX_USER_VIDEO_QUOTA
  363. }
  364. },
  365. trending: {
  366. videos: {
  367. algorithms: {
  368. enabled: CONFIG.TRENDING.VIDEOS.ALGORITHMS.ENABLED,
  369. default: CONFIG.TRENDING.VIDEOS.ALGORITHMS.DEFAULT
  370. }
  371. }
  372. },
  373. autoBlacklist: {
  374. videos: {
  375. ofUsers: {
  376. enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
  377. }
  378. }
  379. },
  380. followers: {
  381. instance: {
  382. enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
  383. manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
  384. }
  385. },
  386. followings: {
  387. instance: {
  388. autoFollowBack: {
  389. enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED
  390. },
  391. autoFollowIndex: {
  392. enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED,
  393. indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
  394. }
  395. }
  396. },
  397. broadcastMessage: {
  398. enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
  399. message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
  400. level: CONFIG.BROADCAST_MESSAGE.LEVEL,
  401. dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
  402. },
  403. search: {
  404. remoteUri: {
  405. users: CONFIG.SEARCH.REMOTE_URI.USERS,
  406. anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
  407. },
  408. searchIndex: {
  409. enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
  410. url: CONFIG.SEARCH.SEARCH_INDEX.URL,
  411. disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
  412. isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
  413. }
  414. },
  415. storyboards: {
  416. enabled: CONFIG.STORYBOARDS.ENABLED
  417. }
  418. }
  419. }
  420. function convertCustomConfigBody (body: CustomConfig) {
  421. function keyConverter (k: string) {
  422. // Transcoding resolutions exception
  423. if (/^\d{3,4}p$/.exec(k)) return k
  424. if (k === '0p') return k
  425. return snakeCase(k)
  426. }
  427. function valueConverter (v: any) {
  428. if (validator.default.isNumeric(v + '')) return parseInt('' + v, 10)
  429. return v
  430. }
  431. return objectConverter(body, keyConverter, valueConverter)
  432. }