Federated video streaming platform using ActivityPub and P2P in the web browser with Angular. https://joinpeertube.org/
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.
 
 
 
 
 
 

336 lines
10 KiB

  1. // ----------- Node modules -----------
  2. import express from 'express'
  3. import morgan, { token } from 'morgan'
  4. import cors from 'cors'
  5. import cookieParser from 'cookie-parser'
  6. import { frameguard } from 'helmet'
  7. import { parse } from 'useragent'
  8. import anonymize from 'ip-anonymize'
  9. import { program as cli } from 'commander'
  10. process.title = 'peertube'
  11. // Create our main app
  12. const app = express().disable('x-powered-by')
  13. // ----------- Core checker -----------
  14. import { checkMissedConfig, checkFFmpeg, checkNodeVersion } from './server/initializers/checker-before-init'
  15. // Do not use barrels because we don't want to load all modules here (we need to initialize database first)
  16. import { CONFIG } from './server/initializers/config'
  17. import { API_VERSION, FILES_CACHE, WEBSERVER, loadLanguages } from './server/initializers/constants'
  18. import { logger } from './server/helpers/logger'
  19. const missed = checkMissedConfig()
  20. if (missed.length !== 0) {
  21. logger.error('Your configuration files miss keys: ' + missed)
  22. process.exit(-1)
  23. }
  24. checkFFmpeg(CONFIG)
  25. .catch(err => {
  26. logger.error('Error in ffmpeg check.', { err })
  27. process.exit(-1)
  28. })
  29. try {
  30. checkNodeVersion()
  31. } catch (err) {
  32. logger.error('Error in NodeJS check.', { err })
  33. process.exit(-1)
  34. }
  35. import { checkConfig, checkActivityPubUrls, checkFFmpegVersion } from './server/initializers/checker-after-init'
  36. const errorMessage = checkConfig()
  37. if (errorMessage !== null) {
  38. throw new Error(errorMessage)
  39. }
  40. // Trust our proxy (IP forwarding...)
  41. app.set('trust proxy', CONFIG.TRUST_PROXY)
  42. // Security middleware
  43. import { baseCSP } from './server/middlewares/csp'
  44. if (CONFIG.CSP.ENABLED) {
  45. app.use(baseCSP)
  46. }
  47. if (CONFIG.SECURITY.FRAMEGUARD.ENABLED) {
  48. app.use(frameguard({
  49. action: 'deny' // we only allow it for /videos/embed, see server/controllers/client.ts
  50. }))
  51. }
  52. // ----------- Database -----------
  53. // Initialize database and models
  54. import { initDatabaseModels, checkDatabaseConnectionOrDie } from './server/initializers/database'
  55. checkDatabaseConnectionOrDie()
  56. import { migrate } from './server/initializers/migrator'
  57. migrate()
  58. .then(() => initDatabaseModels(false))
  59. .then(() => startApplication())
  60. .catch(err => {
  61. logger.error('Cannot start application.', { err })
  62. process.exit(-1)
  63. })
  64. // ----------- Initialize -----------
  65. loadLanguages()
  66. // ----------- PeerTube modules -----------
  67. import { installApplication } from './server/initializers/installer'
  68. import { Emailer } from './server/lib/emailer'
  69. import { JobQueue } from './server/lib/job-queue'
  70. import { VideosPreviewCache, VideosCaptionCache } from './server/lib/files-cache'
  71. import {
  72. activityPubRouter,
  73. apiRouter,
  74. clientsRouter,
  75. feedsRouter,
  76. staticRouter,
  77. lazyStaticRouter,
  78. servicesRouter,
  79. liveRouter,
  80. pluginsRouter,
  81. webfingerRouter,
  82. trackerRouter,
  83. createWebsocketTrackerServer,
  84. botsRouter,
  85. downloadRouter
  86. } from './server/controllers'
  87. import { advertiseDoNotTrack } from './server/middlewares/dnt'
  88. import { apiFailMiddleware } from './server/middlewares/error'
  89. import { Redis } from './server/lib/redis'
  90. import { ActorFollowScheduler } from './server/lib/schedulers/actor-follow-scheduler'
  91. import { RemoveOldViewsScheduler } from './server/lib/schedulers/remove-old-views-scheduler'
  92. import { RemoveOldJobsScheduler } from './server/lib/schedulers/remove-old-jobs-scheduler'
  93. import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-scheduler'
  94. import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler'
  95. import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler'
  96. import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler'
  97. import { AutoFollowIndexInstances } from './server/lib/schedulers/auto-follow-index-instances'
  98. import { RemoveDanglingResumableUploadsScheduler } from './server/lib/schedulers/remove-dangling-resumable-uploads-scheduler'
  99. import { VideoViewsBufferScheduler } from './server/lib/schedulers/video-views-buffer-scheduler'
  100. import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
  101. import { PeerTubeSocket } from './server/lib/peertube-socket'
  102. import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
  103. import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler'
  104. import { PeerTubeVersionCheckScheduler } from './server/lib/schedulers/peertube-version-check-scheduler'
  105. import { Hooks } from './server/lib/plugins/hooks'
  106. import { PluginManager } from './server/lib/plugins/plugin-manager'
  107. import { LiveManager } from './server/lib/live'
  108. import { HttpStatusCode } from './shared/models/http/http-error-codes'
  109. import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache'
  110. import { ServerConfigManager } from '@server/lib/server-config-manager'
  111. import { VideoViews } from '@server/lib/video-views'
  112. import { isTestInstance } from './server/helpers/core-utils'
  113. // ----------- Command line -----------
  114. cli
  115. .option('--no-client', 'Start PeerTube without client interface')
  116. .option('--no-plugins', 'Start PeerTube without plugins/themes enabled')
  117. .option('--benchmark-startup', 'Automatically stop server when initialized')
  118. .parse(process.argv)
  119. // ----------- App -----------
  120. // Enable CORS for develop
  121. if (isTestInstance()) {
  122. app.use(cors({
  123. origin: '*',
  124. exposedHeaders: 'Retry-After',
  125. credentials: true
  126. }))
  127. }
  128. // For the logger
  129. token('remote-addr', (req: express.Request) => {
  130. if (CONFIG.LOG.ANONYMIZE_IP === true || req.get('DNT') === '1') {
  131. return anonymize(req.ip, 16, 16)
  132. }
  133. return req.ip
  134. })
  135. token('user-agent', (req: express.Request) => {
  136. if (req.get('DNT') === '1') {
  137. return parse(req.get('user-agent')).family
  138. }
  139. return req.get('user-agent')
  140. })
  141. app.use(morgan('combined', {
  142. stream: {
  143. write: (str: string) => logger.info(str.trim(), { tags: [ 'http' ] })
  144. },
  145. skip: req => CONFIG.LOG.LOG_PING_REQUESTS === false && req.originalUrl === '/api/v1/ping'
  146. }))
  147. // Add .fail() helper to response
  148. app.use(apiFailMiddleware)
  149. // For body requests
  150. app.use(express.urlencoded({ extended: false }))
  151. app.use(express.json({
  152. type: [ 'application/json', 'application/*+json' ],
  153. limit: '500kb',
  154. verify: (req: express.Request, res: express.Response, buf: Buffer) => {
  155. const valid = isHTTPSignatureDigestValid(buf, req)
  156. if (valid !== true) {
  157. res.fail({
  158. status: HttpStatusCode.FORBIDDEN_403,
  159. message: 'Invalid digest'
  160. })
  161. }
  162. }
  163. }))
  164. // Cookies
  165. app.use(cookieParser())
  166. // W3C DNT Tracking Status
  167. app.use(advertiseDoNotTrack)
  168. // ----------- Views, routes and static files -----------
  169. // API
  170. const apiRoute = '/api/' + API_VERSION
  171. app.use(apiRoute, apiRouter)
  172. // Services (oembed...)
  173. app.use('/services', servicesRouter)
  174. // Live streaming
  175. app.use('/live', liveRouter)
  176. // Plugins & themes
  177. app.use('/', pluginsRouter)
  178. app.use('/', activityPubRouter)
  179. app.use('/', feedsRouter)
  180. app.use('/', webfingerRouter)
  181. app.use('/', trackerRouter)
  182. app.use('/', botsRouter)
  183. // Static files
  184. app.use('/', staticRouter)
  185. app.use('/', downloadRouter)
  186. app.use('/', lazyStaticRouter)
  187. // Client files, last valid routes!
  188. const cliOptions = cli.opts()
  189. if (cliOptions.client) app.use('/', clientsRouter)
  190. // ----------- Errors -----------
  191. // Catch unmatched routes
  192. app.use((req, res: express.Response) => {
  193. res.status(HttpStatusCode.NOT_FOUND_404).end()
  194. })
  195. // Catch thrown errors
  196. app.use((err, req, res: express.Response, next) => {
  197. // Format error to be logged
  198. let error = 'Unknown error.'
  199. if (err) {
  200. error = err.stack || err.message || err
  201. }
  202. // Handling Sequelize error traces
  203. const sql = err.parent ? err.parent.sql : undefined
  204. logger.error('Error in controller.', { err: error, sql })
  205. return res.fail({
  206. status: err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500,
  207. message: err.message,
  208. type: err.name
  209. })
  210. })
  211. const server = createWebsocketTrackerServer(app)
  212. // ----------- Run -----------
  213. async function startApplication () {
  214. const port = CONFIG.LISTEN.PORT
  215. const hostname = CONFIG.LISTEN.HOSTNAME
  216. await installApplication()
  217. // Check activity pub urls are valid
  218. checkActivityPubUrls()
  219. .catch(err => {
  220. logger.error('Error in ActivityPub URLs checker.', { err })
  221. process.exit(-1)
  222. })
  223. checkFFmpegVersion()
  224. .catch(err => logger.error('Cannot check ffmpeg version', { err }))
  225. // Email initialization
  226. Emailer.Instance.init()
  227. await Promise.all([
  228. Emailer.Instance.checkConnection(),
  229. JobQueue.Instance.init(),
  230. ServerConfigManager.Instance.init()
  231. ])
  232. // Caches initializations
  233. VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, FILES_CACHE.PREVIEWS.MAX_AGE)
  234. VideosCaptionCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE)
  235. VideosTorrentCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE)
  236. // Enable Schedulers
  237. ActorFollowScheduler.Instance.enable()
  238. RemoveOldJobsScheduler.Instance.enable()
  239. UpdateVideosScheduler.Instance.enable()
  240. YoutubeDlUpdateScheduler.Instance.enable()
  241. VideosRedundancyScheduler.Instance.enable()
  242. RemoveOldHistoryScheduler.Instance.enable()
  243. RemoveOldViewsScheduler.Instance.enable()
  244. PluginsCheckScheduler.Instance.enable()
  245. PeerTubeVersionCheckScheduler.Instance.enable()
  246. AutoFollowIndexInstances.Instance.enable()
  247. RemoveDanglingResumableUploadsScheduler.Instance.enable()
  248. VideoViewsBufferScheduler.Instance.enable()
  249. Redis.Instance.init()
  250. PeerTubeSocket.Instance.init(server)
  251. VideoViews.Instance.init()
  252. updateStreamingPlaylistsInfohashesIfNeeded()
  253. .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
  254. LiveManager.Instance.init()
  255. if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
  256. // Make server listening
  257. server.listen(port, hostname, async () => {
  258. if (cliOptions.plugins) {
  259. try {
  260. await PluginManager.Instance.registerPluginsAndThemes()
  261. } catch (err) {
  262. logger.error('Cannot register plugins and themes.', { err })
  263. }
  264. }
  265. logger.info('HTTP server listening on %s:%d', hostname, port)
  266. logger.info('Web server: %s', WEBSERVER.URL)
  267. Hooks.runAction('action:application.listening')
  268. if (cliOptions['benchmarkStartup']) process.exit(0)
  269. })
  270. process.on('exit', () => {
  271. JobQueue.Instance.terminate()
  272. })
  273. process.on('SIGINT', () => process.exit(0))
  274. }