aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-04-10 09:23:18 +0200
committerChocobozzz <me@florianbigard.com>2019-04-10 09:23:18 +0200
commit31b6ddf86652502e0c96d77fa10861ce4af11aa4 (patch)
treeb94402972945699134b2a504af5d551124f0bf54
parent22834691abb6e74d31654ffd2ebeaaaa8ef3ac7b (diff)
downloadPeerTube-31b6ddf86652502e0c96d77fa10861ce4af11aa4.tar.gz
PeerTube-31b6ddf86652502e0c96d77fa10861ce4af11aa4.tar.zst
PeerTube-31b6ddf86652502e0c96d77fa10861ce4af11aa4.zip
Add ability to disable tracker
-rw-r--r--client/src/app/core/server/server.service.ts3
-rw-r--r--client/src/app/shared/instance/instance-features-table.component.ts5
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.scss1
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts18
-rw-r--r--client/src/assets/player/utils.ts5
-rw-r--r--client/src/sass/player/peertube-skin.scss6
-rw-r--r--client/src/standalone/videos/embed.ts26
-rw-r--r--config/default.yaml10
-rw-r--r--config/production.yaml.example10
-rw-r--r--server/controllers/api/config.ts3
-rw-r--r--server/controllers/tracker.ts23
-rw-r--r--server/initializers/checker-before-init.ts3
-rw-r--r--server/initializers/constants.ts5
-rw-r--r--server/tests/api/server/tracker.ts26
-rw-r--r--shared/models/server/server-config.model.ts4
-rw-r--r--support/docker/production/config/production.yaml4
16 files changed, 123 insertions, 29 deletions
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index b0c5d1130..3a8a535fd 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -105,6 +105,9 @@ export class ServerService {
105 enabled: false 105 enabled: false
106 } 106 }
107 } 107 }
108 },
109 tracker: {
110 enabled: true
108 } 111 }
109 } 112 }
110 private videoCategories: Array<VideoConstant<number>> = [] 113 private videoCategories: Array<VideoConstant<number>> = []
diff --git a/client/src/app/shared/instance/instance-features-table.component.ts b/client/src/app/shared/instance/instance-features-table.component.ts
index c0257fd59..72e7c2730 100644
--- a/client/src/app/shared/instance/instance-features-table.component.ts
+++ b/client/src/app/shared/instance/instance-features-table.component.ts
@@ -1,7 +1,6 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { ServerService } from '@app/core' 2import { ServerService } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { ServerConfig } from '../../../../../shared'
5 4
6@Component({ 5@Component({
7 selector: 'my-instance-features-table', 6 selector: 'my-instance-features-table',
@@ -65,6 +64,10 @@ export class InstanceFeaturesTableComponent implements OnInit {
65 { 64 {
66 label: this.i18n('Torrent import'), 65 label: this.i18n('Torrent import'),
67 value: config.import.videos.torrent.enabled 66 value: config.import.videos.torrent.enabled
67 },
68 {
69 label: this.i18n('P2P enabled'),
70 value: config.tracker.enabled
68 } 71 }
69 ] 72 ]
70 } 73 }
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index d61a0bc3e..84b9aed39 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -427,6 +427,7 @@ my-video-comments {
427// If the view is not expanded, take into account the menu 427// If the view is not expanded, take into account the menu
428.privacy-concerns { 428.privacy-concerns {
429 width: calc(100% - #{$menu-width}); 429 width: calc(100% - #{$menu-width});
430 margin-left: -15px;
430} 431}
431 432
432@media screen and (max-width: $small-view) { 433@media screen and (max-width: $small-view) {
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index edc546b28..bce652210 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -29,6 +29,7 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
29import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' 29import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
30import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 30import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
31import { Video } from '@app/shared/video/video.model' 31import { Video } from '@app/shared/video/video.model'
32import { isWebRTCDisabled } from '../../../assets/player/utils'
32 33
33@Component({ 34@Component({
34 selector: 'my-video-watch', 35 selector: 'my-video-watch',
@@ -71,6 +72,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
71 private currentTime: number 72 private currentTime: number
72 private paramsSub: Subscription 73 private paramsSub: Subscription
73 private queryParamsSub: Subscription 74 private queryParamsSub: Subscription
75 private configSub: Subscription
74 76
75 constructor ( 77 constructor (
76 private elementRef: ElementRef, 78 private elementRef: ElementRef,
@@ -100,12 +102,16 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
100 } 102 }
101 103
102 ngOnInit () { 104 ngOnInit () {
103 if ( 105 this.configSub = this.serverService.configLoaded
104 !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false || 106 .subscribe(() => {
105 peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' 107 if (
106 ) { 108 isWebRTCDisabled() ||
107 this.hasAlreadyAcceptedPrivacyConcern = true 109 this.serverService.getConfig().tracker.enabled === false ||
108 } 110 peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true'
111 ) {
112 this.hasAlreadyAcceptedPrivacyConcern = true
113 }
114 })
109 115
110 this.paramsSub = this.route.params.subscribe(routeParams => { 116 this.paramsSub = this.route.params.subscribe(routeParams => {
111 const videoId = routeParams[ 'videoId' ] 117 const videoId = routeParams[ 'videoId' ]
diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts
index 0966027ac..366689962 100644
--- a/client/src/assets/player/utils.ts
+++ b/client/src/assets/player/utils.ts
@@ -4,6 +4,10 @@ function toTitleCase (str: string) {
4 return str.charAt(0).toUpperCase() + str.slice(1) 4 return str.charAt(0).toUpperCase() + str.slice(1)
5} 5}
6 6
7function isWebRTCDisabled () {
8 return !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false
9}
10
7// https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts 11// https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts
8// Don't import all Angular stuff, just copy the code with shame 12// Don't import all Angular stuff, just copy the code with shame
9const dictionaryBytes: Array<{max: number, type: string}> = [ 13const dictionaryBytes: Array<{max: number, type: string}> = [
@@ -141,6 +145,7 @@ export {
141 toTitleCase, 145 toTitleCase,
142 timeToInt, 146 timeToInt,
143 secondsToTime, 147 secondsToTime,
148 isWebRTCDisabled,
144 buildVideoLink, 149 buildVideoLink,
145 buildVideoEmbed, 150 buildVideoEmbed,
146 videoFileMaxByResolution, 151 videoFileMaxByResolution,
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss
index a6942001a..e63a2875c 100644
--- a/client/src/sass/player/peertube-skin.scss
+++ b/client/src/sass/player/peertube-skin.scss
@@ -21,16 +21,16 @@
21 .vjs-dock-description { 21 .vjs-dock-description {
22 font-size: 11px; 22 font-size: 11px;
23 23
24 &::before, &::after { 24 .text::before, .text::after {
25 display: inline-block; 25 display: inline-block;
26 content: '\1F308'; 26 content: '\1F308';
27 } 27 }
28 28
29 &::before { 29 .text::before {
30 margin-right: 4px; 30 margin-right: 4px;
31 } 31 }
32 32
33 &::after { 33 .text::after {
34 margin-left: 4px; 34 margin-left: 4px;
35 transform: scale(-1, 1); 35 transform: scale(-1, 1);
36 } 36 }
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 626d55a7c..707f04253 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -2,7 +2,7 @@ import './embed.scss'
2 2
3import * as Channel from 'jschannel' 3import * as Channel from 'jschannel'
4 4
5import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared' 5import { peertubeTranslate, ResultList, ServerConfig, VideoDetails } from '../../../../shared'
6import { PeerTubeResolution } from '../player/definitions' 6import { PeerTubeResolution } from '../player/definitions'
7import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' 7import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
8import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' 8import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
@@ -177,6 +177,10 @@ class PeerTubeEmbed {
177 return fetch(this.getVideoUrl(videoId) + '/captions') 177 return fetch(this.getVideoUrl(videoId) + '/captions')
178 } 178 }
179 179
180 loadConfig (): Promise<Response> {
181 return fetch('/api/v1/config')
182 }
183
180 removeElement (element: HTMLElement) { 184 removeElement (element: HTMLElement) {
181 element.parentElement.removeChild(element) 185 element.parentElement.removeChild(element)
182 } 186 }
@@ -237,10 +241,10 @@ class PeerTubeEmbed {
237 try { 241 try {
238 const params = new URL(window.location.toString()).searchParams 242 const params = new URL(window.location.toString()).searchParams
239 243
240 this.autoplay = this.getParamToggle(params, 'autoplay') 244 this.autoplay = this.getParamToggle(params, 'autoplay', false)
241 this.controls = this.getParamToggle(params, 'controls') 245 this.controls = this.getParamToggle(params, 'controls', true)
242 this.muted = this.getParamToggle(params, 'muted') 246 this.muted = this.getParamToggle(params, 'muted', false)
243 this.loop = this.getParamToggle(params, 'loop') 247 this.loop = this.getParamToggle(params, 'loop', false)
244 this.enableApi = this.getParamToggle(params, 'api', this.enableApi) 248 this.enableApi = this.getParamToggle(params, 'api', this.enableApi)
245 249
246 this.scope = this.getParamString(params, 'scope', this.scope) 250 this.scope = this.getParamString(params, 'scope', this.scope)
@@ -258,10 +262,11 @@ class PeerTubeEmbed {
258 const urlParts = window.location.pathname.split('/') 262 const urlParts = window.location.pathname.split('/')
259 const videoId = urlParts[ urlParts.length - 1 ] 263 const videoId = urlParts[ urlParts.length - 1 ]
260 264
261 const [ serverTranslations, videoResponse, captionsResponse ] = await Promise.all([ 265 const [ serverTranslations, videoResponse, captionsResponse, configResponse ] = await Promise.all([
262 PeertubePlayerManager.getServerTranslations(window.location.origin, navigator.language), 266 PeertubePlayerManager.getServerTranslations(window.location.origin, navigator.language),
263 this.loadVideoInfo(videoId), 267 this.loadVideoInfo(videoId),
264 this.loadVideoCaptions(videoId) 268 this.loadVideoCaptions(videoId),
269 this.loadConfig()
265 ]) 270 ])
266 271
267 if (!videoResponse.ok) { 272 if (!videoResponse.ok) {
@@ -338,9 +343,14 @@ class PeerTubeEmbed {
338 window[ 'videojsPlayer' ] = this.player 343 window[ 'videojsPlayer' ] = this.player
339 344
340 if (this.controls) { 345 if (this.controls) {
346 const config: ServerConfig = await configResponse.json()
347 const description = config.tracker.enabled
348 ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>'
349 : undefined
350
341 this.player.dock({ 351 this.player.dock({
342 title: videoInfo.name, 352 title: videoInfo.name,
343 description: this.player.localize('Uses P2P, others may know your IP is downloading this video.') 353 description
344 }) 354 })
345 } 355 }
346 356
diff --git a/config/default.yaml b/config/default.yaml
index 0d6e34d86..617159c2c 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -101,6 +101,16 @@ csp:
101 report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk! 101 report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk!
102 report_uri: 102 report_uri:
103 103
104tracker:
105 # If you disable the tracker, you disable the P2P aspect of PeerTube
106 enabled: true
107 # Only handle requests on your videos.
108 # If you set this to false it means you have a public tracker.
109 # Then, it is possible that clients overload your instance with external torrents
110 private: true
111 # Reject peers that do a lot of announces (could improve privacy of TCP/UDP peers)
112 reject_too_many_announces: false
113
104cache: 114cache:
105 previews: 115 previews:
106 size: 500 # Max number of previews you want to cache 116 size: 500 # Max number of previews you want to cache
diff --git a/config/production.yaml.example b/config/production.yaml.example
index 5029cc25b..dd5c9769b 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -102,6 +102,16 @@ csp:
102 report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk! 102 report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk!
103 report_uri: 103 report_uri:
104 104
105tracker:
106 # If you disable the tracker, you disable the P2P aspect of PeerTube
107 enabled: true
108 # Only handle requests on your videos.
109 # If you set this to false it means you have a public tracker.
110 # Then, it is possible that clients overload your instance with external torrents
111 private: true
112 # Reject peers that do a lot of announces (could improve privacy of TCP/UDP peers)
113 reject_too_many_announces: false
114
105 115
106############################################################################### 116###############################################################################
107# 117#
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 5c4f455ee..0d7fc8625 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -136,6 +136,9 @@ async function getConfig (req: express.Request, res: express.Response) {
136 videos: { 136 videos: {
137 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS 137 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
138 } 138 }
139 },
140 tracker: {
141 enabled: CONFIG.TRACKER.ENABLED
139 } 142 }
140 } 143 }
141 144
diff --git a/server/controllers/tracker.ts b/server/controllers/tracker.ts
index 8b77d9de7..56a3424a3 100644
--- a/server/controllers/tracker.ts
+++ b/server/controllers/tracker.ts
@@ -23,6 +23,10 @@ const trackerServer = new TrackerServer({
23 ws: false, 23 ws: false,
24 dht: false, 24 dht: false,
25 filter: async function (infoHash, params, cb) { 25 filter: async function (infoHash, params, cb) {
26 if (CONFIG.TRACKER.ENABLED === false) {
27 return cb(new Error('Tracker is disabled on this instance.'))
28 }
29
26 let ip: string 30 let ip: string
27 31
28 if (params.type === 'ws') { 32 if (params.type === 'ws') {
@@ -36,11 +40,13 @@ const trackerServer = new TrackerServer({
36 peersIps[ ip ] = peersIps[ ip ] ? peersIps[ ip ] + 1 : 1 40 peersIps[ ip ] = peersIps[ ip ] ? peersIps[ ip ] + 1 : 1
37 peersIpInfoHash[ key ] = peersIpInfoHash[ key ] ? peersIpInfoHash[ key ] + 1 : 1 41 peersIpInfoHash[ key ] = peersIpInfoHash[ key ] ? peersIpInfoHash[ key ] + 1 : 1
38 42
39 if (peersIpInfoHash[ key ] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) { 43 if (CONFIG.TRACKER.REJECT_TOO_MANY_ANNOUNCES && peersIpInfoHash[ key ] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) {
40 return cb(new Error(`Too many requests (${peersIpInfoHash[ key ]} of ip ${ip} for torrent ${infoHash}`)) 44 return cb(new Error(`Too many requests (${peersIpInfoHash[ key ]} of ip ${ip} for torrent ${infoHash}`))
41 } 45 }
42 46
43 try { 47 try {
48 if (CONFIG.TRACKER.PRIVATE === false) return cb()
49
44 const videoFileExists = await VideoFileModel.doesInfohashExist(infoHash) 50 const videoFileExists = await VideoFileModel.doesInfohashExist(infoHash)
45 if (videoFileExists === true) return cb() 51 if (videoFileExists === true) return cb()
46 52
@@ -55,13 +61,16 @@ const trackerServer = new TrackerServer({
55 } 61 }
56}) 62})
57 63
58trackerServer.on('error', function (err) { 64if (CONFIG.TRACKER.ENABLED !== false) {
59 logger.error('Error in tracker.', { err })
60})
61 65
62trackerServer.on('warning', function (err) { 66 trackerServer.on('error', function (err) {
63 logger.warn('Warning in tracker.', { err }) 67 logger.error('Error in tracker.', { err })
64}) 68 })
69
70 trackerServer.on('warning', function (err) {
71 logger.warn('Warning in tracker.', { err })
72 })
73}
65 74
66const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer) 75const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer)
67trackerRouter.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' })) 76trackerRouter.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' }))
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts
index 3095913a3..6b43debfb 100644
--- a/server/initializers/checker-before-init.ts
+++ b/server/initializers/checker-before-init.ts
@@ -25,7 +25,8 @@ function checkMissedConfig () {
25 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', 25 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
26 'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt', 26 'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
27 'services.twitter.username', 'services.twitter.whitelisted', 27 'services.twitter.username', 'services.twitter.whitelisted',
28 'followers.instance.enabled', 'followers.instance.manual_approval' 28 'followers.instance.enabled', 'followers.instance.manual_approval',
29 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces'
29 ] 30 ]
30 const requiredAlternatives = [ 31 const requiredAlternatives = [
31 [ // set 32 [ // set
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 097199f84..3f02572db 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -243,6 +243,11 @@ const CONFIG = {
243 REPORT_ONLY: config.get<boolean>('csp.report_only'), 243 REPORT_ONLY: config.get<boolean>('csp.report_only'),
244 REPORT_URI: config.get<boolean>('csp.report_uri') 244 REPORT_URI: config.get<boolean>('csp.report_uri')
245 }, 245 },
246 TRACKER: {
247 ENABLED: config.get<boolean>('tracker.enabled'),
248 PRIVATE: config.get<boolean>('tracker.private'),
249 REJECT_TOO_MANY_ANNOUNCES: config.get<boolean>('tracker.reject_too_many_announces')
250 },
246 ADMIN: { 251 ADMIN: {
247 get EMAIL () { return config.get<string>('admin.email') } 252 get EMAIL () { return config.get<string>('admin.email') }
248 }, 253 },
diff --git a/server/tests/api/server/tracker.ts b/server/tests/api/server/tracker.ts
index 25ca00029..41803aef1 100644
--- a/server/tests/api/server/tracker.ts
+++ b/server/tests/api/server/tracker.ts
@@ -2,7 +2,7 @@
2 2
3import * as magnetUtil from 'magnet-uri' 3import * as magnetUtil from 'magnet-uri'
4import 'mocha' 4import 'mocha'
5import { getVideo, killallServers, runServer, ServerInfo, uploadVideo } from '../../../../shared/utils' 5import { getVideo, killallServers, reRunServer, runServer, ServerInfo, uploadVideo } from '../../../../shared/utils'
6import { flushTests, setAccessTokensToServers } from '../../../../shared/utils/index' 6import { flushTests, setAccessTokensToServers } from '../../../../shared/utils/index'
7import { VideoDetails } from '../../../../shared/models/videos' 7import { VideoDetails } from '../../../../shared/models/videos'
8import * as WebTorrent from 'webtorrent' 8import * as WebTorrent from 'webtorrent'
@@ -34,7 +34,7 @@ describe('Test tracker', function () {
34 } 34 }
35 }) 35 })
36 36
37 it('Should return an error when adding an incorrect infohash', done => { 37 it('Should return an error when adding an incorrect infohash', function (done) {
38 this.timeout(10000) 38 this.timeout(10000)
39 const webtorrent = new WebTorrent() 39 const webtorrent = new WebTorrent()
40 40
@@ -49,7 +49,7 @@ describe('Test tracker', function () {
49 torrent.on('done', () => done(new Error('No error on infohash'))) 49 torrent.on('done', () => done(new Error('No error on infohash')))
50 }) 50 })
51 51
52 it('Should succeed with the correct infohash', done => { 52 it('Should succeed with the correct infohash', function (done) {
53 this.timeout(10000) 53 this.timeout(10000)
54 const webtorrent = new WebTorrent() 54 const webtorrent = new WebTorrent()
55 55
@@ -64,6 +64,26 @@ describe('Test tracker', function () {
64 torrent.on('done', done) 64 torrent.on('done', done)
65 }) 65 })
66 66
67 it('Should disable the tracker', function (done) {
68 this.timeout(20000)
69
70 killallServers([ server ])
71 reRunServer(server, { tracker: { enabled: false } })
72 .then(() => {
73 const webtorrent = new WebTorrent()
74
75 const torrent = webtorrent.add(goodMagnet)
76
77 torrent.on('error', done)
78 torrent.on('warning', warn => {
79 const message = typeof warn === 'string' ? warn : warn.message
80 if (message.indexOf('disabled ') !== -1) return done()
81 })
82
83 torrent.on('done', () => done(new Error('Tracker is enabled')))
84 })
85 })
86
67 after(async function () { 87 after(async function () {
68 killallServers([ server ]) 88 killallServers([ server ])
69 }) 89 })
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts
index dcc45be8a..d937e9c05 100644
--- a/shared/models/server/server-config.model.ts
+++ b/shared/models/server/server-config.model.ts
@@ -97,4 +97,8 @@ export interface ServerConfig {
97 intervalDays: number 97 intervalDays: number
98 } 98 }
99 } 99 }
100
101 tracker: {
102 enabled: boolean
103 }
100} 104}
diff --git a/support/docker/production/config/production.yaml b/support/docker/production/config/production.yaml
index 846c838e8..d585cd73e 100644
--- a/support/docker/production/config/production.yaml
+++ b/support/docker/production/config/production.yaml
@@ -46,5 +46,9 @@ storage:
46log: 46log:
47 level: 'info' # debug/info/warning/error 47 level: 'info' # debug/info/warning/error
48 48
49tracker:
50 enabled: true
51 reject_too_many_announces: false # false because we have issues with traefik and ws ip/port forwarding
52
49admin: 53admin:
50 email: null 54 email: null