]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server.ts
feat(client/PageNotFound): mascot margin
[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
c729caf6 48checkConfig()
69b0a27c 49
490b595a
C
50// Trust our proxy (IP forwarding...)
51app.set('trust proxy', CONFIG.TRUST_PROXY)
52
630d0a1b
C
53app.use((_req, res, next) => {
54 res.locals.requestStart = Date.now()
55
56 return next()
57})
58
57c36b27 59// Security middleware
418d092a 60import { baseCSP } from './server/middlewares/csp'
5e755fff 61
539d3f4f
C
62if (CONFIG.CSP.ENABLED) {
63 app.use(baseCSP)
8155db66
C
64}
65
66if (CONFIG.SECURITY.FRAMEGUARD.ENABLED) {
41fb13c3 67 app.use(frameguard({
8155db66 68 action: 'deny' // we only allow it for /videos/embed, see server/controllers/client.ts
539d3f4f
C
69 }))
70}
d00e2393 71
3482688c 72// ----------- Database -----------
91fea9fc 73
3482688c 74// Initialize database and models
74055dc8
C
75import { initDatabaseModels, checkDatabaseConnectionOrDie } from './server/initializers/database'
76checkDatabaseConnectionOrDie()
77
91fea9fc
C
78import { migrate } from './server/initializers/migrator'
79migrate()
80 .then(() => initDatabaseModels(false))
3d3441d6
C
81 .then(() => startApplication())
82 .catch(err => {
83 logger.error('Cannot start application.', { err })
84 process.exit(-1)
85 })
3482688c 86
74dc3bca
C
87// ----------- Initialize -----------
88loadLanguages()
89
00057e85 90// ----------- PeerTube modules -----------
80fdaf06 91import { installApplication } from './server/initializers/installer'
ecb4e35f 92import { Emailer } from './server/lib/emailer'
94a5ff8a 93import { JobQueue } from './server/lib/job-queue'
d74d29ad 94import { VideosPreviewCache, VideosCaptionCache } from './server/lib/files-cache'
244e76a5
RK
95import {
96 activityPubRouter,
97 apiRouter,
98 clientsRouter,
99 feedsRouter,
100 staticRouter,
557b13ae 101 lazyStaticRouter,
244e76a5 102 servicesRouter,
c6c0fa6c 103 liveRouter,
345da516 104 pluginsRouter,
9b67da3d
C
105 webfingerRouter,
106 trackerRouter,
c6c0fa6c 107 createWebsocketTrackerServer,
90a8bd30
C
108 botsRouter,
109 downloadRouter
244e76a5 110} from './server/controllers'
aad0ec24 111import { advertiseDoNotTrack } from './server/middlewares/dnt'
e030bfb5 112import { apiFailMiddleware } from './server/middlewares/error'
ecb4e35f 113import { Redis } from './server/lib/redis'
2f5c6b2f 114import { ActorFollowScheduler } from './server/lib/schedulers/actor-follow-scheduler'
cda03765 115import { RemoveOldViewsScheduler } from './server/lib/schedulers/remove-old-views-scheduler'
94a5ff8a 116import { RemoveOldJobsScheduler } from './server/lib/schedulers/remove-old-jobs-scheduler'
2baea0c7 117import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-scheduler'
ce32426b 118import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler'
c48e82b5 119import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler'
8f0bc73d 120import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler'
6f1b4fa4 121import { AutoFollowIndexInstances } from './server/lib/schedulers/auto-follow-index-instances'
f6d6e7f8 122import { RemoveDanglingResumableUploadsScheduler } from './server/lib/schedulers/remove-dangling-resumable-uploads-scheduler'
51353d9a 123import { VideoViewsBufferScheduler } from './server/lib/schedulers/video-views-buffer-scheduler'
b2111066 124import { GeoIPUpdateScheduler } from './server/lib/schedulers/geo-ip-update-scheduler'
df66d815 125import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
cef534ed 126import { PeerTubeSocket } from './server/lib/peertube-socket'
ae9bbed4 127import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
e0ce715a 128import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler'
32a18cbf 129import { PeerTubeVersionCheckScheduler } from './server/lib/schedulers/peertube-version-check-scheduler'
89cd1275 130import { Hooks } from './server/lib/plugins/hooks'
464687bb 131import { PluginManager } from './server/lib/plugins/plugin-manager'
8ebf2a5d 132import { LiveManager } from './server/lib/live'
c0e8b12e 133import { HttpStatusCode } from './shared/models/http/http-error-codes'
90a8bd30 134import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache'
2539932e 135import { ServerConfigManager } from '@server/lib/server-config-manager'
b2111066 136import { VideoViewsManager } from '@server/lib/views/video-views-manager'
9452d4fd 137import { isTestOrDevInstance } from './server/helpers/core-utils'
630d0a1b 138import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics'
a030a9b2 139
a030a9b2
C
140// ----------- Command line -----------
141
b83b8dd5
RK
142cli
143 .option('--no-client', 'Start PeerTube without client interface')
66e001c8 144 .option('--no-plugins', 'Start PeerTube without plugins/themes enabled')
a9cd881b 145 .option('--benchmark-startup', 'Automatically stop server when initialized')
b83b8dd5
RK
146 .parse(process.argv)
147
a030a9b2
C
148// ----------- App -----------
149
12daa837 150// Enable CORS for develop
9452d4fd 151if (isTestOrDevInstance()) {
62945f06
C
152 app.use(cors({
153 origin: '*',
154 exposedHeaders: 'Retry-After',
155 credentials: true
156 }))
12daa837 157}
74dc3bca 158
a030a9b2 159// For the logger
41fb13c3 160token('remote-addr', (req: express.Request) => {
2f6b5e2d 161 if (CONFIG.LOG.ANONYMIZE_IP === true || req.get('DNT') === '1') {
74dc3bca
C
162 return anonymize(req.ip, 16, 16)
163 }
164
165 return req.ip
166})
41fb13c3 167token('user-agent', (req: express.Request) => {
74dc3bca 168 if (req.get('DNT') === '1') {
41fb13c3 169 return parse(req.get('user-agent')).family
74dc3bca
C
170 }
171
172 return req.get('user-agent')
aad0ec24 173})
e02643f3 174app.use(morgan('combined', {
452b3bea 175 stream: {
943dc182 176 write: (str: string) => logger.info(str.trim(), { tags: [ 'http' ] })
452b3bea 177 },
78d62f4d 178 skip: req => CONFIG.LOG.LOG_PING_REQUESTS === false && req.originalUrl === '/api/v1/ping'
e02643f3 179}))
74dc3bca 180
e030bfb5
C
181// Add .fail() helper to response
182app.use(apiFailMiddleware)
1cfbdd30 183
a030a9b2 184// For body requests
1cfbdd30
RK
185app.use(express.urlencoded({ extended: false }))
186app.use(express.json({
86d13ec2 187 type: [ 'application/json', 'application/*+json' ],
df66d815 188 limit: '500kb',
1cfbdd30 189 verify: (req: express.Request, res: express.Response, buf: Buffer) => {
df66d815 190 const valid = isHTTPSignatureDigestValid(buf, req)
e030bfb5 191
1cfbdd30
RK
192 if (valid !== true) {
193 res.fail({
194 status: HttpStatusCode.FORBIDDEN_403,
195 message: 'Invalid digest'
196 })
197 }
df66d815 198 }
165cdc75 199}))
74dc3bca 200
8afc19a6
C
201// Cookies
202app.use(cookieParser())
74dc3bca 203
aad0ec24
RK
204// W3C DNT Tracking Status
205app.use(advertiseDoNotTrack)
a030a9b2 206
630d0a1b
C
207// ----------- Open Telemetry -----------
208
209OpenTelemetryMetrics.Instance.init(app)
210
a96aed15
C
211// ----------- Views, routes and static files -----------
212
213// API
214const apiRoute = '/api/' + API_VERSION
215app.use(apiRoute, apiRouter)
216
217// Services (oembed...)
218app.use('/services', servicesRouter)
219
c6c0fa6c
C
220// Live streaming
221app.use('/live', liveRouter)
222
345da516 223// Plugins & themes
b5f919ac 224app.use('/', pluginsRouter)
345da516 225
350e31d6 226app.use('/', activityPubRouter)
244e76a5
RK
227app.use('/', feedsRouter)
228app.use('/', webfingerRouter)
9b67da3d 229app.use('/', trackerRouter)
2feebf3e 230app.use('/', botsRouter)
350e31d6 231
a96aed15
C
232// Static files
233app.use('/', staticRouter)
90a8bd30 234app.use('/', downloadRouter)
557b13ae 235app.use('/', lazyStaticRouter)
a96aed15 236
989e526a 237// Client files, last valid routes!
38f57175 238const cliOptions = cli.opts<{ client: boolean, plugins: boolean }>()
ba5a8d89 239if (cliOptions.client) app.use('/', clientsRouter)
a96aed15 240
a030a9b2
C
241// ----------- Errors -----------
242
1cfbdd30 243// Catch unmatched routes
38f57175 244app.use((_req, res: express.Response) => {
1cfbdd30 245 res.status(HttpStatusCode.NOT_FOUND_404).end()
a030a9b2
C
246})
247
1cfbdd30 248// Catch thrown errors
38f57175 249app.use((err, _req, res: express.Response, _next) => {
1cfbdd30 250 // Format error to be logged
e3a682a8
C
251 let error = 'Unknown error.'
252 if (err) {
253 error = err.stack || err.message || err
254 }
1cfbdd30 255 // Handling Sequelize error traces
328e607d 256 const sql = err.parent ? err.parent.sql : undefined
328e607d 257 logger.error('Error in controller.', { err: error, sql })
1cfbdd30 258
76148b27
RK
259 return res.fail({
260 status: err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500,
261 message: err.message,
262 type: err.name
263 })
6f4e2522 264})
a030a9b2 265
cef534ed 266const server = createWebsocketTrackerServer(app)
9b67da3d 267
79530164
C
268// ----------- Run -----------
269
3d3441d6 270async function startApplication () {
65fcc311 271 const port = CONFIG.LISTEN.PORT
cff8b272 272 const hostname = CONFIG.LISTEN.HOSTNAME
91fea9fc 273
3d3441d6
C
274 await installApplication()
275
23687332
C
276 // Check activity pub urls are valid
277 checkActivityPubUrls()
278 .catch(err => {
279 logger.error('Error in ActivityPub URLs checker.', { err })
280 process.exit(-1)
281 })
282
ae71acca
C
283 checkFFmpegVersion()
284 .catch(err => logger.error('Cannot check ffmpeg version', { err }))
285
3d3441d6
C
286 // Email initialization
287 Emailer.Instance.init()
3d3441d6 288
0b2f03d3 289 await Promise.all([
75594f47 290 Emailer.Instance.checkConnection(),
2539932e
C
291 JobQueue.Instance.init(),
292 ServerConfigManager.Instance.init()
0b2f03d3 293 ])
3d3441d6
C
294
295 // Caches initializations
d74d29ad
C
296 VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, FILES_CACHE.PREVIEWS.MAX_AGE)
297 VideosCaptionCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE)
90a8bd30 298 VideosTorrentCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE)
3d3441d6
C
299
300 // Enable Schedulers
2f5c6b2f 301 ActorFollowScheduler.Instance.enable()
3d3441d6 302 RemoveOldJobsScheduler.Instance.enable()
2baea0c7 303 UpdateVideosScheduler.Instance.enable()
ce32426b 304 YoutubeDlUpdateScheduler.Instance.enable()
c48e82b5 305 VideosRedundancyScheduler.Instance.enable()
8f0bc73d 306 RemoveOldHistoryScheduler.Instance.enable()
cda03765 307 RemoveOldViewsScheduler.Instance.enable()
e0ce715a 308 PluginsCheckScheduler.Instance.enable()
32a18cbf 309 PeerTubeVersionCheckScheduler.Instance.enable()
6f1b4fa4 310 AutoFollowIndexInstances.Instance.enable()
f6d6e7f8 311 RemoveDanglingResumableUploadsScheduler.Instance.enable()
51353d9a 312 VideoViewsBufferScheduler.Instance.enable()
b2111066 313 GeoIPUpdateScheduler.Instance.enable()
630d0a1b 314 OpenTelemetryMetrics.Instance.registerMetrics()
3d3441d6 315
3d3441d6 316 Redis.Instance.init()
cef534ed 317 PeerTubeSocket.Instance.init(server)
b2111066 318 VideoViewsManager.Instance.init()
cef534ed 319
ae9bbed4
C
320 updateStreamingPlaylistsInfohashesIfNeeded()
321 .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
322
c6c0fa6c 323 LiveManager.Instance.init()
df1db951 324 if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
c6c0fa6c 325
3d3441d6 326 // Make server listening
e19fdf57
C
327 server.listen(port, hostname, async () => {
328 if (cliOptions.plugins) {
329 try {
330 await PluginManager.Instance.registerPluginsAndThemes()
331 } catch (err) {
332 logger.error('Cannot register plugins and themes.', { err })
333 }
334 }
335
c2b82382 336 logger.info('HTTP server listening on %s:%d', hostname, port)
74dc3bca 337 logger.info('Web server: %s', WEBSERVER.URL)
8d76959e 338
89cd1275 339 Hooks.runAction('action:application.listening')
a9cd881b
C
340
341 if (cliOptions['benchmarkStartup']) process.exit(0)
f55e5a7b 342 })
14f2b3ad
C
343
344 process.on('exit', () => {
345 JobQueue.Instance.terminate()
346 })
347
348 process.on('SIGINT', () => process.exit(0))
5804c0db 349}