config.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. import { IConfig } from 'config'
  2. import { dirname, join } from 'path'
  3. import { VideosRedundancyStrategy } from '../../shared/models'
  4. // Do not use barrels, remain constants as independent as possible
  5. import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils'
  6. import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
  7. import * as bytes from 'bytes'
  8. import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type'
  9. import { BroadcastMessageLevel } from '@shared/models/server'
  10. // Use a variable to reload the configuration if we need
  11. let config: IConfig = require('config')
  12. const configChangedHandlers: Function[] = []
  13. const CONFIG = {
  14. CUSTOM_FILE: getLocalConfigFilePath(),
  15. LISTEN: {
  16. PORT: config.get<number>('listen.port'),
  17. HOSTNAME: config.get<string>('listen.hostname')
  18. },
  19. DATABASE: {
  20. DBNAME: config.has('database.name') ? config.get<string>('database.name') : 'peertube' + config.get<string>('database.suffix'),
  21. HOSTNAME: config.get<string>('database.hostname'),
  22. PORT: config.get<number>('database.port'),
  23. SSL: config.get<boolean>('database.ssl'),
  24. USERNAME: config.get<string>('database.username'),
  25. PASSWORD: config.get<string>('database.password'),
  26. POOL: {
  27. MAX: config.get<number>('database.pool.max')
  28. }
  29. },
  30. REDIS: {
  31. HOSTNAME: config.has('redis.hostname') ? config.get<string>('redis.hostname') : null,
  32. PORT: config.has('redis.port') ? config.get<number>('redis.port') : null,
  33. SOCKET: config.has('redis.socket') ? config.get<string>('redis.socket') : null,
  34. AUTH: config.has('redis.auth') ? config.get<string>('redis.auth') : null,
  35. DB: config.has('redis.db') ? config.get<number>('redis.db') : null
  36. },
  37. SMTP: {
  38. TRANSPORT: config.has('smtp.transport') ? config.get<string>('smtp.transport') : 'smtp',
  39. SENDMAIL: config.has('smtp.sendmail') ? config.get<string>('smtp.sendmail') : null,
  40. HOSTNAME: config.get<string>('smtp.hostname'),
  41. PORT: config.get<number>('smtp.port'),
  42. USERNAME: config.get<string>('smtp.username'),
  43. PASSWORD: config.get<string>('smtp.password'),
  44. TLS: config.get<boolean>('smtp.tls'),
  45. DISABLE_STARTTLS: config.get<boolean>('smtp.disable_starttls'),
  46. CA_FILE: config.get<string>('smtp.ca_file'),
  47. FROM_ADDRESS: config.get<string>('smtp.from_address')
  48. },
  49. EMAIL: {
  50. BODY: {
  51. SIGNATURE: config.get<string>('email.body.signature')
  52. },
  53. SUBJECT: {
  54. PREFIX: config.get<string>('email.subject.prefix') + ' '
  55. }
  56. },
  57. STORAGE: {
  58. TMP_DIR: buildPath(config.get<string>('storage.tmp')),
  59. AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
  60. LOG_DIR: buildPath(config.get<string>('storage.logs')),
  61. VIDEOS_DIR: buildPath(config.get<string>('storage.videos')),
  62. STREAMING_PLAYLISTS_DIR: buildPath(config.get<string>('storage.streaming_playlists')),
  63. REDUNDANCY_DIR: buildPath(config.get<string>('storage.redundancy')),
  64. THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')),
  65. PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')),
  66. CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')),
  67. TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')),
  68. CACHE_DIR: buildPath(config.get<string>('storage.cache')),
  69. PLUGINS_DIR: buildPath(config.get<string>('storage.plugins')),
  70. CLIENT_OVERRIDES_DIR: buildPath(config.get<string>('storage.client_overrides'))
  71. },
  72. WEBSERVER: {
  73. SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http',
  74. WS: config.get<boolean>('webserver.https') === true ? 'wss' : 'ws',
  75. HOSTNAME: config.get<string>('webserver.hostname'),
  76. PORT: config.get<number>('webserver.port')
  77. },
  78. RATES_LIMIT: {
  79. API: {
  80. WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.api.window')),
  81. MAX: config.get<number>('rates_limit.api.max')
  82. },
  83. SIGNUP: {
  84. WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.signup.window')),
  85. MAX: config.get<number>('rates_limit.signup.max')
  86. },
  87. LOGIN: {
  88. WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.login.window')),
  89. MAX: config.get<number>('rates_limit.login.max')
  90. },
  91. ASK_SEND_EMAIL: {
  92. WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')),
  93. MAX: config.get<number>('rates_limit.ask_send_email.max')
  94. }
  95. },
  96. TRUST_PROXY: config.get<string[]>('trust_proxy'),
  97. LOG: {
  98. LEVEL: config.get<string>('log.level'),
  99. ROTATION: {
  100. ENABLED: config.get<boolean>('log.rotation.enabled'),
  101. MAX_FILE_SIZE: bytes.parse(config.get<string>('log.rotation.maxFileSize')),
  102. MAX_FILES: config.get<number>('log.rotation.maxFiles')
  103. },
  104. ANONYMIZE_IP: config.get<boolean>('log.anonymizeIP')
  105. },
  106. TRENDING: {
  107. VIDEOS: {
  108. INTERVAL_DAYS: config.get<number>('trending.videos.interval_days')
  109. }
  110. },
  111. REDUNDANCY: {
  112. VIDEOS: {
  113. CHECK_INTERVAL: parseDurationToMs(config.get<string>('redundancy.videos.check_interval')),
  114. STRATEGIES: buildVideosRedundancy(config.get<any[]>('redundancy.videos.strategies'))
  115. }
  116. },
  117. REMOTE_REDUNDANCY: {
  118. VIDEOS: {
  119. ACCEPT_FROM: config.get<VideoRedundancyConfigFilter>('remote_redundancy.videos.accept_from')
  120. }
  121. },
  122. CSP: {
  123. ENABLED: config.get<boolean>('csp.enabled'),
  124. REPORT_ONLY: config.get<boolean>('csp.report_only'),
  125. REPORT_URI: config.get<boolean>('csp.report_uri')
  126. },
  127. TRACKER: {
  128. ENABLED: config.get<boolean>('tracker.enabled'),
  129. PRIVATE: config.get<boolean>('tracker.private'),
  130. REJECT_TOO_MANY_ANNOUNCES: config.get<boolean>('tracker.reject_too_many_announces')
  131. },
  132. HISTORY: {
  133. VIDEOS: {
  134. MAX_AGE: parseDurationToMs(config.get('history.videos.max_age'))
  135. }
  136. },
  137. VIEWS: {
  138. VIDEOS: {
  139. REMOTE: {
  140. MAX_AGE: parseDurationToMs(config.get('views.videos.remote.max_age'))
  141. }
  142. }
  143. },
  144. PLUGINS: {
  145. INDEX: {
  146. ENABLED: config.get<boolean>('plugins.index.enabled'),
  147. CHECK_LATEST_VERSIONS_INTERVAL: parseDurationToMs(config.get<string>('plugins.index.check_latest_versions_interval')),
  148. URL: config.get<string>('plugins.index.url')
  149. }
  150. },
  151. FEDERATION: {
  152. VIDEOS: {
  153. FEDERATE_UNLISTED: config.get<boolean>('federation.videos.federate_unlisted')
  154. }
  155. },
  156. ADMIN: {
  157. get EMAIL () { return config.get<string>('admin.email') }
  158. },
  159. CONTACT_FORM: {
  160. get ENABLED () { return config.get<boolean>('contact_form.enabled') }
  161. },
  162. SIGNUP: {
  163. get ENABLED () { return config.get<boolean>('signup.enabled') },
  164. get LIMIT () { return config.get<number>('signup.limit') },
  165. get REQUIRES_EMAIL_VERIFICATION () { return config.get<boolean>('signup.requires_email_verification') },
  166. FILTERS: {
  167. CIDR: {
  168. get WHITELIST () { return config.get<string[]>('signup.filters.cidr.whitelist') },
  169. get BLACKLIST () { return config.get<string[]>('signup.filters.cidr.blacklist') }
  170. }
  171. }
  172. },
  173. USER: {
  174. get VIDEO_QUOTA () { return parseBytes(config.get<number>('user.video_quota')) },
  175. get VIDEO_QUOTA_DAILY () { return parseBytes(config.get<number>('user.video_quota_daily')) }
  176. },
  177. TRANSCODING: {
  178. get ENABLED () { return config.get<boolean>('transcoding.enabled') },
  179. get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
  180. get ALLOW_AUDIO_FILES () { return config.get<boolean>('transcoding.allow_audio_files') },
  181. get THREADS () { return config.get<number>('transcoding.threads') },
  182. RESOLUTIONS: {
  183. get '0p' () { return config.get<boolean>('transcoding.resolutions.0p') },
  184. get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
  185. get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') },
  186. get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') },
  187. get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') },
  188. get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') },
  189. get '2160p' () { return config.get<boolean>('transcoding.resolutions.2160p') }
  190. },
  191. HLS: {
  192. get ENABLED () { return config.get<boolean>('transcoding.hls.enabled') }
  193. },
  194. WEBTORRENT: {
  195. get ENABLED () { return config.get<boolean>('transcoding.webtorrent.enabled') }
  196. }
  197. },
  198. IMPORT: {
  199. VIDEOS: {
  200. HTTP: {
  201. get ENABLED () { return config.get<boolean>('import.videos.http.enabled') },
  202. PROXY: {
  203. get ENABLED () { return config.get<boolean>('import.videos.http.proxy.enabled') },
  204. get URL () { return config.get<string>('import.videos.http.proxy.url') }
  205. }
  206. },
  207. TORRENT: {
  208. get ENABLED () { return config.get<boolean>('import.videos.torrent.enabled') }
  209. }
  210. }
  211. },
  212. AUTO_BLACKLIST: {
  213. VIDEOS: {
  214. OF_USERS: {
  215. get ENABLED () { return config.get<boolean>('auto_blacklist.videos.of_users.enabled') }
  216. }
  217. }
  218. },
  219. CACHE: {
  220. PREVIEWS: {
  221. get SIZE () { return config.get<number>('cache.previews.size') }
  222. },
  223. VIDEO_CAPTIONS: {
  224. get SIZE () { return config.get<number>('cache.captions.size') }
  225. }
  226. },
  227. INSTANCE: {
  228. get NAME () { return config.get<string>('instance.name') },
  229. get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') },
  230. get DESCRIPTION () { return config.get<string>('instance.description') },
  231. get TERMS () { return config.get<string>('instance.terms') },
  232. get CODE_OF_CONDUCT () { return config.get<string>('instance.code_of_conduct') },
  233. get CREATION_REASON () { return config.get<string>('instance.creation_reason') },
  234. get MODERATION_INFORMATION () { return config.get<string>('instance.moderation_information') },
  235. get ADMINISTRATOR () { return config.get<string>('instance.administrator') },
  236. get MAINTENANCE_LIFETIME () { return config.get<string>('instance.maintenance_lifetime') },
  237. get BUSINESS_MODEL () { return config.get<string>('instance.business_model') },
  238. get HARDWARE_INFORMATION () { return config.get<string>('instance.hardware_information') },
  239. get LANGUAGES () { return config.get<string[]>('instance.languages') || [] },
  240. get CATEGORIES () { return config.get<number[]>('instance.categories') || [] },
  241. get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') },
  242. get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
  243. get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') },
  244. CUSTOMIZATIONS: {
  245. get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
  246. get CSS () { return config.get<string>('instance.customizations.css') }
  247. },
  248. get ROBOTS () { return config.get<string>('instance.robots') },
  249. get SECURITYTXT () { return config.get<string>('instance.securitytxt') },
  250. get SECURITYTXT_CONTACT () { return config.get<string>('admin.email') }
  251. },
  252. SERVICES: {
  253. TWITTER: {
  254. get USERNAME () { return config.get<string>('services.twitter.username') },
  255. get WHITELISTED () { return config.get<boolean>('services.twitter.whitelisted') }
  256. }
  257. },
  258. FOLLOWERS: {
  259. INSTANCE: {
  260. get ENABLED () { return config.get<boolean>('followers.instance.enabled') },
  261. get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') }
  262. }
  263. },
  264. FOLLOWINGS: {
  265. INSTANCE: {
  266. AUTO_FOLLOW_BACK: {
  267. get ENABLED () {
  268. return config.get<boolean>('followings.instance.auto_follow_back.enabled')
  269. }
  270. },
  271. AUTO_FOLLOW_INDEX: {
  272. get ENABLED () {
  273. return config.get<boolean>('followings.instance.auto_follow_index.enabled')
  274. },
  275. get INDEX_URL () {
  276. return config.get<string>('followings.instance.auto_follow_index.index_url')
  277. }
  278. }
  279. }
  280. },
  281. THEME: {
  282. get DEFAULT () { return config.get<string>('theme.default') }
  283. },
  284. BROADCAST_MESSAGE: {
  285. get ENABLED () { return config.get<boolean>('broadcast_message.enabled') },
  286. get MESSAGE () { return config.get<string>('broadcast_message.message') },
  287. get LEVEL () { return config.get<BroadcastMessageLevel>('broadcast_message.level') },
  288. get DISMISSABLE () { return config.get<boolean>('broadcast_message.dismissable') }
  289. },
  290. SEARCH: {
  291. REMOTE_URI: {
  292. USERS: config.get<boolean>('search.remote_uri.users'),
  293. ANONYMOUS: config.get<boolean>('search.remote_uri.anonymous')
  294. },
  295. SEARCH_INDEX: {
  296. get ENABLED () { return config.get<boolean>('search.search_index.enabled') },
  297. get URL () { return config.get<string>('search.search_index.url') },
  298. get DISABLE_LOCAL_SEARCH () { return config.get<boolean>('search.search_index.disable_local_search') },
  299. get IS_DEFAULT_SEARCH () { return config.get<boolean>('search.search_index.is_default_search') }
  300. }
  301. }
  302. }
  303. function registerConfigChangedHandler (fun: Function) {
  304. configChangedHandlers.push(fun)
  305. }
  306. function isEmailEnabled () {
  307. return !!CONFIG.SMTP.HOSTNAME && !!CONFIG.SMTP.PORT
  308. }
  309. // ---------------------------------------------------------------------------
  310. export {
  311. CONFIG,
  312. registerConfigChangedHandler,
  313. isEmailEnabled
  314. }
  315. // ---------------------------------------------------------------------------
  316. function getLocalConfigFilePath () {
  317. const configSources = config.util.getConfigSources()
  318. if (configSources.length === 0) throw new Error('Invalid config source.')
  319. let filename = 'local'
  320. if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
  321. if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
  322. return join(dirname(configSources[0].name), filename + '.json')
  323. }
  324. function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] {
  325. if (!objs) return []
  326. if (!Array.isArray(objs)) return objs
  327. return objs.map(obj => {
  328. return Object.assign({}, obj, {
  329. minLifetime: parseDurationToMs(obj.min_lifetime),
  330. size: bytes.parse(obj.size),
  331. minViews: obj.min_views
  332. })
  333. })
  334. }
  335. export function reloadConfig () {
  336. function directory () {
  337. if (process.env.NODE_CONFIG_DIR) {
  338. return process.env.NODE_CONFIG_DIR
  339. }
  340. return join(root(), 'config')
  341. }
  342. function purge () {
  343. for (const fileName in require.cache) {
  344. if (fileName.includes(directory()) === false) {
  345. continue
  346. }
  347. delete require.cache[fileName]
  348. }
  349. delete require.cache[require.resolve('config')]
  350. }
  351. purge()
  352. config = require('config')
  353. for (const configChangedHandler of configChangedHandlers) {
  354. configChangedHandler()
  355. }
  356. }