]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - server.ts
Update translations
[github/Chocobozzz/PeerTube.git] / server.ts
... / ...
CommitLineData
1// ----------- Node modules -----------
2import { registerOpentelemetryTracing } from './server/lib/opentelemetry/tracing'
3registerOpentelemetryTracing()
4
5import express from 'express'
6import morgan, { token } from 'morgan'
7import cors from 'cors'
8import cookieParser from 'cookie-parser'
9import { frameguard } from 'helmet'
10import { parse } from 'useragent'
11import anonymize from 'ip-anonymize'
12import { program as cli } from 'commander'
13
14process.title = 'peertube'
15
16// Create our main app
17const app = express().disable('x-powered-by')
18
19// ----------- Core checker -----------
20import { checkMissedConfig, checkFFmpeg, checkNodeVersion } from './server/initializers/checker-before-init'
21
22// Do not use barrels because we don't want to load all modules here (we need to initialize database first)
23import { CONFIG } from './server/initializers/config'
24import { API_VERSION, FILES_CACHE, WEBSERVER, loadLanguages } from './server/initializers/constants'
25import { logger } from './server/helpers/logger'
26
27const missed = checkMissedConfig()
28if (missed.length !== 0) {
29 logger.error('Your configuration files miss keys: ' + missed)
30 process.exit(-1)
31}
32
33checkFFmpeg(CONFIG)
34 .catch(err => {
35 logger.error('Error in ffmpeg check.', { err })
36 process.exit(-1)
37 })
38
39try {
40 checkNodeVersion()
41} catch (err) {
42 logger.error('Error in NodeJS check.', { err })
43 process.exit(-1)
44}
45
46import { checkConfig, checkActivityPubUrls, checkFFmpegVersion } from './server/initializers/checker-after-init'
47
48try {
49 checkConfig()
50} catch (err) {
51 logger.error('Config error.', { err })
52 process.exit(-1)
53}
54
55// Trust our proxy (IP forwarding...)
56app.set('trust proxy', CONFIG.TRUST_PROXY)
57
58app.use((_req, res, next) => {
59 // OpenTelemetry
60 res.locals.requestStart = Date.now()
61
62 if (CONFIG.SECURITY.POWERED_BY_HEADER.ENABLED === true) {
63 res.setHeader('x-powered-by', 'PeerTube')
64 }
65
66 return next()
67})
68
69// Security middleware
70import { baseCSP } from './server/middlewares/csp'
71
72if (CONFIG.CSP.ENABLED) {
73 app.use(baseCSP)
74}
75
76if (CONFIG.SECURITY.FRAMEGUARD.ENABLED) {
77 app.use(frameguard({
78 action: 'deny' // we only allow it for /videos/embed, see server/controllers/client.ts
79 }))
80}
81
82// ----------- Database -----------
83
84// Initialize database and models
85import { initDatabaseModels, checkDatabaseConnectionOrDie } from './server/initializers/database'
86checkDatabaseConnectionOrDie()
87
88import { migrate } from './server/initializers/migrator'
89migrate()
90 .then(() => initDatabaseModels(false))
91 .then(() => startApplication())
92 .catch(err => {
93 logger.error('Cannot start application.', { err })
94 process.exit(-1)
95 })
96
97// ----------- Initialize -----------
98loadLanguages()
99
100// ----------- PeerTube modules -----------
101import { installApplication } from './server/initializers/installer'
102import { Emailer } from './server/lib/emailer'
103import { JobQueue } from './server/lib/job-queue'
104import { VideosPreviewCache, VideosCaptionCache } from './server/lib/files-cache'
105import {
106 activityPubRouter,
107 apiRouter,
108 miscRouter,
109 clientsRouter,
110 feedsRouter,
111 staticRouter,
112 wellKnownRouter,
113 lazyStaticRouter,
114 servicesRouter,
115 objectStorageProxyRouter,
116 pluginsRouter,
117 trackerRouter,
118 createWebsocketTrackerServer,
119 botsRouter,
120 downloadRouter
121} from './server/controllers'
122import { advertiseDoNotTrack } from './server/middlewares/dnt'
123import { apiFailMiddleware } from './server/middlewares/error'
124import { Redis } from './server/lib/redis'
125import { ActorFollowScheduler } from './server/lib/schedulers/actor-follow-scheduler'
126import { RemoveOldViewsScheduler } from './server/lib/schedulers/remove-old-views-scheduler'
127import { RemoveOldJobsScheduler } from './server/lib/schedulers/remove-old-jobs-scheduler'
128import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-scheduler'
129import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler'
130import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler'
131import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler'
132import { AutoFollowIndexInstances } from './server/lib/schedulers/auto-follow-index-instances'
133import { RemoveDanglingResumableUploadsScheduler } from './server/lib/schedulers/remove-dangling-resumable-uploads-scheduler'
134import { VideoViewsBufferScheduler } from './server/lib/schedulers/video-views-buffer-scheduler'
135import { GeoIPUpdateScheduler } from './server/lib/schedulers/geo-ip-update-scheduler'
136import { RunnerJobWatchDogScheduler } from './server/lib/schedulers/runner-job-watch-dog-scheduler'
137import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
138import { PeerTubeSocket } from './server/lib/peertube-socket'
139import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
140import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler'
141import { PeerTubeVersionCheckScheduler } from './server/lib/schedulers/peertube-version-check-scheduler'
142import { Hooks } from './server/lib/plugins/hooks'
143import { PluginManager } from './server/lib/plugins/plugin-manager'
144import { LiveManager } from './server/lib/live'
145import { HttpStatusCode } from './shared/models/http/http-error-codes'
146import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache'
147import { ServerConfigManager } from '@server/lib/server-config-manager'
148import { VideoViewsManager } from '@server/lib/views/video-views-manager'
149import { isTestOrDevInstance } from './server/helpers/core-utils'
150import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics'
151import { ApplicationModel } from '@server/models/application/application'
152import { VideoChannelSyncLatestScheduler } from '@server/lib/schedulers/video-channel-sync-latest-scheduler'
153
154// ----------- Command line -----------
155
156cli
157 .option('--no-client', 'Start PeerTube without client interface')
158 .option('--no-plugins', 'Start PeerTube without plugins/themes enabled')
159 .option('--benchmark-startup', 'Automatically stop server when initialized')
160 .parse(process.argv)
161
162// ----------- App -----------
163
164// Enable CORS for develop
165if (isTestOrDevInstance()) {
166 app.use(cors({
167 origin: '*',
168 exposedHeaders: 'Retry-After',
169 credentials: true
170 }))
171}
172
173// For the logger
174token('remote-addr', (req: express.Request) => {
175 if (CONFIG.LOG.ANONYMIZE_IP === true || req.get('DNT') === '1') {
176 return anonymize(req.ip, 16, 16)
177 }
178
179 return req.ip
180})
181token('user-agent', (req: express.Request) => {
182 if (req.get('DNT') === '1') {
183 return parse(req.get('user-agent')).family
184 }
185
186 return req.get('user-agent')
187})
188app.use(morgan('combined', {
189 stream: {
190 write: (str: string) => logger.info(str.trim(), { tags: [ 'http' ] })
191 },
192 skip: req => CONFIG.LOG.LOG_PING_REQUESTS === false && req.originalUrl === '/api/v1/ping'
193}))
194
195// Add .fail() helper to response
196app.use(apiFailMiddleware)
197
198// For body requests
199app.use(express.urlencoded({ extended: false }))
200app.use(express.json({
201 type: [ 'application/json', 'application/*+json' ],
202 limit: '500kb',
203 verify: (req: express.Request, res: express.Response, buf: Buffer) => {
204 const valid = isHTTPSignatureDigestValid(buf, req)
205
206 if (valid !== true) {
207 res.fail({
208 status: HttpStatusCode.FORBIDDEN_403,
209 message: 'Invalid digest'
210 })
211 }
212 }
213}))
214
215// Cookies
216app.use(cookieParser())
217
218// W3C DNT Tracking Status
219app.use(advertiseDoNotTrack)
220
221// ----------- Open Telemetry -----------
222
223OpenTelemetryMetrics.Instance.init(app)
224
225// ----------- Views, routes and static files -----------
226
227// API
228const apiRoute = '/api/' + API_VERSION
229app.use(apiRoute, apiRouter)
230
231// Services (oembed...)
232app.use('/services', servicesRouter)
233
234// Plugins & themes
235app.use('/', pluginsRouter)
236
237app.use('/', activityPubRouter)
238app.use('/', feedsRouter)
239app.use('/', trackerRouter)
240app.use('/', botsRouter)
241
242// Static files
243app.use('/', staticRouter)
244app.use('/', wellKnownRouter)
245app.use('/', miscRouter)
246app.use('/', downloadRouter)
247app.use('/', lazyStaticRouter)
248app.use('/', objectStorageProxyRouter)
249
250// Client files, last valid routes!
251const cliOptions = cli.opts<{ client: boolean, plugins: boolean }>()
252if (cliOptions.client) app.use('/', clientsRouter)
253
254// ----------- Errors -----------
255
256// Catch unmatched routes
257app.use((_req, res: express.Response) => {
258 res.status(HttpStatusCode.NOT_FOUND_404).end()
259})
260
261// Catch thrown errors
262app.use((err, _req, res: express.Response, _next) => {
263 // Format error to be logged
264 let error = 'Unknown error.'
265 if (err) {
266 error = err.stack || err.message || err
267 }
268
269 // Handling Sequelize error traces
270 const sql = err?.parent ? err.parent.sql : undefined
271
272 // Help us to debug SequelizeConnectionAcquireTimeoutError errors
273 const activeRequests = err?.name === 'SequelizeConnectionAcquireTimeoutError' && typeof (process as any)._getActiveRequests !== 'function'
274 ? (process as any)._getActiveRequests()
275 : undefined
276
277 logger.error('Error in controller.', { err: error, sql, activeRequests })
278
279 return res.fail({
280 status: err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500,
281 message: err.message,
282 type: err.name
283 })
284})
285
286const { server, trackerServer } = createWebsocketTrackerServer(app)
287
288// ----------- Run -----------
289
290async function startApplication () {
291 const port = CONFIG.LISTEN.PORT
292 const hostname = CONFIG.LISTEN.HOSTNAME
293
294 await installApplication()
295
296 // Check activity pub urls are valid
297 checkActivityPubUrls()
298 .catch(err => {
299 logger.error('Error in ActivityPub URLs checker.', { err })
300 process.exit(-1)
301 })
302
303 checkFFmpegVersion()
304 .catch(err => logger.error('Cannot check ffmpeg version', { err }))
305
306 Redis.Instance.init()
307 Emailer.Instance.init()
308
309 await Promise.all([
310 Emailer.Instance.checkConnection(),
311 JobQueue.Instance.init(),
312 ServerConfigManager.Instance.init()
313 ])
314
315 // Caches initializations
316 VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, FILES_CACHE.PREVIEWS.MAX_AGE)
317 VideosCaptionCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE)
318 VideosTorrentCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE)
319
320 // Enable Schedulers
321 ActorFollowScheduler.Instance.enable()
322 RemoveOldJobsScheduler.Instance.enable()
323 UpdateVideosScheduler.Instance.enable()
324 YoutubeDlUpdateScheduler.Instance.enable()
325 VideosRedundancyScheduler.Instance.enable()
326 RemoveOldHistoryScheduler.Instance.enable()
327 RemoveOldViewsScheduler.Instance.enable()
328 PluginsCheckScheduler.Instance.enable()
329 PeerTubeVersionCheckScheduler.Instance.enable()
330 AutoFollowIndexInstances.Instance.enable()
331 RemoveDanglingResumableUploadsScheduler.Instance.enable()
332 VideoChannelSyncLatestScheduler.Instance.enable()
333 VideoViewsBufferScheduler.Instance.enable()
334 GeoIPUpdateScheduler.Instance.enable()
335 RunnerJobWatchDogScheduler.Instance.enable()
336
337 OpenTelemetryMetrics.Instance.registerMetrics({ trackerServer })
338
339 PluginManager.Instance.init(server)
340 // Before PeerTubeSocket init
341 PluginManager.Instance.registerWebSocketRouter()
342
343 PeerTubeSocket.Instance.init(server)
344 VideoViewsManager.Instance.init()
345
346 updateStreamingPlaylistsInfohashesIfNeeded()
347 .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
348
349 LiveManager.Instance.init()
350 if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
351
352 // Make server listening
353 server.listen(port, hostname, async () => {
354 if (cliOptions.plugins) {
355 try {
356 await PluginManager.Instance.rebuildNativePluginsIfNeeded()
357
358 await PluginManager.Instance.registerPluginsAndThemes()
359 } catch (err) {
360 logger.error('Cannot register plugins and themes.', { err })
361 }
362 }
363
364 ApplicationModel.updateNodeVersions()
365 .catch(err => logger.error('Cannot update node versions.', { err }))
366
367 JobQueue.Instance.start()
368 .catch(err => {
369 logger.error('Cannot start job queue.', { err })
370 process.exit(-1)
371 })
372
373 logger.info('HTTP server listening on %s:%d', hostname, port)
374 logger.info('Web server: %s', WEBSERVER.URL)
375
376 Hooks.runAction('action:application.listening')
377
378 if (cliOptions['benchmarkStartup']) process.exit(0)
379 })
380
381 process.on('exit', () => {
382 JobQueue.Instance.terminate()
383 .catch(err => logger.error('Cannot terminate job queue.', { err }))
384 })
385
386 process.on('SIGINT', () => process.exit(0))
387}