]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server.ts
Translated using Weblate (German)
[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'
0c9668f7 136import { RunnerJobWatchDogScheduler } from './server/lib/schedulers/runner-job-watch-dog-scheduler'
df66d815 137import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
cef534ed 138import { PeerTubeSocket } from './server/lib/peertube-socket'
ae9bbed4 139import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
e0ce715a 140import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler'
32a18cbf 141import { PeerTubeVersionCheckScheduler } from './server/lib/schedulers/peertube-version-check-scheduler'
89cd1275 142import { Hooks } from './server/lib/plugins/hooks'
464687bb 143import { PluginManager } from './server/lib/plugins/plugin-manager'
8ebf2a5d 144import { LiveManager } from './server/lib/live'
c0e8b12e 145import { HttpStatusCode } from './shared/models/http/http-error-codes'
90a8bd30 146import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache'
2539932e 147import { ServerConfigManager } from '@server/lib/server-config-manager'
b2111066 148import { VideoViewsManager } from '@server/lib/views/video-views-manager'
9452d4fd 149import { isTestOrDevInstance } from './server/helpers/core-utils'
630d0a1b 150import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics'
c795e196 151import { ApplicationModel } from '@server/models/application/application'
2a491182 152import { VideoChannelSyncLatestScheduler } from '@server/lib/schedulers/video-channel-sync-latest-scheduler'
a030a9b2 153
a030a9b2
C
154// ----------- Command line -----------
155
b83b8dd5
RK
156cli
157 .option('--no-client', 'Start PeerTube without client interface')
66e001c8 158 .option('--no-plugins', 'Start PeerTube without plugins/themes enabled')
a9cd881b 159 .option('--benchmark-startup', 'Automatically stop server when initialized')
b83b8dd5
RK
160 .parse(process.argv)
161
a030a9b2
C
162// ----------- App -----------
163
12daa837 164// Enable CORS for develop
9452d4fd 165if (isTestOrDevInstance()) {
62945f06
C
166 app.use(cors({
167 origin: '*',
168 exposedHeaders: 'Retry-After',
169 credentials: true
170 }))
12daa837 171}
74dc3bca 172
a030a9b2 173// For the logger
41fb13c3 174token('remote-addr', (req: express.Request) => {
2f6b5e2d 175 if (CONFIG.LOG.ANONYMIZE_IP === true || req.get('DNT') === '1') {
74dc3bca
C
176 return anonymize(req.ip, 16, 16)
177 }
178
179 return req.ip
180})
41fb13c3 181token('user-agent', (req: express.Request) => {
74dc3bca 182 if (req.get('DNT') === '1') {
41fb13c3 183 return parse(req.get('user-agent')).family
74dc3bca
C
184 }
185
186 return req.get('user-agent')
aad0ec24 187})
e02643f3 188app.use(morgan('combined', {
452b3bea 189 stream: {
943dc182 190 write: (str: string) => logger.info(str.trim(), { tags: [ 'http' ] })
452b3bea 191 },
78d62f4d 192 skip: req => CONFIG.LOG.LOG_PING_REQUESTS === false && req.originalUrl === '/api/v1/ping'
e02643f3 193}))
74dc3bca 194
e030bfb5
C
195// Add .fail() helper to response
196app.use(apiFailMiddleware)
1cfbdd30 197
a030a9b2 198// For body requests
1cfbdd30
RK
199app.use(express.urlencoded({ extended: false }))
200app.use(express.json({
86d13ec2 201 type: [ 'application/json', 'application/*+json' ],
df66d815 202 limit: '500kb',
1cfbdd30 203 verify: (req: express.Request, res: express.Response, buf: Buffer) => {
df66d815 204 const valid = isHTTPSignatureDigestValid(buf, req)
e030bfb5 205
1cfbdd30
RK
206 if (valid !== true) {
207 res.fail({
208 status: HttpStatusCode.FORBIDDEN_403,
209 message: 'Invalid digest'
210 })
211 }
df66d815 212 }
165cdc75 213}))
74dc3bca 214
8afc19a6
C
215// Cookies
216app.use(cookieParser())
74dc3bca 217
aad0ec24
RK
218// W3C DNT Tracking Status
219app.use(advertiseDoNotTrack)
a030a9b2 220
630d0a1b
C
221// ----------- Open Telemetry -----------
222
223OpenTelemetryMetrics.Instance.init(app)
224
a96aed15
C
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
345da516 234// Plugins & themes
b5f919ac 235app.use('/', pluginsRouter)
345da516 236
350e31d6 237app.use('/', activityPubRouter)
244e76a5 238app.use('/', feedsRouter)
9b67da3d 239app.use('/', trackerRouter)
2feebf3e 240app.use('/', botsRouter)
350e31d6 241
a96aed15
C
242// Static files
243app.use('/', staticRouter)
ea8107bf
C
244app.use('/', wellKnownRouter)
245app.use('/', miscRouter)
90a8bd30 246app.use('/', downloadRouter)
557b13ae 247app.use('/', lazyStaticRouter)
9ab330b9 248app.use('/', objectStorageProxyRouter)
a96aed15 249
989e526a 250// Client files, last valid routes!
38f57175 251const cliOptions = cli.opts<{ client: boolean, plugins: boolean }>()
ba5a8d89 252if (cliOptions.client) app.use('/', clientsRouter)
a96aed15 253
a030a9b2
C
254// ----------- Errors -----------
255
1cfbdd30 256// Catch unmatched routes
38f57175 257app.use((_req, res: express.Response) => {
1cfbdd30 258 res.status(HttpStatusCode.NOT_FOUND_404).end()
a030a9b2
C
259})
260
1cfbdd30 261// Catch thrown errors
38f57175 262app.use((err, _req, res: express.Response, _next) => {
1cfbdd30 263 // Format error to be logged
e3a682a8
C
264 let error = 'Unknown error.'
265 if (err) {
266 error = err.stack || err.message || err
267 }
37a04703 268
1cfbdd30 269 // Handling Sequelize error traces
37a04703
C
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 })
1cfbdd30 278
76148b27
RK
279 return res.fail({
280 status: err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500,
281 message: err.message,
282 type: err.name
283 })
6f4e2522 284})
a030a9b2 285
58477244 286const { server, trackerServer } = createWebsocketTrackerServer(app)
9b67da3d 287
79530164
C
288// ----------- Run -----------
289
3d3441d6 290async function startApplication () {
65fcc311 291 const port = CONFIG.LISTEN.PORT
cff8b272 292 const hostname = CONFIG.LISTEN.HOSTNAME
91fea9fc 293
3d3441d6
C
294 await installApplication()
295
23687332
C
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
ae71acca
C
303 checkFFmpegVersion()
304 .catch(err => logger.error('Cannot check ffmpeg version', { err }))
305
dea0df90 306 Redis.Instance.init()
3d3441d6 307 Emailer.Instance.init()
3d3441d6 308
0b2f03d3 309 await Promise.all([
75594f47 310 Emailer.Instance.checkConnection(),
2539932e
C
311 JobQueue.Instance.init(),
312 ServerConfigManager.Instance.init()
0b2f03d3 313 ])
3d3441d6
C
314
315 // Caches initializations
d74d29ad
C
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)
90a8bd30 318 VideosTorrentCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE)
3d3441d6
C
319
320 // Enable Schedulers
2f5c6b2f 321 ActorFollowScheduler.Instance.enable()
3d3441d6 322 RemoveOldJobsScheduler.Instance.enable()
2baea0c7 323 UpdateVideosScheduler.Instance.enable()
ce32426b 324 YoutubeDlUpdateScheduler.Instance.enable()
c48e82b5 325 VideosRedundancyScheduler.Instance.enable()
8f0bc73d 326 RemoveOldHistoryScheduler.Instance.enable()
cda03765 327 RemoveOldViewsScheduler.Instance.enable()
e0ce715a 328 PluginsCheckScheduler.Instance.enable()
32a18cbf 329 PeerTubeVersionCheckScheduler.Instance.enable()
6f1b4fa4 330 AutoFollowIndexInstances.Instance.enable()
f6d6e7f8 331 RemoveDanglingResumableUploadsScheduler.Instance.enable()
2a491182 332 VideoChannelSyncLatestScheduler.Instance.enable()
51353d9a 333 VideoViewsBufferScheduler.Instance.enable()
b2111066 334 GeoIPUpdateScheduler.Instance.enable()
0c9668f7 335 RunnerJobWatchDogScheduler.Instance.enable()
58477244
C
336
337 OpenTelemetryMetrics.Instance.registerMetrics({ trackerServer })
3d3441d6 338
9d4c60dc
C
339 PluginManager.Instance.init(server)
340 // Before PeerTubeSocket init
341 PluginManager.Instance.registerWebSocketRouter()
342
cef534ed 343 PeerTubeSocket.Instance.init(server)
b2111066 344 VideoViewsManager.Instance.init()
cef534ed 345
ae9bbed4
C
346 updateStreamingPlaylistsInfohashesIfNeeded()
347 .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
348
c6c0fa6c 349 LiveManager.Instance.init()
df1db951 350 if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
c6c0fa6c 351
3d3441d6 352 // Make server listening
e19fdf57
C
353 server.listen(port, hostname, async () => {
354 if (cliOptions.plugins) {
355 try {
c795e196
C
356 await PluginManager.Instance.rebuildNativePluginsIfNeeded()
357
e19fdf57
C
358 await PluginManager.Instance.registerPluginsAndThemes()
359 } catch (err) {
360 logger.error('Cannot register plugins and themes.', { err })
361 }
362 }
363
c795e196
C
364 ApplicationModel.updateNodeVersions()
365 .catch(err => logger.error('Cannot update node versions.', { err }))
366
4404a7c4
C
367 JobQueue.Instance.start()
368 .catch(err => {
369 logger.error('Cannot start job queue.', { err })
370 process.exit(-1)
371 })
372
c2b82382 373 logger.info('HTTP server listening on %s:%d', hostname, port)
74dc3bca 374 logger.info('Web server: %s', WEBSERVER.URL)
8d76959e 375
89cd1275 376 Hooks.runAction('action:application.listening')
a9cd881b
C
377
378 if (cliOptions['benchmarkStartup']) process.exit(0)
f55e5a7b 379 })
14f2b3ad
C
380
381 process.on('exit', () => {
382 JobQueue.Instance.terminate()
5a921e7b 383 .catch(err => logger.error('Cannot terminate job queue.', { err }))
14f2b3ad
C
384 })
385
386 process.on('SIGINT', () => process.exit(0))
5804c0db 387}