]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server.ts
Handle async validators
[github/Chocobozzz/PeerTube.git] / server.ts
CommitLineData
2aaa1a3f 1import { registerTSPaths } from './server/helpers/register-ts-paths'
2aaa1a3f 2registerTSPaths()
f023a19c 3
1840c2f7 4import { isTestInstance } from './server/helpers/core-utils'
1840c2f7 5if (isTestInstance()) {
e02643f3
C
6 require('source-map-support').install()
7}
8
a030a9b2 9// ----------- Node modules -----------
41fb13c3
C
10import express from 'express'
11import morgan, { token } from 'morgan'
12import cors from 'cors'
13import cookieParser from 'cookie-parser'
14import { frameguard } from 'helmet'
15import { parse } from 'useragent'
16import anonymize from 'ip-anonymize'
8cc61201 17import { program as cli } from 'commander'
a030a9b2 18
9f540774
C
19process.title = 'peertube'
20
a030a9b2 21// Create our main app
6c8c15f9 22const app = express().disable("x-powered-by")
a030a9b2 23
3482688c 24// ----------- Core checker -----------
51c35447 25import { checkMissedConfig, checkFFmpeg, checkNodeVersion } from './server/initializers/checker-before-init'
69b0a27c 26
d5b7d911 27// Do not use barrels because we don't want to load all modules here (we need to initialize database first)
74dc3bca 28import { CONFIG } from './server/initializers/config'
c1340a6a
C
29import { API_VERSION, FILES_CACHE, WEBSERVER, loadLanguages } from './server/initializers/constants'
30import { logger } from './server/helpers/logger'
d5b7d911 31
65fcc311 32const missed = checkMissedConfig()
b65c27aa 33if (missed.length !== 0) {
d5b7d911
C
34 logger.error('Your configuration files miss keys: ' + missed)
35 process.exit(-1)
b65c27aa 36}
3482688c 37
3482688c 38checkFFmpeg(CONFIG)
d5b7d911
C
39 .catch(err => {
40 logger.error('Error in ffmpeg check.', { err })
41 process.exit(-1)
42 })
b65c27aa 43
5a637488
C
44try {
45 checkNodeVersion()
46} catch (err) {
47 logger.error('Error in NodeJS check.', { err })
48 process.exit(-1)
49}
51c35447 50
ae71acca 51import { checkConfig, checkActivityPubUrls, checkFFmpegVersion } from './server/initializers/checker-after-init'
e5565833 52
65fcc311 53const errorMessage = checkConfig()
b65c27aa
C
54if (errorMessage !== null) {
55 throw new Error(errorMessage)
69b0a27c
C
56}
57
490b595a
C
58// Trust our proxy (IP forwarding...)
59app.set('trust proxy', CONFIG.TRUST_PROXY)
60
57c36b27 61// Security middleware
418d092a 62import { baseCSP } from './server/middlewares/csp'
5e755fff 63
539d3f4f
C
64if (CONFIG.CSP.ENABLED) {
65 app.use(baseCSP)
8155db66
C
66}
67
68if (CONFIG.SECURITY.FRAMEGUARD.ENABLED) {
41fb13c3 69 app.use(frameguard({
8155db66 70 action: 'deny' // we only allow it for /videos/embed, see server/controllers/client.ts
539d3f4f
C
71 }))
72}
d00e2393 73
3482688c 74// ----------- Database -----------
91fea9fc 75
3482688c 76// Initialize database and models
74055dc8
C
77import { initDatabaseModels, checkDatabaseConnectionOrDie } from './server/initializers/database'
78checkDatabaseConnectionOrDie()
79
91fea9fc
C
80import { migrate } from './server/initializers/migrator'
81migrate()
82 .then(() => initDatabaseModels(false))
3d3441d6
C
83 .then(() => startApplication())
84 .catch(err => {
85 logger.error('Cannot start application.', { err })
86 process.exit(-1)
87 })
3482688c 88
74dc3bca
C
89// ----------- Initialize -----------
90loadLanguages()
91
00057e85 92// ----------- PeerTube modules -----------
80fdaf06 93import { installApplication } from './server/initializers/installer'
ecb4e35f 94import { Emailer } from './server/lib/emailer'
94a5ff8a 95import { JobQueue } from './server/lib/job-queue'
d74d29ad 96import { VideosPreviewCache, VideosCaptionCache } from './server/lib/files-cache'
244e76a5
RK
97import {
98 activityPubRouter,
99 apiRouter,
100 clientsRouter,
101 feedsRouter,
102 staticRouter,
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'
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'
51353d9a 137import { VideoViews } from '@server/lib/video-views'
a030a9b2 138
a030a9b2
C
139// ----------- Command line -----------
140
b83b8dd5
RK
141cli
142 .option('--no-client', 'Start PeerTube without client interface')
66e001c8 143 .option('--no-plugins', 'Start PeerTube without plugins/themes enabled')
a9cd881b 144 .option('--benchmark-startup', 'Automatically stop server when initialized')
b83b8dd5
RK
145 .parse(process.argv)
146
a030a9b2
C
147// ----------- App -----------
148
12daa837
WL
149// Enable CORS for develop
150if (isTestInstance()) {
62945f06
C
151 app.use(cors({
152 origin: '*',
153 exposedHeaders: 'Retry-After',
154 credentials: true
155 }))
12daa837 156}
74dc3bca 157
a030a9b2 158// For the logger
41fb13c3 159token('remote-addr', (req: express.Request) => {
2f6b5e2d 160 if (CONFIG.LOG.ANONYMIZE_IP === true || req.get('DNT') === '1') {
74dc3bca
C
161 return anonymize(req.ip, 16, 16)
162 }
163
164 return req.ip
165})
41fb13c3 166token('user-agent', (req: express.Request) => {
74dc3bca 167 if (req.get('DNT') === '1') {
41fb13c3 168 return parse(req.get('user-agent')).family
74dc3bca
C
169 }
170
171 return req.get('user-agent')
aad0ec24 172})
e02643f3 173app.use(morgan('combined', {
452b3bea 174 stream: {
943dc182 175 write: (str: string) => logger.info(str.trim(), { tags: [ 'http' ] })
452b3bea 176 },
78d62f4d 177 skip: req => CONFIG.LOG.LOG_PING_REQUESTS === false && req.originalUrl === '/api/v1/ping'
e02643f3 178}))
74dc3bca 179
e030bfb5
C
180// Add .fail() helper to response
181app.use(apiFailMiddleware)
1cfbdd30 182
a030a9b2 183// For body requests
1cfbdd30
RK
184app.use(express.urlencoded({ extended: false }))
185app.use(express.json({
86d13ec2 186 type: [ 'application/json', 'application/*+json' ],
df66d815 187 limit: '500kb',
1cfbdd30 188 verify: (req: express.Request, res: express.Response, buf: Buffer) => {
df66d815 189 const valid = isHTTPSignatureDigestValid(buf, req)
e030bfb5 190
1cfbdd30
RK
191 if (valid !== true) {
192 res.fail({
193 status: HttpStatusCode.FORBIDDEN_403,
194 message: 'Invalid digest'
195 })
196 }
df66d815 197 }
165cdc75 198}))
74dc3bca 199
8afc19a6
C
200// Cookies
201app.use(cookieParser())
74dc3bca 202
aad0ec24
RK
203// W3C DNT Tracking Status
204app.use(advertiseDoNotTrack)
a030a9b2 205
a96aed15
C
206// ----------- Views, routes and static files -----------
207
208// API
209const apiRoute = '/api/' + API_VERSION
210app.use(apiRoute, apiRouter)
211
212// Services (oembed...)
213app.use('/services', servicesRouter)
214
c6c0fa6c
C
215// Live streaming
216app.use('/live', liveRouter)
217
345da516 218// Plugins & themes
b5f919ac 219app.use('/', pluginsRouter)
345da516 220
350e31d6 221app.use('/', activityPubRouter)
244e76a5
RK
222app.use('/', feedsRouter)
223app.use('/', webfingerRouter)
9b67da3d 224app.use('/', trackerRouter)
2feebf3e 225app.use('/', botsRouter)
350e31d6 226
a96aed15
C
227// Static files
228app.use('/', staticRouter)
90a8bd30 229app.use('/', downloadRouter)
557b13ae 230app.use('/', lazyStaticRouter)
a96aed15 231
989e526a 232// Client files, last valid routes!
ba5a8d89
C
233const cliOptions = cli.opts()
234if (cliOptions.client) app.use('/', clientsRouter)
a96aed15 235
a030a9b2
C
236// ----------- Errors -----------
237
1cfbdd30
RK
238// Catch unmatched routes
239app.use((req, res: express.Response) => {
240 res.status(HttpStatusCode.NOT_FOUND_404).end()
a030a9b2
C
241})
242
1cfbdd30
RK
243// Catch thrown errors
244app.use((err, req, res: express.Response, next) => {
245 // Format error to be logged
e3a682a8
C
246 let error = 'Unknown error.'
247 if (err) {
248 error = err.stack || err.message || err
249 }
1cfbdd30 250 // Handling Sequelize error traces
328e607d 251 const sql = err.parent ? err.parent.sql : undefined
328e607d 252 logger.error('Error in controller.', { err: error, sql })
1cfbdd30 253
76148b27
RK
254 return res.fail({
255 status: err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500,
256 message: err.message,
257 type: err.name
258 })
6f4e2522 259})
a030a9b2 260
cef534ed 261const server = createWebsocketTrackerServer(app)
9b67da3d 262
79530164
C
263// ----------- Run -----------
264
3d3441d6 265async function startApplication () {
65fcc311 266 const port = CONFIG.LISTEN.PORT
cff8b272 267 const hostname = CONFIG.LISTEN.HOSTNAME
91fea9fc 268
3d3441d6
C
269 await installApplication()
270
23687332
C
271 // Check activity pub urls are valid
272 checkActivityPubUrls()
273 .catch(err => {
274 logger.error('Error in ActivityPub URLs checker.', { err })
275 process.exit(-1)
276 })
277
ae71acca
C
278 checkFFmpegVersion()
279 .catch(err => logger.error('Cannot check ffmpeg version', { err }))
280
3d3441d6
C
281 // Email initialization
282 Emailer.Instance.init()
3d3441d6 283
0b2f03d3 284 await Promise.all([
75594f47 285 Emailer.Instance.checkConnection(),
2539932e
C
286 JobQueue.Instance.init(),
287 ServerConfigManager.Instance.init()
0b2f03d3 288 ])
3d3441d6
C
289
290 // Caches initializations
d74d29ad
C
291 VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, FILES_CACHE.PREVIEWS.MAX_AGE)
292 VideosCaptionCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE)
90a8bd30 293 VideosTorrentCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE)
3d3441d6
C
294
295 // Enable Schedulers
2f5c6b2f 296 ActorFollowScheduler.Instance.enable()
3d3441d6 297 RemoveOldJobsScheduler.Instance.enable()
2baea0c7 298 UpdateVideosScheduler.Instance.enable()
ce32426b 299 YoutubeDlUpdateScheduler.Instance.enable()
c48e82b5 300 VideosRedundancyScheduler.Instance.enable()
8f0bc73d 301 RemoveOldHistoryScheduler.Instance.enable()
cda03765 302 RemoveOldViewsScheduler.Instance.enable()
e0ce715a 303 PluginsCheckScheduler.Instance.enable()
32a18cbf 304 PeerTubeVersionCheckScheduler.Instance.enable()
6f1b4fa4 305 AutoFollowIndexInstances.Instance.enable()
f6d6e7f8 306 RemoveDanglingResumableUploadsScheduler.Instance.enable()
51353d9a 307 VideoViewsBufferScheduler.Instance.enable()
3d3441d6 308
3d3441d6 309 Redis.Instance.init()
cef534ed 310 PeerTubeSocket.Instance.init(server)
51353d9a 311 VideoViews.Instance.init()
cef534ed 312
ae9bbed4
C
313 updateStreamingPlaylistsInfohashesIfNeeded()
314 .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
315
c6c0fa6c 316 LiveManager.Instance.init()
df1db951 317 if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
c6c0fa6c 318
3d3441d6 319 // Make server listening
e19fdf57
C
320 server.listen(port, hostname, async () => {
321 if (cliOptions.plugins) {
322 try {
323 await PluginManager.Instance.registerPluginsAndThemes()
324 } catch (err) {
325 logger.error('Cannot register plugins and themes.', { err })
326 }
327 }
328
c2b82382 329 logger.info('HTTP server listening on %s:%d', hostname, port)
74dc3bca 330 logger.info('Web server: %s', WEBSERVER.URL)
8d76959e 331
89cd1275 332 Hooks.runAction('action:application.listening')
a9cd881b
C
333
334 if (cliOptions['benchmarkStartup']) process.exit(0)
f55e5a7b 335 })
14f2b3ad
C
336
337 process.on('exit', () => {
338 JobQueue.Instance.terminate()
339 })
340
341 process.on('SIGINT', () => process.exit(0))
5804c0db 342}