import express from 'express' ; import morgan , { token } from 'morgan' ; import cors from 'cors' ; import cookieParser from 'cookie-parser' ; import { frameguard } from 'helmet' ; import { parse } from 'useragent' ; import anonymize from 'ip-anonymize' ; import { program as cli } from 'commander' ; process . title = 'peertube' ; const app = express ( ) . disable ( 'x-powered-by' ) ; import { checkMissedConfig , checkFFmpeg , checkNodeVersion } from './server/initializers/checker-before-init' ; import { CONFIG } from './server/initializers/config' ; import { API_VERSION , FILES_CACHE , WEBSERVER , loadLanguages } from './server/initializers/constants' ; import { logger } from './server/helpers/logger' ; const missed = checkMissedConfig ( ) ; if ( missed . length !== 0 ) { logger . error ( 'Your configuration files miss keys: ' + missed ) ; process . exit ( - 1 ) ; } checkFFmpeg ( CONFIG ) . catch ( err => { logger . error ( 'Error in ffmpeg check.' , { err } ) ; process . exit ( - 1 ) ; } ) ; try { checkNodeVersion ( ) ; } catch ( err ) { logger . error ( 'Error in NodeJS check.' , { err } ) ; process . exit ( - 1 ) ; } import { checkConfig , checkActivityPubUrls , checkFFmpegVersion } from './server/initializers/checker-after-init' ; const errorMessage = checkConfig ( ) ; if ( errorMessage !== null ) { throw new Error ( errorMessage ) ; } app . set ( 'trust proxy' , CONFIG . TRUST_PROXY ) ; import { baseCSP } from './server/middlewares/csp' ; if ( CONFIG . CSP . ENABLED ) { app . use ( baseCSP ) ; } if ( CONFIG . SECURITY . FRAMEGUARD . ENABLED ) { app . use ( frameguard ( { action : 'deny' } ) ) ; } import { initDatabaseModels , checkDatabaseConnectionOrDie } from './server/initializers/database' ; checkDatabaseConnectionOrDie ( ) ; import { migrate } from './server/initializers/migrator' ; migrate ( ) . then ( ( ) => initDatabaseModels ( false ) ) . then ( ( ) => startApplication ( ) ) . catch ( err => { logger . error ( 'Cannot start application.' , { err } ) ; process . exit ( - 1 ) ; } ) ; loadLanguages ( ) ; import { installApplication } from './server/initializers/installer' ; import { Emailer } from './server/lib/emailer' ; import { JobQueue } from './server/lib/job-queue' ; import { VideosPreviewCache , VideosCaptionCache } from './server/lib/files-cache' ; import { activityPubRouter , apiRouter , clientsRouter , feedsRouter , staticRouter , lazyStaticRouter , servicesRouter , liveRouter , pluginsRouter , webfingerRouter , trackerRouter , createWebsocketTrackerServer , botsRouter , downloadRouter } from './server/controllers' ; import { advertiseDoNotTrack } from './server/middlewares/dnt' ; import { apiFailMiddleware } from './server/middlewares/error' ; import { Redis } from './server/lib/redis' ; import { ActorFollowScheduler } from './server/lib/schedulers/actor-follow-scheduler' ; import { RemoveOldViewsScheduler } from './server/lib/schedulers/remove-old-views-scheduler' ; import { RemoveOldJobsScheduler } from './server/lib/schedulers/remove-old-jobs-scheduler' ; import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-scheduler' ; import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler' ; import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler' ; import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler' ; import { AutoFollowIndexInstances } from './server/lib/schedulers/auto-follow-index-instances' ; import { RemoveDanglingResumableUploadsScheduler } from './server/lib/schedulers/remove-dangling-resumable-uploads-scheduler' ; import { VideoViewsBufferScheduler } from './server/lib/schedulers/video-views-buffer-scheduler' ; import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto' ; import { PeerTubeSocket } from './server/lib/peertube-socket' ; import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls' ; import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler' ; import { PeerTubeVersionCheckScheduler } from './server/lib/schedulers/peertube-version-check-scheduler' ; import { Hooks } from './server/lib/plugins/hooks' ; import { PluginManager } from './server/lib/plugins/plugin-manager' ; import { LiveManager } from './server/lib/live' ; import { HttpStatusCode } from './shared/models/http/http-error-codes' ; import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' ; import { ServerConfigManager } from '@server/lib/server-config-manager' ; import { VideoViews } from '@server/lib/video-views' ; import { isTestInstance } from './server/helpers/core-utils' ; cli . option ( '--no-client' , 'Start PeerTube without client interface' ) . option ( '--no-plugins' , 'Start PeerTube without plugins/themes enabled' ) . option ( '--benchmark-startup' , 'Automatically stop server when initialized' ) . parse ( process . argv ) ; if ( isTestInstance ( ) ) { app . use ( cors ( { origin : '*' , exposedHeaders : 'Retry-After' , credentials : true } ) ) ; } token ( 'remote-addr' , ( req ) => { if ( CONFIG . LOG . ANONYMIZE_IP === true || req . get ( 'DNT' ) === '1' ) { return anonymize ( req . ip , 16 , 16 ) ; } return req . ip ; } ) ; token ( 'user-agent' , ( req ) => { if ( req . get ( 'DNT' ) === '1' ) { return parse ( req . get ( 'user-agent' ) ) . family ; } return req . get ( 'user-agent' ) ; } ) ; app . use ( morgan ( 'combined' , { stream : { write : ( str ) => logger . info ( str . trim ( ) , { tags : [ 'http' ] } ) } , skip : req => CONFIG . LOG . LOG_PING_REQUESTS === false && req . originalUrl === '/api/v1/ping' } ) ) ; app . use ( apiFailMiddleware ) ; app . use ( express . urlencoded ( { extended : false } ) ) ; app . use ( express . json ( { type : [ 'application/json' , 'application/*+json' ] , limit : '500kb' , verify : ( req , res , buf ) => { const valid = isHTTPSignatureDigestValid ( buf , req ) ; if ( valid !== true ) { res . fail ( { status : HttpStatusCode . FORBIDDEN_403 , message : 'Invalid digest' } ) ; } } } ) ) ; app . use ( cookieParser ( ) ) ; app . use ( advertiseDoNotTrack ) ; const apiRoute = '/api/' + API_VERSION ; app . use ( apiRoute , apiRouter ) ; app . use ( '/services' , servicesRouter ) ; app . use ( '/live' , liveRouter ) ; app . use ( '/' , pluginsRouter ) ; app . use ( '/' , activityPubRouter ) ; app . use ( '/' , feedsRouter ) ; app . use ( '/' , webfingerRouter ) ; app . use ( '/' , trackerRouter ) ; app . use ( '/' , botsRouter ) ; app . use ( '/' , staticRouter ) ; app . use ( '/' , downloadRouter ) ; app . use ( '/' , lazyStaticRouter ) ; const cliOptions = cli . opts ( ) ; if ( cliOptions . client ) app . use ( '/' , clientsRouter ) ; app . use ( ( req , res ) => { res . status ( HttpStatusCode . NOT_FOUND_404 ) . end ( ) ; } ) ; app . use ( ( err , req , res , next ) => { let error = 'Unknown error.' ; if ( err ) { error = err . stack || err . message || err ; } const sql = err . parent ? err . parent . sql : undefined ; logger . error ( 'Error in controller.' , { err : error , sql } ) ; return res . fail ( { status : err . status || HttpStatusCode . INTERNAL_SERVER_ERROR_500 , message : err . message , type : err . name } ) ; } ) ; const server = createWebsocketTrackerServer ( app ) ; async function startApplication ( ) { const port = CONFIG . LISTEN . PORT ; const hostname = CONFIG . LISTEN . HOSTNAME ; await installApplication ( ) ; checkActivityPubUrls ( ) . catch ( err => { logger . error ( 'Error in ActivityPub URLs checker.' , { err } ) ; process . exit ( - 1 ) ; } ) ; checkFFmpegVersion ( ) . catch ( err => logger . error ( 'Cannot check ffmpeg version' , { err } ) ) ; Emailer . Instance . init ( ) ; await Promise . all ( [ Emailer . Instance . checkConnection ( ) , JobQueue . Instance . init ( ) , ServerConfigManager . Instance . init ( ) ] ) ; VideosPreviewCache . Instance . init ( CONFIG . CACHE . PREVIEWS . SIZE , FILES_CACHE . PREVIEWS . MAX_AGE ) ; VideosCaptionCache . Instance . init ( CONFIG . CACHE . VIDEO_CAPTIONS . SIZE , FILES_CACHE . VIDEO_CAPTIONS . MAX_AGE ) ; VideosTorrentCache . Instance . init ( CONFIG . CACHE . TORRENTS . SIZE , FILES_CACHE . TORRENTS . MAX_AGE ) ; ActorFollowScheduler . Instance . enable ( ) ; RemoveOldJobsScheduler . Instance . enable ( ) ; UpdateVideosScheduler . Instance . enable ( ) ; YoutubeDlUpdateScheduler . Instance . enable ( ) ; VideosRedundancyScheduler . Instance . enable ( ) ; RemoveOldHistoryScheduler . Instance . enable ( ) ; RemoveOldViewsScheduler . Instance . enable ( ) ; PluginsCheckScheduler . Instance . enable ( ) ; PeerTubeVersionCheckScheduler . Instance . enable ( ) ; AutoFollowIndexInstances . Instance . enable ( ) ; RemoveDanglingResumableUploadsScheduler . Instance . enable ( ) ; VideoViewsBufferScheduler . Instance . enable ( ) ; Redis . Instance . init ( ) ; PeerTubeSocket . Instance . init ( server ) ; VideoViews . Instance . init ( ) ; updateStreamingPlaylistsInfohashesIfNeeded ( ) . catch ( err => logger . error ( 'Cannot update streaming playlist infohashes.' , { err } ) ) ; LiveManager . Instance . init ( ) ; if ( CONFIG . LIVE . ENABLED ) await LiveManager . Instance . run ( ) ; server . listen ( port , hostname , async ( ) => { if ( cliOptions . plugins ) { try { await PluginManager . Instance . registerPluginsAndThemes ( ) ; } catch ( err ) { logger . error ( 'Cannot register plugins and themes.' , { err } ) ; } } logger . info ( 'HTTP server listening on %s:%d' , hostname , port ) ; logger . info ( 'Web server: %s' , WEBSERVER . URL ) ; Hooks . runAction ( 'action:application.listening' ) ; if ( cliOptions [ 'benchmarkStartup' ] ) process . exit ( 0 ) ; } ) ; process . on ( 'exit' , ( ) => { JobQueue . Instance . terminate ( ) ; } ) ; process . on ( 'SIGINT' , ( ) => process . exit ( 0 ) ) ; }