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