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