]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - server.ts
Don't display alt for actor images
[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 { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
137import { PeerTubeSocket } from './server/lib/peertube-socket'
138import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
139import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler'
140import { PeerTubeVersionCheckScheduler } from './server/lib/schedulers/peertube-version-check-scheduler'
141import { Hooks } from './server/lib/plugins/hooks'
142import { PluginManager } from './server/lib/plugins/plugin-manager'
143import { LiveManager } from './server/lib/live'
144import { HttpStatusCode } from './shared/models/http/http-error-codes'
145import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache'
146import { ServerConfigManager } from '@server/lib/server-config-manager'
147import { VideoViewsManager } from '@server/lib/views/video-views-manager'
148import { isTestOrDevInstance } from './server/helpers/core-utils'
149import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics'
150import { ApplicationModel } from '@server/models/application/application'
151import { VideoChannelSyncLatestScheduler } from '@server/lib/schedulers/video-channel-sync-latest-scheduler'
152
153// ----------- Command line -----------
154
155cli
156 .option('--no-client', 'Start PeerTube without client interface')
157 .option('--no-plugins', 'Start PeerTube without plugins/themes enabled')
158 .option('--benchmark-startup', 'Automatically stop server when initialized')
159 .parse(process.argv)
160
161// ----------- App -----------
162
163// Enable CORS for develop
164if (isTestOrDevInstance()) {
165 app.use(cors({
166 origin: '*',
167 exposedHeaders: 'Retry-After',
168 credentials: true
169 }))
170}
171
172// For the logger
173token('remote-addr', (req: express.Request) => {
174 if (CONFIG.LOG.ANONYMIZE_IP === true || req.get('DNT') === '1') {
175 return anonymize(req.ip, 16, 16)
176 }
177
178 return req.ip
179})
180token('user-agent', (req: express.Request) => {
181 if (req.get('DNT') === '1') {
182 return parse(req.get('user-agent')).family
183 }
184
185 return req.get('user-agent')
186})
187app.use(morgan('combined', {
188 stream: {
189 write: (str: string) => logger.info(str.trim(), { tags: [ 'http' ] })
190 },
191 skip: req => CONFIG.LOG.LOG_PING_REQUESTS === false && req.originalUrl === '/api/v1/ping'
192}))
193
194// Add .fail() helper to response
195app.use(apiFailMiddleware)
196
197// For body requests
198app.use(express.urlencoded({ extended: false }))
199app.use(express.json({
200 type: [ 'application/json', 'application/*+json' ],
201 limit: '500kb',
202 verify: (req: express.Request, res: express.Response, buf: Buffer) => {
203 const valid = isHTTPSignatureDigestValid(buf, req)
204
205 if (valid !== true) {
206 res.fail({
207 status: HttpStatusCode.FORBIDDEN_403,
208 message: 'Invalid digest'
209 })
210 }
211 }
212}))
213
214// Cookies
215app.use(cookieParser())
216
217// W3C DNT Tracking Status
218app.use(advertiseDoNotTrack)
219
220// ----------- Open Telemetry -----------
221
222OpenTelemetryMetrics.Instance.init(app)
223
224// ----------- Views, routes and static files -----------
225
226// API
227const apiRoute = '/api/' + API_VERSION
228app.use(apiRoute, apiRouter)
229
230// Services (oembed...)
231app.use('/services', servicesRouter)
232
233// Plugins & themes
234app.use('/', pluginsRouter)
235
236app.use('/', activityPubRouter)
237app.use('/', feedsRouter)
238app.use('/', trackerRouter)
239app.use('/', botsRouter)
240
241// Static files
242app.use('/', staticRouter)
243app.use('/', wellKnownRouter)
244app.use('/', miscRouter)
245app.use('/', downloadRouter)
246app.use('/', lazyStaticRouter)
247app.use('/', objectStorageProxyRouter)
248
249// Client files, last valid routes!
250const cliOptions = cli.opts<{ client: boolean, plugins: boolean }>()
251if (cliOptions.client) app.use('/', clientsRouter)
252
253// ----------- Errors -----------
254
255// Catch unmatched routes
256app.use((_req, res: express.Response) => {
257 res.status(HttpStatusCode.NOT_FOUND_404).end()
258})
259
260// Catch thrown errors
261app.use((err, _req, res: express.Response, _next) => {
262 // Format error to be logged
263 let error = 'Unknown error.'
264 if (err) {
265 error = err.stack || err.message || err
266 }
267
268 // Handling Sequelize error traces
269 const sql = err?.parent ? err.parent.sql : undefined
270
271 // Help us to debug SequelizeConnectionAcquireTimeoutError errors
272 const activeRequests = err?.name === 'SequelizeConnectionAcquireTimeoutError' && typeof (process as any)._getActiveRequests !== 'function'
273 ? (process as any)._getActiveRequests()
274 : undefined
275
276 logger.error('Error in controller.', { err: error, sql, activeRequests })
277
278 return res.fail({
279 status: err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500,
280 message: err.message,
281 type: err.name
282 })
283})
284
285const { server, trackerServer } = createWebsocketTrackerServer(app)
286
287// ----------- Run -----------
288
289async function startApplication () {
290 const port = CONFIG.LISTEN.PORT
291 const hostname = CONFIG.LISTEN.HOSTNAME
292
293 await installApplication()
294
295 // Check activity pub urls are valid
296 checkActivityPubUrls()
297 .catch(err => {
298 logger.error('Error in ActivityPub URLs checker.', { err })
299 process.exit(-1)
300 })
301
302 checkFFmpegVersion()
303 .catch(err => logger.error('Cannot check ffmpeg version', { err }))
304
305 Redis.Instance.init()
306 Emailer.Instance.init()
307
308 await Promise.all([
309 Emailer.Instance.checkConnection(),
310 JobQueue.Instance.init(),
311 ServerConfigManager.Instance.init()
312 ])
313
314 // Caches initializations
315 VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, FILES_CACHE.PREVIEWS.MAX_AGE)
316 VideosCaptionCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE)
317 VideosTorrentCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE)
318
319 // Enable Schedulers
320 ActorFollowScheduler.Instance.enable()
321 RemoveOldJobsScheduler.Instance.enable()
322 UpdateVideosScheduler.Instance.enable()
323 YoutubeDlUpdateScheduler.Instance.enable()
324 VideosRedundancyScheduler.Instance.enable()
325 RemoveOldHistoryScheduler.Instance.enable()
326 RemoveOldViewsScheduler.Instance.enable()
327 PluginsCheckScheduler.Instance.enable()
328 PeerTubeVersionCheckScheduler.Instance.enable()
329 AutoFollowIndexInstances.Instance.enable()
330 RemoveDanglingResumableUploadsScheduler.Instance.enable()
331 VideoChannelSyncLatestScheduler.Instance.enable()
332 VideoViewsBufferScheduler.Instance.enable()
333 GeoIPUpdateScheduler.Instance.enable()
334
335 OpenTelemetryMetrics.Instance.registerMetrics({ trackerServer })
336
337 PluginManager.Instance.init(server)
338 // Before PeerTubeSocket init
339 PluginManager.Instance.registerWebSocketRouter()
340
341 PeerTubeSocket.Instance.init(server)
342 VideoViewsManager.Instance.init()
343
344 updateStreamingPlaylistsInfohashesIfNeeded()
345 .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
346
347 LiveManager.Instance.init()
348 if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
349
350 // Make server listening
351 server.listen(port, hostname, async () => {
352 if (cliOptions.plugins) {
353 try {
354 await PluginManager.Instance.rebuildNativePluginsIfNeeded()
355
356 await PluginManager.Instance.registerPluginsAndThemes()
357 } catch (err) {
358 logger.error('Cannot register plugins and themes.', { err })
359 }
360 }
361
362 ApplicationModel.updateNodeVersions()
363 .catch(err => logger.error('Cannot update node versions.', { err }))
364
365 JobQueue.Instance.start()
366 .catch(err => {
367 logger.error('Cannot start job queue.', { err })
368 process.exit(-1)
369 })
370
371 logger.info('HTTP server listening on %s:%d', hostname, port)
372 logger.info('Web server: %s', WEBSERVER.URL)
373
374 Hooks.runAction('action:application.listening')
375
376 if (cliOptions['benchmarkStartup']) process.exit(0)
377 })
378
379 process.on('exit', () => {
380 JobQueue.Instance.terminate()
381 .catch(err => logger.error('Cannot terminate job queue.', { err }))
382 })
383
384 process.on('SIGINT', () => process.exit(0))
385}