]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server.ts
Don't display alt for actor images
[github/Chocobozzz/PeerTube.git] / server.ts
CommitLineData
a030a9b2 1// ----------- Node modules -----------
630d0a1b
C
2import { registerOpentelemetryTracing } from './server/lib/opentelemetry/tracing'
3registerOpentelemetryTracing()
4
41fb13c3
C
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'
8cc61201 12import { program as cli } from 'commander'
a030a9b2 13
9f540774
C
14process.title = 'peertube'
15
a030a9b2 16// Create our main app
9270bd3a 17const app = express().disable('x-powered-by')
a030a9b2 18
3482688c 19// ----------- Core checker -----------
51c35447 20import { checkMissedConfig, checkFFmpeg, checkNodeVersion } from './server/initializers/checker-before-init'
69b0a27c 21
d5b7d911 22// Do not use barrels because we don't want to load all modules here (we need to initialize database first)
74dc3bca 23import { CONFIG } from './server/initializers/config'
c1340a6a
C
24import { API_VERSION, FILES_CACHE, WEBSERVER, loadLanguages } from './server/initializers/constants'
25import { logger } from './server/helpers/logger'
d5b7d911 26
65fcc311 27const missed = checkMissedConfig()
b65c27aa 28if (missed.length !== 0) {
d5b7d911
C
29 logger.error('Your configuration files miss keys: ' + missed)
30 process.exit(-1)
b65c27aa 31}
3482688c 32
3482688c 33checkFFmpeg(CONFIG)
d5b7d911
C
34 .catch(err => {
35 logger.error('Error in ffmpeg check.', { err })
36 process.exit(-1)
37 })
b65c27aa 38
5a637488
C
39try {
40 checkNodeVersion()
41} catch (err) {
42 logger.error('Error in NodeJS check.', { err })
43 process.exit(-1)
44}
51c35447 45
ae71acca 46import { checkConfig, checkActivityPubUrls, checkFFmpegVersion } from './server/initializers/checker-after-init'
e5565833 47
a3e5f804
C
48try {
49 checkConfig()
50} catch (err) {
51 logger.error('Config error.', { err })
52 process.exit(-1)
53}
69b0a27c 54
490b595a
C
55// Trust our proxy (IP forwarding...)
56app.set('trust proxy', CONFIG.TRUST_PROXY)
57
630d0a1b 58app.use((_req, res, next) => {
47653481 59 // OpenTelemetry
630d0a1b
C
60 res.locals.requestStart = Date.now()
61
47653481
C
62 if (CONFIG.SECURITY.POWERED_BY_HEADER.ENABLED === true) {
63 res.setHeader('x-powered-by', 'PeerTube')
64 }
65
630d0a1b
C
66 return next()
67})
68
57c36b27 69// Security middleware
418d092a 70import { baseCSP } from './server/middlewares/csp'
5e755fff 71
539d3f4f
C
72if (CONFIG.CSP.ENABLED) {
73 app.use(baseCSP)
8155db66
C
74}
75
76if (CONFIG.SECURITY.FRAMEGUARD.ENABLED) {
41fb13c3 77 app.use(frameguard({
8155db66 78 action: 'deny' // we only allow it for /videos/embed, see server/controllers/client.ts
539d3f4f
C
79 }))
80}
d00e2393 81
3482688c 82// ----------- Database -----------
91fea9fc 83
3482688c 84// Initialize database and models
74055dc8
C
85import { initDatabaseModels, checkDatabaseConnectionOrDie } from './server/initializers/database'
86checkDatabaseConnectionOrDie()
87
91fea9fc
C
88import { migrate } from './server/initializers/migrator'
89migrate()
90 .then(() => initDatabaseModels(false))
3d3441d6
C
91 .then(() => startApplication())
92 .catch(err => {
93 logger.error('Cannot start application.', { err })
94 process.exit(-1)
95 })
3482688c 96
74dc3bca
C
97// ----------- Initialize -----------
98loadLanguages()
99
00057e85 100// ----------- PeerTube modules -----------
80fdaf06 101import { installApplication } from './server/initializers/installer'
ecb4e35f 102import { Emailer } from './server/lib/emailer'
94a5ff8a 103import { JobQueue } from './server/lib/job-queue'
d74d29ad 104import { VideosPreviewCache, VideosCaptionCache } from './server/lib/files-cache'
244e76a5
RK
105import {
106 activityPubRouter,
107 apiRouter,
ea8107bf 108 miscRouter,
244e76a5
RK
109 clientsRouter,
110 feedsRouter,
111 staticRouter,
ea8107bf 112 wellKnownRouter,
557b13ae 113 lazyStaticRouter,
244e76a5 114 servicesRouter,
9ab330b9 115 objectStorageProxyRouter,
345da516 116 pluginsRouter,
9b67da3d 117 trackerRouter,
c6c0fa6c 118 createWebsocketTrackerServer,
90a8bd30
C
119 botsRouter,
120 downloadRouter
244e76a5 121} from './server/controllers'
aad0ec24 122import { advertiseDoNotTrack } from './server/middlewares/dnt'
e030bfb5 123import { apiFailMiddleware } from './server/middlewares/error'
ecb4e35f 124import { Redis } from './server/lib/redis'
2f5c6b2f 125import { ActorFollowScheduler } from './server/lib/schedulers/actor-follow-scheduler'
cda03765 126import { RemoveOldViewsScheduler } from './server/lib/schedulers/remove-old-views-scheduler'
94a5ff8a 127import { RemoveOldJobsScheduler } from './server/lib/schedulers/remove-old-jobs-scheduler'
2baea0c7 128import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-scheduler'
ce32426b 129import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler'
c48e82b5 130import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler'
8f0bc73d 131import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler'
6f1b4fa4 132import { AutoFollowIndexInstances } from './server/lib/schedulers/auto-follow-index-instances'
f6d6e7f8 133import { RemoveDanglingResumableUploadsScheduler } from './server/lib/schedulers/remove-dangling-resumable-uploads-scheduler'
51353d9a 134import { VideoViewsBufferScheduler } from './server/lib/schedulers/video-views-buffer-scheduler'
b2111066 135import { GeoIPUpdateScheduler } from './server/lib/schedulers/geo-ip-update-scheduler'
df66d815 136import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
cef534ed 137import { PeerTubeSocket } from './server/lib/peertube-socket'
ae9bbed4 138import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
e0ce715a 139import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler'
32a18cbf 140import { PeerTubeVersionCheckScheduler } from './server/lib/schedulers/peertube-version-check-scheduler'
89cd1275 141import { Hooks } from './server/lib/plugins/hooks'
464687bb 142import { PluginManager } from './server/lib/plugins/plugin-manager'
8ebf2a5d 143import { LiveManager } from './server/lib/live'
c0e8b12e 144import { HttpStatusCode } from './shared/models/http/http-error-codes'
90a8bd30 145import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache'
2539932e 146import { ServerConfigManager } from '@server/lib/server-config-manager'
b2111066 147import { VideoViewsManager } from '@server/lib/views/video-views-manager'
9452d4fd 148import { isTestOrDevInstance } from './server/helpers/core-utils'
630d0a1b 149import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics'
c795e196 150import { ApplicationModel } from '@server/models/application/application'
2a491182 151import { VideoChannelSyncLatestScheduler } from '@server/lib/schedulers/video-channel-sync-latest-scheduler'
a030a9b2 152
a030a9b2
C
153// ----------- Command line -----------
154
b83b8dd5
RK
155cli
156 .option('--no-client', 'Start PeerTube without client interface')
66e001c8 157 .option('--no-plugins', 'Start PeerTube without plugins/themes enabled')
a9cd881b 158 .option('--benchmark-startup', 'Automatically stop server when initialized')
b83b8dd5
RK
159 .parse(process.argv)
160
a030a9b2
C
161// ----------- App -----------
162
12daa837 163// Enable CORS for develop
9452d4fd 164if (isTestOrDevInstance()) {
62945f06
C
165 app.use(cors({
166 origin: '*',
167 exposedHeaders: 'Retry-After',
168 credentials: true
169 }))
12daa837 170}
74dc3bca 171
a030a9b2 172// For the logger
41fb13c3 173token('remote-addr', (req: express.Request) => {
2f6b5e2d 174 if (CONFIG.LOG.ANONYMIZE_IP === true || req.get('DNT') === '1') {
74dc3bca
C
175 return anonymize(req.ip, 16, 16)
176 }
177
178 return req.ip
179})
41fb13c3 180token('user-agent', (req: express.Request) => {
74dc3bca 181 if (req.get('DNT') === '1') {
41fb13c3 182 return parse(req.get('user-agent')).family
74dc3bca
C
183 }
184
185 return req.get('user-agent')
aad0ec24 186})
e02643f3 187app.use(morgan('combined', {
452b3bea 188 stream: {
943dc182 189 write: (str: string) => logger.info(str.trim(), { tags: [ 'http' ] })
452b3bea 190 },
78d62f4d 191 skip: req => CONFIG.LOG.LOG_PING_REQUESTS === false && req.originalUrl === '/api/v1/ping'
e02643f3 192}))
74dc3bca 193
e030bfb5
C
194// Add .fail() helper to response
195app.use(apiFailMiddleware)
1cfbdd30 196
a030a9b2 197// For body requests
1cfbdd30
RK
198app.use(express.urlencoded({ extended: false }))
199app.use(express.json({
86d13ec2 200 type: [ 'application/json', 'application/*+json' ],
df66d815 201 limit: '500kb',
1cfbdd30 202 verify: (req: express.Request, res: express.Response, buf: Buffer) => {
df66d815 203 const valid = isHTTPSignatureDigestValid(buf, req)
e030bfb5 204
1cfbdd30
RK
205 if (valid !== true) {
206 res.fail({
207 status: HttpStatusCode.FORBIDDEN_403,
208 message: 'Invalid digest'
209 })
210 }
df66d815 211 }
165cdc75 212}))
74dc3bca 213
8afc19a6
C
214// Cookies
215app.use(cookieParser())
74dc3bca 216
aad0ec24
RK
217// W3C DNT Tracking Status
218app.use(advertiseDoNotTrack)
a030a9b2 219
630d0a1b
C
220// ----------- Open Telemetry -----------
221
222OpenTelemetryMetrics.Instance.init(app)
223
a96aed15
C
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
345da516 233// Plugins & themes
b5f919ac 234app.use('/', pluginsRouter)
345da516 235
350e31d6 236app.use('/', activityPubRouter)
244e76a5 237app.use('/', feedsRouter)
9b67da3d 238app.use('/', trackerRouter)
2feebf3e 239app.use('/', botsRouter)
350e31d6 240
a96aed15
C
241// Static files
242app.use('/', staticRouter)
ea8107bf
C
243app.use('/', wellKnownRouter)
244app.use('/', miscRouter)
90a8bd30 245app.use('/', downloadRouter)
557b13ae 246app.use('/', lazyStaticRouter)
9ab330b9 247app.use('/', objectStorageProxyRouter)
a96aed15 248
989e526a 249// Client files, last valid routes!
38f57175 250const cliOptions = cli.opts<{ client: boolean, plugins: boolean }>()
ba5a8d89 251if (cliOptions.client) app.use('/', clientsRouter)
a96aed15 252
a030a9b2
C
253// ----------- Errors -----------
254
1cfbdd30 255// Catch unmatched routes
38f57175 256app.use((_req, res: express.Response) => {
1cfbdd30 257 res.status(HttpStatusCode.NOT_FOUND_404).end()
a030a9b2
C
258})
259
1cfbdd30 260// Catch thrown errors
38f57175 261app.use((err, _req, res: express.Response, _next) => {
1cfbdd30 262 // Format error to be logged
e3a682a8
C
263 let error = 'Unknown error.'
264 if (err) {
265 error = err.stack || err.message || err
266 }
37a04703 267
1cfbdd30 268 // Handling Sequelize error traces
37a04703
C
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 })
1cfbdd30 277
76148b27
RK
278 return res.fail({
279 status: err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500,
280 message: err.message,
281 type: err.name
282 })
6f4e2522 283})
a030a9b2 284
58477244 285const { server, trackerServer } = createWebsocketTrackerServer(app)
9b67da3d 286
79530164
C
287// ----------- Run -----------
288
3d3441d6 289async function startApplication () {
65fcc311 290 const port = CONFIG.LISTEN.PORT
cff8b272 291 const hostname = CONFIG.LISTEN.HOSTNAME
91fea9fc 292
3d3441d6
C
293 await installApplication()
294
23687332
C
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
ae71acca
C
302 checkFFmpegVersion()
303 .catch(err => logger.error('Cannot check ffmpeg version', { err }))
304
dea0df90 305 Redis.Instance.init()
3d3441d6 306 Emailer.Instance.init()
3d3441d6 307
0b2f03d3 308 await Promise.all([
75594f47 309 Emailer.Instance.checkConnection(),
2539932e
C
310 JobQueue.Instance.init(),
311 ServerConfigManager.Instance.init()
0b2f03d3 312 ])
3d3441d6
C
313
314 // Caches initializations
d74d29ad
C
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)
90a8bd30 317 VideosTorrentCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE)
3d3441d6
C
318
319 // Enable Schedulers
2f5c6b2f 320 ActorFollowScheduler.Instance.enable()
3d3441d6 321 RemoveOldJobsScheduler.Instance.enable()
2baea0c7 322 UpdateVideosScheduler.Instance.enable()
ce32426b 323 YoutubeDlUpdateScheduler.Instance.enable()
c48e82b5 324 VideosRedundancyScheduler.Instance.enable()
8f0bc73d 325 RemoveOldHistoryScheduler.Instance.enable()
cda03765 326 RemoveOldViewsScheduler.Instance.enable()
e0ce715a 327 PluginsCheckScheduler.Instance.enable()
32a18cbf 328 PeerTubeVersionCheckScheduler.Instance.enable()
6f1b4fa4 329 AutoFollowIndexInstances.Instance.enable()
f6d6e7f8 330 RemoveDanglingResumableUploadsScheduler.Instance.enable()
2a491182 331 VideoChannelSyncLatestScheduler.Instance.enable()
51353d9a 332 VideoViewsBufferScheduler.Instance.enable()
b2111066 333 GeoIPUpdateScheduler.Instance.enable()
58477244
C
334
335 OpenTelemetryMetrics.Instance.registerMetrics({ trackerServer })
3d3441d6 336
9d4c60dc
C
337 PluginManager.Instance.init(server)
338 // Before PeerTubeSocket init
339 PluginManager.Instance.registerWebSocketRouter()
340
cef534ed 341 PeerTubeSocket.Instance.init(server)
b2111066 342 VideoViewsManager.Instance.init()
cef534ed 343
ae9bbed4
C
344 updateStreamingPlaylistsInfohashesIfNeeded()
345 .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
346
c6c0fa6c 347 LiveManager.Instance.init()
df1db951 348 if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
c6c0fa6c 349
3d3441d6 350 // Make server listening
e19fdf57
C
351 server.listen(port, hostname, async () => {
352 if (cliOptions.plugins) {
353 try {
c795e196
C
354 await PluginManager.Instance.rebuildNativePluginsIfNeeded()
355
e19fdf57
C
356 await PluginManager.Instance.registerPluginsAndThemes()
357 } catch (err) {
358 logger.error('Cannot register plugins and themes.', { err })
359 }
360 }
361
c795e196
C
362 ApplicationModel.updateNodeVersions()
363 .catch(err => logger.error('Cannot update node versions.', { err }))
364
4404a7c4
C
365 JobQueue.Instance.start()
366 .catch(err => {
367 logger.error('Cannot start job queue.', { err })
368 process.exit(-1)
369 })
370
c2b82382 371 logger.info('HTTP server listening on %s:%d', hostname, port)
74dc3bca 372 logger.info('Web server: %s', WEBSERVER.URL)
8d76959e 373
89cd1275 374 Hooks.runAction('action:application.listening')
a9cd881b
C
375
376 if (cliOptions['benchmarkStartup']) process.exit(0)
f55e5a7b 377 })
14f2b3ad
C
378
379 process.on('exit', () => {
380 JobQueue.Instance.terminate()
5a921e7b 381 .catch(err => logger.error('Cannot terminate job queue.', { err }))
14f2b3ad
C
382 })
383
384 process.on('SIGINT', () => process.exit(0))
5804c0db 385}