]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Allow configuration to be static/readonly (#4315)
authorJelle Besseling <jelle@pingiun.com>
Tue, 12 Oct 2021 11:33:44 +0000 (13:33 +0200)
committerGitHub <noreply@github.com>
Tue, 12 Oct 2021 11:33:44 +0000 (13:33 +0200)
* Allow configuration to be static/readonly

* Make all components disableable

* Improve disabled component styling

* Rename edits allowed field in configuration

* Fix CI

23 files changed:
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
client/src/app/shared/shared-forms/markdown-textarea.component.html
client/src/app/shared/shared-forms/markdown-textarea.component.ts
client/src/app/shared/shared-forms/select/select-checkbox.component.html
client/src/app/shared/shared-forms/select/select-checkbox.component.ts
client/src/app/shared/shared-forms/select/select-custom-value.component.html
client/src/app/shared/shared-forms/select/select-custom-value.component.ts
client/src/app/shared/shared-forms/select/select-options.component.html
client/src/app/shared/shared-forms/select/select-options.component.ts
client/src/sass/include/_mixins.scss
config/default.yaml
config/production.yaml.example
server/controllers/api/config.ts
server/initializers/config.ts
server/lib/server-config-manager.ts
server/middlewares/validators/config.ts
server/tests/api/server/config.ts
shared/models/server/server-config.model.ts
support/docker/production/Dockerfile.buster
support/docker/production/config/custom-environment-variables.yaml
support/docker/production/entrypoint.sh

index 3ceea02ca7a0f825e9458d111253e641db4e4cd1..6ae7b1b796bbdb77855b1afdd198637c746cd434 100644 (file)
@@ -63,7 +63,7 @@
     <div class="col-md-7 col-xl-5"></div>
     <div class="col-md-5 col-xl-5">
 
-      <div class="form-error submit-error" i18n *ngIf="!form.valid">
+      <div class="form-error submit-error" i18n *ngIf="!form.valid && serverConfig.allowEdits">
         There are errors in the form:
 
         <ul>
         You cannot allow live replay if you don't enable transcoding.
       </span>
 
-      <input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid || !hasConsistentOptions()">
+      <span i18n *ngIf="!serverConfig.allowEdits">
+        You cannot change the server configuration because it's managed externally.
+      </span>
+
+      <input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid || !hasConsistentOptions() || !serverConfig.allowEdits">
     </div>
   </div>
 </form>
index 5951d0aaa575ea9a5c3c9af5f1025e055ea867c1..0458d257fb89760f44871a3a25277093098e964f 100644 (file)
@@ -33,6 +33,11 @@ input[type=number] {
     top: 5px;
     right: 2.5rem;
   }
+
+  input[disabled] {
+    background-color: #f9f9f9;
+    pointer-events: none;
+  }
 }
 
 input[type=checkbox] {
@@ -93,6 +98,11 @@ textarea {
   }
 }
 
+input[disabled] {
+  opacity: 0.5;
+}
+
+
 .form-group-right {
   padding-top: 2px;
 }
index f13fe4bf943ad80f720889606ff9ede9c980d6e8..04b0175a732e8fb1811cd966b100cbfe86513685 100644 (file)
@@ -258,6 +258,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
 
     this.loadConfigAndUpdateForm()
     this.loadCategoriesAndLanguages()
+    if (!this.serverConfig.allowEdits) {
+      this.form.disable()
+    }
   }
 
   formValidated () {
index 6e70e2f37570b4411c2ea585122aea6490f92140..a460cb9b7bf98f7f12c1a1aca92dbcd8eec06709 100644 (file)
@@ -2,6 +2,7 @@
   <textarea #textarea
     [(ngModel)]="content" (ngModelChange)="onModelChange()"
     class="form-control" [ngClass]="classes"
+    [attr.disabled]="disabled"
     [ngStyle]="{ height: textareaHeight }"
     [id]="name" [name]="name">
   </textarea>
     </ng-container>
 
     <my-button
-      *ngIf="!isMaximized" [title]="maximizeInText" className="maximize-button" icon="fullscreen" (click)="onMaximizeClick()"
+      *ngIf="!isMaximized" [title]="maximizeInText" className="maximize-button" icon="fullscreen" (click)="onMaximizeClick()" [disabled]="disabled"
     ></my-button>
 
     <my-button
-      *ngIf="isMaximized" [title]="maximizeOutText" className="maximize-button" icon="exit-fullscreen" (click)="onMaximizeClick()"
+      *ngIf="isMaximized" [title]="maximizeOutText" className="maximize-button" icon="exit-fullscreen" (click)="onMaximizeClick()" [disabled]="disabled"
     ></my-button>
   </div>
 
index 80ca6690f834439b3d8182982c50b4ef5f6f0f2d..dcb5d20daca791d0fd1beef0dff22e0621edab92 100644 (file)
@@ -45,6 +45,7 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
   previewHTML: SafeHtml | string = ''
 
   isMaximized = false
+  disabled = false
 
   maximizeInText = $localize`Maximize editor`
   maximizeOutText = $localize`Exit maximized editor`
@@ -108,6 +109,10 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
     }
   }
 
+  setDisabledState (isDisabled: boolean) {
+    this.disabled = isDisabled
+  }
+
   private lockBodyScroll () {
     this.scrollPosition = this.viewportScroller.getScrollPosition()
     document.getElementById('content').classList.add('lock-scroll')
index 7b49a0c01299c97e5a150173d5088b2d5cf70e2c..03db2875b811de405ac5b2c6483c249e20ca4cde 100644 (file)
@@ -7,6 +7,7 @@
   [multiple]="true"
   [searchable]="true"
   [closeOnSelect]="false"
+  [disabled]="disabled"
 
   bindValue="id"
   bindLabel="label"
index 12f697628948cc1ddd9bd3bac811c57d362e9202..c9a5003248d2f751570ba0d7d35ba5537826b65b 100644 (file)
@@ -23,6 +23,8 @@ export class SelectCheckboxComponent implements OnInit, ControlValueAccessor {
   @Input() selectableGroupAsModel: boolean
   @Input() placeholder: string
 
+  disabled = false
+
   ngOnInit () {
     if (!this.placeholder) this.placeholder = $localize`Add a new option`
   }
@@ -59,6 +61,10 @@ export class SelectCheckboxComponent implements OnInit, ControlValueAccessor {
     this.propagateChange(this.selectedItems)
   }
 
+  setDisabledState (isDisabled: boolean) {
+    this.disabled = isDisabled
+  }
+
   compareFn (item: SelectOptionsItem, selected: ItemSelectCheckboxValue) {
     if (typeof selected === 'string' || typeof selected === 'number') {
       return item.id === selected
index 9dc8c2ec20663a18f1ed70ed3b2a6808c7b89804..69fdedc106f3e23b68b4a50297e82484bac68fae 100644 (file)
@@ -5,6 +5,7 @@
     [searchable]="searchable"
     [groupBy]="groupBy"
     [labelForId]="labelForId"
+    [disabled]="disabled"
 
     [(ngModel)]="selectedId"
     (ngModelChange)="onModelChange()"
index bc6b863c72a136dbfa8ed6452ca22aa1ca70d506..636bd61017750898cc0f0d8b0ccd58ea4944d6c3 100644 (file)
@@ -25,6 +25,7 @@ export class SelectCustomValueComponent implements ControlValueAccessor, OnChang
 
   customValue: number | string = ''
   selectedId: number | string
+  disabled = false
 
   itemsWithCustom: SelectOptionsItem[] = []
 
@@ -75,4 +76,8 @@ export class SelectCustomValueComponent implements ControlValueAccessor, OnChang
   isCustomValue () {
     return this.selectedId === 'other'
   }
+
+  setDisabledState (isDisabled: boolean) {
+    this.disabled = isDisabled
+  }
 }
index 3b17612559fd0e6b6d7896f7b415d5d058bfec45..83c7de9f5ca31f594eff0a16c488761dc46b72fe 100644 (file)
@@ -7,6 +7,7 @@
   [labelForId]="labelForId"
   [searchable]="searchable"
   [searchFn]="searchFn"
+  [disabled]="disabled"
 
   bindLabel="label"
   bindValue="id"
index 8482b9deab2ed81199ca459dedd3a00c7d88e703..820a82c24b0e203d701d200987a94493c3b3a37f 100644 (file)
@@ -23,6 +23,7 @@ export class SelectOptionsComponent implements ControlValueAccessor {
   @Input() searchFn: any
 
   selectedId: number | string
+  disabled = false
 
   propagateChange = (_: any) => { /* empty */ }
 
@@ -48,4 +49,8 @@ export class SelectOptionsComponent implements ControlValueAccessor {
   onModelChange () {
     this.propagateChange(this.selectedId)
   }
+
+  setDisabledState (isDisabled: boolean) {
+    this.disabled = isDisabled
+  }
 }
index 9f6d691314c52eb1c5b3dca75cd98eceeb29dd58..679c235a6ff7bd67dcce211b71f340292c0f1e77 100644 (file)
       cursor: default;
     }
   }
+  select[disabled] {
+    background-color: #f9f9f9;
+  }
 
   @media screen and (max-width: $width) {
     width: 100%;
index 3865ab5cf19720e5f8016bebbf36837964ce1f78..eb96b6bbb0ed478462540d59bb3d132a78b72c87 100644 (file)
@@ -243,6 +243,11 @@ peertube:
     # You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
     url: 'https://joinpeertube.org/api/v1/versions.json'
 
+webadmin:
+  configuration:
+    edit: 
+      allowed: true
+
 cache:
   previews:
     size: 500 # Max number of previews you want to cache
index 94238fad00f6bb70a968b28d45d2d5f2e1cc4059..082c75e53519f106e23aadb9afcd83b0ac0be2a5 100644 (file)
@@ -241,6 +241,11 @@ peertube:
     # You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
     url: 'https://joinpeertube.org/api/v1/versions.json'
 
+webadmin:
+  configuration:
+    # Set to false if you want the config to be readonly
+    allow_edits: true
+
 ###############################################################################
 #
 # From this point, all the following keys can be overridden by the web interface
index d542f62aa34a992520ae7dc627040b7c1f6e5ae7..5ea1f67c99103a9f8e24dddc53294f18c8ff05c8 100644 (file)
@@ -11,7 +11,7 @@ import { objectConverter } from '../../helpers/core-utils'
 import { CONFIG, reloadConfig } from '../../initializers/config'
 import { ClientHtml } from '../../lib/client-html'
 import { asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares'
-import { customConfigUpdateValidator } from '../../middlewares/validators/config'
+import { customConfigUpdateValidator, ensureConfigIsEditable } from '../../middlewares/validators/config'
 
 const configRouter = express.Router()
 
@@ -38,6 +38,7 @@ configRouter.put('/custom',
   openapiOperationDoc({ operationId: 'putCustomConfig' }),
   authenticate,
   ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
+  ensureConfigIsEditable,
   customConfigUpdateValidator,
   asyncMiddleware(updateCustomConfig)
 )
@@ -46,6 +47,7 @@ configRouter.delete('/custom',
   openapiOperationDoc({ operationId: 'delCustomConfig' }),
   authenticate,
   ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
+  ensureConfigIsEditable,
   asyncMiddleware(deleteCustomConfig)
 )
 
index be9fc61f083a43be735410f93eab0a01c33ef992..b2a8e9e199901c9d72382e6846fd2aa33c6a7f6e 100644 (file)
@@ -195,6 +195,13 @@ const CONFIG = {
       URL: config.get<string>('peertube.check_latest_version.url')
     }
   },
+  WEBADMIN: {
+    CONFIGURATION: {
+      EDITS: {
+        ALLOWED: config.get<boolean>('webadmin.configuration.edit.allowed')
+      }
+    }
+  },
   ADMIN: {
     get EMAIL () { return config.get<string>('admin.email') }
   },
@@ -411,14 +418,22 @@ export {
 // ---------------------------------------------------------------------------
 
 function getLocalConfigFilePath () {
-  const configSources = config.util.getConfigSources()
-  if (configSources.length === 0) throw new Error('Invalid config source.')
+  const localConfigDir = getLocalConfigDir()
 
   let filename = 'local'
   if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
   if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
 
-  return join(dirname(configSources[0].name), filename + '.json')
+  return join(localConfigDir, filename + '.json')
+}
+
+function getLocalConfigDir () {
+  if (process.env.PEERTUBE_LOCAL_CONFIG) return process.env.PEERTUBE_LOCAL_CONFIG
+
+  const configSources = config.util.getConfigSources()
+  if (configSources.length === 0) throw new Error('Invalid config source.')
+
+  return dirname(configSources[0].name)
 }
 
 function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] {
@@ -437,19 +452,19 @@ function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] {
 
 export function reloadConfig () {
 
-  function getConfigDirectory () {
+  function getConfigDirectories () {
     if (process.env.NODE_CONFIG_DIR) {
-      return process.env.NODE_CONFIG_DIR
+      return process.env.NODE_CONFIG_DIR.split(":")
     }
 
-    return join(root(), 'config')
+    return [ join(root(), 'config') ]
   }
 
   function purge () {
-    const directory = getConfigDirectory()
+    const directories = getConfigDirectories()
 
     for (const fileName in require.cache) {
-      if (fileName.includes(directory) === false) {
+      if (directories.some((dir) => fileName.includes(dir)) === false) {
         continue
       }
 
index 80d87a9d343af15eca9df627112fb0c72aa68c03..358f47133ca4974f91d28b72a44b102b947ab138 100644 (file)
@@ -42,6 +42,7 @@ class ServerConfigManager {
     const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
 
     return {
+      allowEdits: CONFIG.WEBADMIN.CONFIGURATION.EDITS.ALLOWED,
       instance: {
         name: CONFIG.INSTANCE.NAME,
         shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
index 16a840667dc6cfd6987bc85aaf735d9d61f30625..5f1ac89bc909021ff6302d7243c2bf5d5ed723ef 100644 (file)
@@ -1,13 +1,14 @@
 import express from 'express'
 import { body } from 'express-validator'
 import { isIntOrNull } from '@server/helpers/custom-validators/misc'
-import { isEmailEnabled } from '@server/initializers/config'
+import { CONFIG, isEmailEnabled } from '@server/initializers/config'
 import { CustomConfig } from '../../../shared/models/server/custom-config.model'
 import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
 import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users'
 import { logger } from '../../helpers/logger'
 import { isThemeRegistered } from '../../lib/plugins/theme-utils'
 import { areValidationErrors } from './shared'
+import { HttpStatusCode } from '@shared/models/http/http-error-codes'
 
 const customConfigUpdateValidator = [
   body('instance.name').exists().withMessage('Should have a valid instance name'),
@@ -104,10 +105,21 @@ const customConfigUpdateValidator = [
   }
 ]
 
+function ensureConfigIsEditable (req: express.Request, res: express.Response, next: express.NextFunction) {
+  if (!CONFIG.WEBADMIN.CONFIGURATION.EDITS.ALLOWED) {
+    return res.fail({
+      status: HttpStatusCode.METHOD_NOT_ALLOWED_405,
+      message: 'Server configuration is static and cannot be edited'
+    })
+  }
+  return next()
+}
+
 // ---------------------------------------------------------------------------
 
 export {
-  customConfigUpdateValidator
+  customConfigUpdateValidator,
+  ensureConfigIsEditable
 }
 
 function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: express.Response) {
index c4dd882b890b4b4dd527f78c18988571cfe0ad64..e057ec1a2bb4c99a1f4ab1ed5268b70a41bd0adf 100644 (file)
@@ -201,6 +201,199 @@ function checkUpdatedConfig (data: CustomConfig) {
   expect(data.broadcastMessage.dismissable).to.be.true
 }
 
+const newCustomConfig: CustomConfig = {
+  instance: {
+    name: 'PeerTube updated',
+    shortDescription: 'my short description',
+    description: 'my super description',
+    terms: 'my super terms',
+    codeOfConduct: 'my super coc',
+
+    creationReason: 'my super creation reason',
+    moderationInformation: 'my super moderation information',
+    administrator: 'Kuja',
+    maintenanceLifetime: 'forever',
+    businessModel: 'my super business model',
+    hardwareInformation: '2vCore 3GB RAM',
+
+    languages: [ 'en', 'es' ],
+    categories: [ 1, 2 ],
+
+    isNSFW: true,
+    defaultNSFWPolicy: 'blur' as 'blur',
+
+    defaultClientRoute: '/videos/recently-added',
+
+    customizations: {
+      javascript: 'alert("coucou")',
+      css: 'body { background-color: red; }'
+    }
+  },
+  theme: {
+    default: 'default'
+  },
+  services: {
+    twitter: {
+      username: '@Kuja',
+      whitelisted: true
+    }
+  },
+  cache: {
+    previews: {
+      size: 2
+    },
+    captions: {
+      size: 3
+    },
+    torrents: {
+      size: 4
+    }
+  },
+  signup: {
+    enabled: false,
+    limit: 5,
+    requiresEmailVerification: false,
+    minimumAge: 10
+  },
+  admin: {
+    email: 'superadmin1@example.com'
+  },
+  contactForm: {
+    enabled: false
+  },
+  user: {
+    videoQuota: 5242881,
+    videoQuotaDaily: 318742
+  },
+  transcoding: {
+    enabled: true,
+    allowAdditionalExtensions: true,
+    allowAudioFiles: true,
+    threads: 1,
+    concurrency: 3,
+    profile: 'vod_profile',
+    resolutions: {
+      '0p': false,
+      '240p': false,
+      '360p': true,
+      '480p': true,
+      '720p': false,
+      '1080p': false,
+      '1440p': false,
+      '2160p': false
+    },
+    webtorrent: {
+      enabled: true
+    },
+    hls: {
+      enabled: false
+    }
+  },
+  live: {
+    enabled: true,
+    allowReplay: true,
+    maxDuration: 5000,
+    maxInstanceLives: -1,
+    maxUserLives: 10,
+    transcoding: {
+      enabled: true,
+      threads: 4,
+      profile: 'live_profile',
+      resolutions: {
+        '240p': true,
+        '360p': true,
+        '480p': true,
+        '720p': true,
+        '1080p': true,
+        '1440p': true,
+        '2160p': true
+      }
+    }
+  },
+  import: {
+    videos: {
+      concurrency: 4,
+      http: {
+        enabled: false
+      },
+      torrent: {
+        enabled: false
+      }
+    }
+  },
+  trending: {
+    videos: {
+      algorithms: {
+        enabled: [ 'best', 'hot', 'most-viewed', 'most-liked' ],
+        default: 'hot'
+      }
+    }
+  },
+  autoBlacklist: {
+    videos: {
+      ofUsers: {
+        enabled: true
+      }
+    }
+  },
+  followers: {
+    instance: {
+      enabled: false,
+      manualApproval: true
+    }
+  },
+  followings: {
+    instance: {
+      autoFollowBack: {
+        enabled: true
+      },
+      autoFollowIndex: {
+        enabled: true,
+        indexUrl: 'https://updated.example.com'
+      }
+    }
+  },
+  broadcastMessage: {
+    enabled: true,
+    level: 'error',
+    message: 'super bad message',
+    dismissable: true
+  },
+  search: {
+    remoteUri: {
+      anonymous: true,
+      users: true
+    },
+    searchIndex: {
+      enabled: true,
+      url: 'https://search.joinpeertube.org',
+      disableLocalSearch: true,
+      isDefaultSearch: true
+    }
+  }
+}
+
+describe('Test static config', function () {
+  let server: PeerTubeServer = null
+
+  before(async function () {
+    this.timeout(30000)
+
+    server = await createSingleServer(1, { webadmin: { configuration: { edit: { allowed: false } } } })
+    await setAccessTokensToServers([ server ])
+  })
+
+  it('Should tell the client that edits are not allowed', async function () {
+    const data = await server.config.getConfig()
+
+    expect(data.allowEdits).to.be.false
+  })
+
+  it('Should error when client tries to update', async function () {
+    await server.config.updateCustomConfig({ newCustomConfig, expectedStatus: 405 })
+  })
+})
+
 describe('Test config', function () {
   let server: PeerTubeServer = null
 
@@ -252,177 +445,6 @@ describe('Test config', function () {
   })
 
   it('Should update the customized configuration', async function () {
-    const newCustomConfig: CustomConfig = {
-      instance: {
-        name: 'PeerTube updated',
-        shortDescription: 'my short description',
-        description: 'my super description',
-        terms: 'my super terms',
-        codeOfConduct: 'my super coc',
-
-        creationReason: 'my super creation reason',
-        moderationInformation: 'my super moderation information',
-        administrator: 'Kuja',
-        maintenanceLifetime: 'forever',
-        businessModel: 'my super business model',
-        hardwareInformation: '2vCore 3GB RAM',
-
-        languages: [ 'en', 'es' ],
-        categories: [ 1, 2 ],
-
-        isNSFW: true,
-        defaultNSFWPolicy: 'blur' as 'blur',
-
-        defaultClientRoute: '/videos/recently-added',
-
-        customizations: {
-          javascript: 'alert("coucou")',
-          css: 'body { background-color: red; }'
-        }
-      },
-      theme: {
-        default: 'default'
-      },
-      services: {
-        twitter: {
-          username: '@Kuja',
-          whitelisted: true
-        }
-      },
-      cache: {
-        previews: {
-          size: 2
-        },
-        captions: {
-          size: 3
-        },
-        torrents: {
-          size: 4
-        }
-      },
-      signup: {
-        enabled: false,
-        limit: 5,
-        requiresEmailVerification: false,
-        minimumAge: 10
-      },
-      admin: {
-        email: 'superadmin1@example.com'
-      },
-      contactForm: {
-        enabled: false
-      },
-      user: {
-        videoQuota: 5242881,
-        videoQuotaDaily: 318742
-      },
-      transcoding: {
-        enabled: true,
-        allowAdditionalExtensions: true,
-        allowAudioFiles: true,
-        threads: 1,
-        concurrency: 3,
-        profile: 'vod_profile',
-        resolutions: {
-          '0p': false,
-          '240p': false,
-          '360p': true,
-          '480p': true,
-          '720p': false,
-          '1080p': false,
-          '1440p': false,
-          '2160p': false
-        },
-        webtorrent: {
-          enabled: true
-        },
-        hls: {
-          enabled: false
-        }
-      },
-      live: {
-        enabled: true,
-        allowReplay: true,
-        maxDuration: 5000,
-        maxInstanceLives: -1,
-        maxUserLives: 10,
-        transcoding: {
-          enabled: true,
-          threads: 4,
-          profile: 'live_profile',
-          resolutions: {
-            '240p': true,
-            '360p': true,
-            '480p': true,
-            '720p': true,
-            '1080p': true,
-            '1440p': true,
-            '2160p': true
-          }
-        }
-      },
-      import: {
-        videos: {
-          concurrency: 4,
-          http: {
-            enabled: false
-          },
-          torrent: {
-            enabled: false
-          }
-        }
-      },
-      trending: {
-        videos: {
-          algorithms: {
-            enabled: [ 'best', 'hot', 'most-viewed', 'most-liked' ],
-            default: 'hot'
-          }
-        }
-      },
-      autoBlacklist: {
-        videos: {
-          ofUsers: {
-            enabled: true
-          }
-        }
-      },
-      followers: {
-        instance: {
-          enabled: false,
-          manualApproval: true
-        }
-      },
-      followings: {
-        instance: {
-          autoFollowBack: {
-            enabled: true
-          },
-          autoFollowIndex: {
-            enabled: true,
-            indexUrl: 'https://updated.example.com'
-          }
-        }
-      },
-      broadcastMessage: {
-        enabled: true,
-        level: 'error',
-        message: 'super bad message',
-        dismissable: true
-      },
-      search: {
-        remoteUri: {
-          anonymous: true,
-          users: true
-        },
-        searchIndex: {
-          enabled: true,
-          url: 'https://search.joinpeertube.org',
-          disableLocalSearch: true,
-          isDefaultSearch: true
-        }
-      }
-    }
     await server.config.updateCustomConfig({ newCustomConfig })
 
     const data = await server.config.getCustomConfig()
index 585e99acaca85648e48ef3c59e8ec78db6e1a74c..3b026e3a53e2586cc935352430b8459fcd58f182 100644 (file)
@@ -30,6 +30,7 @@ export interface RegisteredIdAndPassAuthConfig {
 }
 
 export interface ServerConfig {
+  allowEdits: boolean
   serverVersion: string
   serverCommit?: string
 
index 2ff0591f9666ee85e8d81eaefadd0993893e3814..163c514f5897de93175963530e611a3b1de6cd46 100644 (file)
@@ -33,7 +33,8 @@ RUN mkdir /data /config
 RUN chown -R peertube:peertube /data /config
 
 ENV NODE_ENV production
-ENV NODE_CONFIG_DIR /config
+ENV NODE_CONFIG_DIR /app/config:/app/support/docker/production/config:/config
+ENV PEERTUBE_LOCAL_CONFIG /config
 
 VOLUME /data
 VOLUME /config
index 1b474582a94091ecf65c6cdaf0b72338006dc1f1..7c430a9955c394fab88947624148c155b14a1864 100644 (file)
@@ -68,6 +68,13 @@ object_storage:
     prefix: "PEERTUBE_OBJECT_STORAGE_VIDEOS_PREFIX"
     base_url: "PEERTUBE_OBJECT_STORAGE_VIDEOS_BASE_URL"
 
+webadmin:
+  configuration:
+    edit:
+      allowed: 
+        __name: "PEERTUBE_ALLOW_WEBADMIN_CONFIG"
+        __format: "json"
+
 log:
   level: "PEERTUBE_LOG_LEVEL"
   log_ping_requests:
index 7dd626b9f02ae06d74cdf474ba90afedc1542d0a..261055e844de8cabeed2bc0219f36eef6151f95a 100755 (executable)
@@ -1,15 +1,8 @@
 #!/bin/sh
 set -e
 
-# Populate config directory
-if [ -z "$(ls -A /config)" ]; then
-    cp /app/support/docker/production/config/* /config
-fi
 
-# Always copy default and custom env configuration file, in cases where new keys were added
-cp /app/config/default.yaml /config
-cp /app/support/docker/production/config/custom-environment-variables.yaml /config
-find /config ! -user peertube -exec chown peertube:peertube {} \;
+find /config ! -user peertube -exec chown peertube:peertube {} \; || true
 
 # first arg is `-f` or `--some-option`
 # or first arg is `something.conf`