]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server.ts
Automatically rebuild native modules on ABI change
[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'
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
c6c0fa6c
C
223// Live streaming
224app.use('/live', liveRouter)
225
345da516 226// Plugins & themes
b5f919ac 227app.use('/', pluginsRouter)
345da516 228
350e31d6 229app.use('/', activityPubRouter)
244e76a5
RK
230app.use('/', feedsRouter)
231app.use('/', webfingerRouter)
9b67da3d 232app.use('/', trackerRouter)
2feebf3e 233app.use('/', botsRouter)
350e31d6 234
a96aed15
C
235// Static files
236app.use('/', staticRouter)
ea8107bf
C
237app.use('/', wellKnownRouter)
238app.use('/', miscRouter)
90a8bd30 239app.use('/', downloadRouter)
557b13ae 240app.use('/', lazyStaticRouter)
a96aed15 241
989e526a 242// Client files, last valid routes!
38f57175 243const cliOptions = cli.opts<{ client: boolean, plugins: boolean }>()
ba5a8d89 244if (cliOptions.client) app.use('/', clientsRouter)
a96aed15 245
a030a9b2
C
246// ----------- Errors -----------
247
1cfbdd30 248// Catch unmatched routes
38f57175 249app.use((_req, res: express.Response) => {
1cfbdd30 250 res.status(HttpStatusCode.NOT_FOUND_404).end()
a030a9b2
C
251})
252
1cfbdd30 253// Catch thrown errors
38f57175 254app.use((err, _req, res: express.Response, _next) => {
1cfbdd30 255 // Format error to be logged
e3a682a8
C
256 let error = 'Unknown error.'
257 if (err) {
258 error = err.stack || err.message || err
259 }
1cfbdd30 260 // Handling Sequelize error traces
328e607d 261 const sql = err.parent ? err.parent.sql : undefined
328e607d 262 logger.error('Error in controller.', { err: error, sql })
1cfbdd30 263
76148b27
RK
264 return res.fail({
265 status: err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500,
266 message: err.message,
267 type: err.name
268 })
6f4e2522 269})
a030a9b2 270
cef534ed 271const server = createWebsocketTrackerServer(app)
9b67da3d 272
79530164
C
273// ----------- Run -----------
274
3d3441d6 275async function startApplication () {
65fcc311 276 const port = CONFIG.LISTEN.PORT
cff8b272 277 const hostname = CONFIG.LISTEN.HOSTNAME
91fea9fc 278
3d3441d6
C
279 await installApplication()
280
23687332
C
281 // Check activity pub urls are valid
282 checkActivityPubUrls()
283 .catch(err => {
284 logger.error('Error in ActivityPub URLs checker.', { err })
285 process.exit(-1)
286 })
287
ae71acca
C
288 checkFFmpegVersion()
289 .catch(err => logger.error('Cannot check ffmpeg version', { err }))
290
dea0df90 291 Redis.Instance.init()
3d3441d6 292 Emailer.Instance.init()
3d3441d6 293
0b2f03d3 294 await Promise.all([
75594f47 295 Emailer.Instance.checkConnection(),
2539932e
C
296 JobQueue.Instance.init(),
297 ServerConfigManager.Instance.init()
0b2f03d3 298 ])
3d3441d6
C
299
300 // Caches initializations
d74d29ad
C
301 VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, FILES_CACHE.PREVIEWS.MAX_AGE)
302 VideosCaptionCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE)
90a8bd30 303 VideosTorrentCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE)
3d3441d6
C
304
305 // Enable Schedulers
2f5c6b2f 306 ActorFollowScheduler.Instance.enable()
3d3441d6 307 RemoveOldJobsScheduler.Instance.enable()
2baea0c7 308 UpdateVideosScheduler.Instance.enable()
ce32426b 309 YoutubeDlUpdateScheduler.Instance.enable()
c48e82b5 310 VideosRedundancyScheduler.Instance.enable()
8f0bc73d 311 RemoveOldHistoryScheduler.Instance.enable()
cda03765 312 RemoveOldViewsScheduler.Instance.enable()
e0ce715a 313 PluginsCheckScheduler.Instance.enable()
32a18cbf 314 PeerTubeVersionCheckScheduler.Instance.enable()
6f1b4fa4 315 AutoFollowIndexInstances.Instance.enable()
f6d6e7f8 316 RemoveDanglingResumableUploadsScheduler.Instance.enable()
51353d9a 317 VideoViewsBufferScheduler.Instance.enable()
b2111066 318 GeoIPUpdateScheduler.Instance.enable()
630d0a1b 319 OpenTelemetryMetrics.Instance.registerMetrics()
3d3441d6 320
cef534ed 321 PeerTubeSocket.Instance.init(server)
b2111066 322 VideoViewsManager.Instance.init()
cef534ed 323
ae9bbed4
C
324 updateStreamingPlaylistsInfohashesIfNeeded()
325 .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
326
c6c0fa6c 327 LiveManager.Instance.init()
df1db951 328 if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
c6c0fa6c 329
3d3441d6 330 // Make server listening
e19fdf57
C
331 server.listen(port, hostname, async () => {
332 if (cliOptions.plugins) {
333 try {
c795e196
C
334 await PluginManager.Instance.rebuildNativePluginsIfNeeded()
335
e19fdf57
C
336 await PluginManager.Instance.registerPluginsAndThemes()
337 } catch (err) {
338 logger.error('Cannot register plugins and themes.', { err })
339 }
340 }
341
c795e196
C
342 ApplicationModel.updateNodeVersions()
343 .catch(err => logger.error('Cannot update node versions.', { err }))
344
c2b82382 345 logger.info('HTTP server listening on %s:%d', hostname, port)
74dc3bca 346 logger.info('Web server: %s', WEBSERVER.URL)
8d76959e 347
89cd1275 348 Hooks.runAction('action:application.listening')
a9cd881b
C
349
350 if (cliOptions['benchmarkStartup']) process.exit(0)
f55e5a7b 351 })
14f2b3ad
C
352
353 process.on('exit', () => {
354 JobQueue.Instance.terminate()
355 })
356
357 process.on('SIGINT', () => process.exit(0))
5804c0db 358}