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