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