aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/server.ts
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-07-31 14:34:36 +0200
committerChocobozzz <me@florianbigard.com>2023-08-11 15:02:33 +0200
commit3a4992633ee62d5edfbb484d9c6bcb3cf158489d (patch)
treee4510b39bdac9c318fdb4b47018d08f15368b8f0 /server/server.ts
parent04d1da5621d25d59bd5fa1543b725c497bf5d9a8 (diff)
downloadPeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.gz
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.zst
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.zip
Migrate server to ESM
Sorry for the very big commit that may lead to git log issues and merge conflicts, but it's a major step forward: * Server can be faster at startup because imports() are async and we can easily lazy import big modules * Angular doesn't seem to support ES import (with .js extension), so we had to correctly organize peertube into a monorepo: * Use yarn workspace feature * Use typescript reference projects for dependencies * Shared projects have been moved into "packages", each one is now a node module (with a dedicated package.json/tsconfig.json) * server/tools have been moved into apps/ and is now a dedicated app bundled and published on NPM so users don't have to build peertube cli tools manually * server/tests have been moved into packages/ so we don't compile them every time we want to run the server * Use isolatedModule option: * Had to move from const enum to const (https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums) * Had to explictely specify "type" imports when used in decorators * Prefer tsx (that uses esbuild under the hood) instead of ts-node to load typescript files (tests with mocha or scripts): * To reduce test complexity as esbuild doesn't support decorator metadata, we only test server files that do not import server models * We still build tests files into js files for a faster CI * Remove unmaintained peertube CLI import script * Removed some barrels to speed up execution (less imports)
Diffstat (limited to 'server/server.ts')
-rw-r--r--server/server.ts376
1 files changed, 376 insertions, 0 deletions
diff --git a/server/server.ts b/server/server.ts
new file mode 100644
index 000000000..7f9c68ad9
--- /dev/null
+++ b/server/server.ts
@@ -0,0 +1,376 @@
1import { registerOpentelemetryTracing } from '@server/lib/opentelemetry/tracing.js'
2await registerOpentelemetryTracing()
3
4process.title = 'peertube'
5
6// ----------- Core checker -----------
7import { checkMissedConfig, checkFFmpeg, checkNodeVersion } from './server/initializers/checker-before-init.js'
8
9// Do not use barrels because we don't want to load all modules here (we need to initialize database first)
10import { CONFIG } from './server/initializers/config.js'
11import { API_VERSION, WEBSERVER, loadLanguages } from './server/initializers/constants.js'
12import { logger } from './server/helpers/logger.js'
13
14const missed = checkMissedConfig()
15if (missed.length !== 0) {
16 logger.error('Your configuration files miss keys: ' + missed)
17 process.exit(-1)
18}
19
20checkFFmpeg(CONFIG)
21 .catch(err => {
22 logger.error('Error in ffmpeg check.', { err })
23 process.exit(-1)
24 })
25
26try {
27 checkNodeVersion()
28} catch (err) {
29 logger.error('Error in NodeJS check.', { err })
30 process.exit(-1)
31}
32
33import { checkConfig, checkActivityPubUrls, checkFFmpegVersion } from './server/initializers/checker-after-init.js'
34
35try {
36 checkConfig()
37} catch (err) {
38 logger.error('Config error.', { err })
39 process.exit(-1)
40}
41
42// ----------- Database -----------
43
44// Initialize database and models
45import { initDatabaseModels, checkDatabaseConnectionOrDie } from './server/initializers/database.js'
46checkDatabaseConnectionOrDie()
47
48import { migrate } from './server/initializers/migrator.js'
49migrate()
50 .then(() => initDatabaseModels(false))
51 .then(() => startApplication())
52 .catch(err => {
53 logger.error('Cannot start application.', { err })
54 process.exit(-1)
55 })
56
57// ----------- Initialize -----------
58loadLanguages()
59 .catch(err => logger.error('Cannot load languages', { err }))
60
61// Express configuration
62import express from 'express'
63import morgan, { token } from 'morgan'
64import cors from 'cors'
65import cookieParser from 'cookie-parser'
66import { frameguard } from 'helmet'
67import { parse } from 'useragent'
68import anonymize from 'ip-anonymize'
69import { program as cli } from 'commander'
70
71const app = express().disable('x-powered-by')
72
73// Trust our proxy (IP forwarding...)
74app.set('trust proxy', CONFIG.TRUST_PROXY)
75
76app.use((_req, res, next) => {
77 // OpenTelemetry
78 res.locals.requestStart = Date.now()
79
80 if (CONFIG.SECURITY.POWERED_BY_HEADER.ENABLED === true) {
81 res.setHeader('x-powered-by', 'PeerTube')
82 }
83
84 return next()
85})
86
87// Security middleware
88import { baseCSP } from './server/middlewares/csp.js'
89
90if (CONFIG.CSP.ENABLED) {
91 app.use(baseCSP)
92}
93
94if (CONFIG.SECURITY.FRAMEGUARD.ENABLED) {
95 app.use(frameguard({
96 action: 'deny' // we only allow it for /videos/embed, see server/controllers/client.ts
97 }))
98}
99
100// ----------- PeerTube modules -----------
101import { installApplication } from './server/initializers/installer.js'
102import { Emailer } from './server/lib/emailer.js'
103import { JobQueue } from './server/lib/job-queue/index.js'
104import {
105 activityPubRouter,
106 apiRouter,
107 miscRouter,
108 clientsRouter,
109 feedsRouter,
110 staticRouter,
111 wellKnownRouter,
112 lazyStaticRouter,
113 servicesRouter,
114 objectStorageProxyRouter,
115 pluginsRouter,
116 trackerRouter,
117 createWebsocketTrackerServer,
118 sitemapRouter,
119 downloadRouter
120} from './server/controllers/index.js'
121import { advertiseDoNotTrack } from './server/middlewares/dnt.js'
122import { apiFailMiddleware } from './server/middlewares/error.js'
123import { Redis } from './server/lib/redis.js'
124import { ActorFollowScheduler } from './server/lib/schedulers/actor-follow-scheduler.js'
125import { RemoveOldViewsScheduler } from './server/lib/schedulers/remove-old-views-scheduler.js'
126import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-scheduler.js'
127import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler.js'
128import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler.js'
129import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler.js'
130import { AutoFollowIndexInstances } from './server/lib/schedulers/auto-follow-index-instances.js'
131import { RemoveDanglingResumableUploadsScheduler } from './server/lib/schedulers/remove-dangling-resumable-uploads-scheduler.js'
132import { VideoViewsBufferScheduler } from './server/lib/schedulers/video-views-buffer-scheduler.js'
133import { GeoIPUpdateScheduler } from './server/lib/schedulers/geo-ip-update-scheduler.js'
134import { RunnerJobWatchDogScheduler } from './server/lib/schedulers/runner-job-watch-dog-scheduler.js'
135import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto.js'
136import { PeerTubeSocket } from './server/lib/peertube-socket.js'
137import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls.js'
138import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler.js'
139import { PeerTubeVersionCheckScheduler } from './server/lib/schedulers/peertube-version-check-scheduler.js'
140import { Hooks } from './server/lib/plugins/hooks.js'
141import { PluginManager } from './server/lib/plugins/plugin-manager.js'
142import { LiveManager } from './server/lib/live/index.js'
143import { HttpStatusCode } from '@peertube/peertube-models'
144import { ServerConfigManager } from '@server/lib/server-config-manager.js'
145import { VideoViewsManager } from '@server/lib/views/video-views-manager.js'
146import { isTestOrDevInstance } from '@peertube/peertube-node-utils'
147import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics.js'
148import { ApplicationModel } from '@server/models/application/application.js'
149import { VideoChannelSyncLatestScheduler } from '@server/lib/schedulers/video-channel-sync-latest-scheduler.js'
150
151// ----------- Command line -----------
152
153cli
154 .option('--no-client', 'Start PeerTube without client interface')
155 .option('--no-plugins', 'Start PeerTube without plugins/themes enabled')
156 .option('--benchmark-startup', 'Automatically stop server when initialized')
157 .parse(process.argv)
158
159// ----------- App -----------
160
161// Enable CORS for develop
162if (isTestOrDevInstance()) {
163 app.use(cors({
164 origin: '*',
165 exposedHeaders: 'Retry-After',
166 credentials: true
167 }))
168}
169
170// For the logger
171token('remote-addr', (req: express.Request) => {
172 if (CONFIG.LOG.ANONYMIZE_IP === true || req.get('DNT') === '1') {
173 return anonymize(req.ip, 16, 16)
174 }
175
176 return req.ip
177})
178token('user-agent', (req: express.Request) => {
179 if (req.get('DNT') === '1') {
180 return parse(req.get('user-agent')).family
181 }
182
183 return req.get('user-agent')
184})
185app.use(morgan('combined', {
186 stream: {
187 write: (str: string) => logger.info(str.trim(), { tags: [ 'http' ] })
188 },
189 skip: req => CONFIG.LOG.LOG_PING_REQUESTS === false && req.originalUrl === '/api/v1/ping'
190}))
191
192// Add .fail() helper to response
193app.use(apiFailMiddleware)
194
195// For body requests
196app.use(express.urlencoded({ extended: false }))
197app.use(express.json({
198 type: [ 'application/json', 'application/*+json' ],
199 limit: '500kb',
200 verify: (req: express.Request, res: express.Response, buf: Buffer) => {
201 const valid = isHTTPSignatureDigestValid(buf, req)
202
203 if (valid !== true) {
204 res.fail({
205 status: HttpStatusCode.FORBIDDEN_403,
206 message: 'Invalid digest'
207 })
208 }
209 }
210}))
211
212// Cookies
213app.use(cookieParser())
214
215// W3C DNT Tracking Status
216app.use(advertiseDoNotTrack)
217
218// ----------- Open Telemetry -----------
219
220OpenTelemetryMetrics.Instance.init(app)
221
222// ----------- Views, routes and static files -----------
223
224app.use('/api/' + API_VERSION, apiRouter)
225
226// Services (oembed...)
227app.use('/services', servicesRouter)
228
229// Plugins & themes
230app.use('/', pluginsRouter)
231
232app.use('/', activityPubRouter)
233app.use('/', feedsRouter)
234app.use('/', trackerRouter)
235app.use('/', sitemapRouter)
236
237// Static files
238app.use('/', staticRouter)
239app.use('/', wellKnownRouter)
240app.use('/', miscRouter)
241app.use('/', downloadRouter)
242app.use('/', lazyStaticRouter)
243app.use('/', objectStorageProxyRouter)
244
245// Client files, last valid routes!
246const cliOptions = cli.opts<{ client: boolean, plugins: boolean }>()
247if (cliOptions.client) app.use('/', clientsRouter)
248
249// ----------- Errors -----------
250
251// Catch unmatched routes
252app.use((_req, res: express.Response) => {
253 res.status(HttpStatusCode.NOT_FOUND_404).end()
254})
255
256// Catch thrown errors
257app.use((err, _req, res: express.Response, _next) => {
258 // Format error to be logged
259 let error = 'Unknown error.'
260 if (err) {
261 error = err.stack || err.message || err
262 }
263
264 // Handling Sequelize error traces
265 const sql = err?.parent ? err.parent.sql : undefined
266
267 // Help us to debug SequelizeConnectionAcquireTimeoutError errors
268 const activeRequests = err?.name === 'SequelizeConnectionAcquireTimeoutError' && typeof (process as any)._getActiveRequests !== 'function'
269 ? (process as any)._getActiveRequests()
270 : undefined
271
272 logger.error('Error in controller.', { err: error, sql, activeRequests })
273
274 return res.fail({
275 status: err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500,
276 message: err.message,
277 type: err.name
278 })
279})
280
281const { server, trackerServer } = createWebsocketTrackerServer(app)
282
283// ----------- Run -----------
284
285async function startApplication () {
286 const port = CONFIG.LISTEN.PORT
287 const hostname = CONFIG.LISTEN.HOSTNAME
288
289 await installApplication()
290
291 // Check activity pub urls are valid
292 checkActivityPubUrls()
293 .catch(err => {
294 logger.error('Error in ActivityPub URLs checker.', { err })
295 process.exit(-1)
296 })
297
298 checkFFmpegVersion()
299 .catch(err => logger.error('Cannot check ffmpeg version', { err }))
300
301 Redis.Instance.init()
302 Emailer.Instance.init()
303
304 await Promise.all([
305 Emailer.Instance.checkConnection(),
306 JobQueue.Instance.init(),
307 ServerConfigManager.Instance.init()
308 ])
309
310 // Enable Schedulers
311 ActorFollowScheduler.Instance.enable()
312 UpdateVideosScheduler.Instance.enable()
313 YoutubeDlUpdateScheduler.Instance.enable()
314 VideosRedundancyScheduler.Instance.enable()
315 RemoveOldHistoryScheduler.Instance.enable()
316 RemoveOldViewsScheduler.Instance.enable()
317 PluginsCheckScheduler.Instance.enable()
318 PeerTubeVersionCheckScheduler.Instance.enable()
319 AutoFollowIndexInstances.Instance.enable()
320 RemoveDanglingResumableUploadsScheduler.Instance.enable()
321 VideoChannelSyncLatestScheduler.Instance.enable()
322 VideoViewsBufferScheduler.Instance.enable()
323 GeoIPUpdateScheduler.Instance.enable()
324 RunnerJobWatchDogScheduler.Instance.enable()
325
326 OpenTelemetryMetrics.Instance.registerMetrics({ trackerServer })
327
328 PluginManager.Instance.init(server)
329 // Before PeerTubeSocket init
330 PluginManager.Instance.registerWebSocketRouter()
331
332 PeerTubeSocket.Instance.init(server)
333 VideoViewsManager.Instance.init()
334
335 updateStreamingPlaylistsInfohashesIfNeeded()
336 .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
337
338 LiveManager.Instance.init()
339 if (CONFIG.LIVE.ENABLED) await LiveManager.Instance.run()
340
341 // Make server listening
342 server.listen(port, hostname, async () => {
343 if (cliOptions.plugins) {
344 try {
345 await PluginManager.Instance.rebuildNativePluginsIfNeeded()
346
347 await PluginManager.Instance.registerPluginsAndThemes()
348 } catch (err) {
349 logger.error('Cannot register plugins and themes.', { err })
350 }
351 }
352
353 ApplicationModel.updateNodeVersions()
354 .catch(err => logger.error('Cannot update node versions.', { err }))
355
356 JobQueue.Instance.start()
357 .catch(err => {
358 logger.error('Cannot start job queue.', { err })
359 process.exit(-1)
360 })
361
362 logger.info('HTTP server listening on %s:%d', hostname, port)
363 logger.info('Web server: %s', WEBSERVER.URL)
364
365 Hooks.runAction('action:application.listening')
366
367 if (cliOptions['benchmarkStartup']) process.exit(0)
368 })
369
370 process.on('exit', () => {
371 JobQueue.Instance.terminate()
372 .catch(err => logger.error('Cannot terminate job queue.', { err }))
373 })
374
375 process.on('SIGINT', () => process.exit(0))
376}