mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Update:Add server setting for backupPath and allow overriding with BACKUP_PATH env variable #2973
This commit is contained in:
		
							parent
							
								
									a75ad5d659
								
							
						
					
					
						commit
						7bc70effb0
					
				
							
								
								
									
										1
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								index.js
									
									
									
									
									
								
							@ -10,6 +10,7 @@ if (isDev) {
 | 
			
		||||
  if (devEnv.FFmpegPath) process.env.FFMPEG_PATH = devEnv.FFmpegPath
 | 
			
		||||
  if (devEnv.FFProbePath) process.env.FFPROBE_PATH = devEnv.FFProbePath
 | 
			
		||||
  if (devEnv.SkipBinariesCheck) process.env.SKIP_BINARIES_CHECK = '1'
 | 
			
		||||
  if (devEnv.BackupPath) process.env.BACKUP_PATH = devEnv.BackupPath
 | 
			
		||||
  process.env.SOURCE = 'local'
 | 
			
		||||
  process.env.ROUTER_BASE_PATH = devEnv.RouterBasePath || ''
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,12 +2,12 @@ const Logger = require('../Logger')
 | 
			
		||||
const { encodeUriPath } = require('../utils/fileUtils')
 | 
			
		||||
 | 
			
		||||
class BackupController {
 | 
			
		||||
  constructor() { }
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  getAll(req, res) {
 | 
			
		||||
    res.json({
 | 
			
		||||
      backups: this.backupManager.backups.map(b => b.toJSON()),
 | 
			
		||||
      backupLocation: this.backupManager.backupLocation
 | 
			
		||||
      backups: this.backupManager.backups.map((b) => b.toJSON()),
 | 
			
		||||
      backupLocation: this.backupManager.backupPath
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ class BackupController {
 | 
			
		||||
    await this.backupManager.removeBackup(req.backup)
 | 
			
		||||
 | 
			
		||||
    res.json({
 | 
			
		||||
      backups: this.backupManager.backups.map(b => b.toJSON())
 | 
			
		||||
      backups: this.backupManager.backups.map((b) => b.toJSON())
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -33,9 +33,9 @@ class BackupController {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * api/backups/:id/download
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {*} req 
 | 
			
		||||
   * @param {*} res 
 | 
			
		||||
   *
 | 
			
		||||
   * @param {*} req
 | 
			
		||||
   * @param {*} res
 | 
			
		||||
   */
 | 
			
		||||
  download(req, res) {
 | 
			
		||||
    if (global.XAccel) {
 | 
			
		||||
@ -50,9 +50,9 @@ class BackupController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {import('express').Request} req 
 | 
			
		||||
   * @param {import('express').Response} res 
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  apply(req, res) {
 | 
			
		||||
    this.backupManager.requestApplyBackup(this.apiCacheManager, req.backup, res)
 | 
			
		||||
@ -65,7 +65,7 @@ class BackupController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (req.params.id) {
 | 
			
		||||
      req.backup = this.backupManager.backups.find(b => b.id === req.params.id)
 | 
			
		||||
      req.backup = this.backupManager.backups.find((b) => b.id === req.params.id)
 | 
			
		||||
      if (!req.backup) {
 | 
			
		||||
        return res.sendStatus(404)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,6 @@ const Backup = require('../objects/Backup')
 | 
			
		||||
 | 
			
		||||
class BackupManager {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.BackupPath = Path.join(global.MetadataPath, 'backups')
 | 
			
		||||
    this.ItemsMetadataPath = Path.join(global.MetadataPath, 'items')
 | 
			
		||||
    this.AuthorsMetadataPath = Path.join(global.MetadataPath, 'authors')
 | 
			
		||||
 | 
			
		||||
@ -26,8 +25,8 @@ class BackupManager {
 | 
			
		||||
    this.backups = []
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get backupLocation() {
 | 
			
		||||
    return this.BackupPath
 | 
			
		||||
  get backupPath() {
 | 
			
		||||
    return global.ServerSettings.backupPath
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get backupSchedule() {
 | 
			
		||||
@ -43,9 +42,9 @@ class BackupManager {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async init() {
 | 
			
		||||
    const backupsDirExists = await fs.pathExists(this.BackupPath)
 | 
			
		||||
    const backupsDirExists = await fs.pathExists(this.backupPath)
 | 
			
		||||
    if (!backupsDirExists) {
 | 
			
		||||
      await fs.ensureDir(this.BackupPath)
 | 
			
		||||
      await fs.ensureDir(this.backupPath)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await this.loadBackups()
 | 
			
		||||
@ -87,11 +86,14 @@ class BackupManager {
 | 
			
		||||
      return res.status(500).send('Invalid backup file')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const tempPath = Path.join(this.BackupPath, fileUtils.sanitizeFilename(backupFile.name))
 | 
			
		||||
    const success = await backupFile.mv(tempPath).then(() => true).catch((error) => {
 | 
			
		||||
      Logger.error('[BackupManager] Failed to move backup file', path, error)
 | 
			
		||||
      return false
 | 
			
		||||
    })
 | 
			
		||||
    const tempPath = Path.join(this.backupPath, fileUtils.sanitizeFilename(backupFile.name))
 | 
			
		||||
    const success = await backupFile
 | 
			
		||||
      .mv(tempPath)
 | 
			
		||||
      .then(() => true)
 | 
			
		||||
      .catch((error) => {
 | 
			
		||||
        Logger.error('[BackupManager] Failed to move backup file', path, error)
 | 
			
		||||
        return false
 | 
			
		||||
      })
 | 
			
		||||
    if (!success) {
 | 
			
		||||
      return res.status(500).send('Failed to move backup file into backups directory')
 | 
			
		||||
    }
 | 
			
		||||
@ -122,7 +124,7 @@ class BackupManager {
 | 
			
		||||
 | 
			
		||||
    backup.fileSize = await getFileSize(backup.fullPath)
 | 
			
		||||
 | 
			
		||||
    const existingBackupIndex = this.backups.findIndex(b => b.id === backup.id)
 | 
			
		||||
    const existingBackupIndex = this.backups.findIndex((b) => b.id === backup.id)
 | 
			
		||||
    if (existingBackupIndex >= 0) {
 | 
			
		||||
      Logger.warn(`[BackupManager] Backup already exists with id ${backup.id} - overwriting`)
 | 
			
		||||
      this.backups.splice(existingBackupIndex, 1, backup)
 | 
			
		||||
@ -131,7 +133,7 @@ class BackupManager {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.json({
 | 
			
		||||
      backups: this.backups.map(b => b.toJSON())
 | 
			
		||||
      backups: this.backups.map((b) => b.toJSON())
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -139,7 +141,7 @@ class BackupManager {
 | 
			
		||||
    var backupSuccess = await this.runBackup()
 | 
			
		||||
    if (backupSuccess) {
 | 
			
		||||
      res.json({
 | 
			
		||||
        backups: this.backups.map(b => b.toJSON())
 | 
			
		||||
        backups: this.backups.map((b) => b.toJSON())
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      res.sendStatus(500)
 | 
			
		||||
@ -147,10 +149,10 @@ class BackupManager {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {import('./ApiCacheManager')} apiCacheManager 
 | 
			
		||||
   * @param {Backup} backup 
 | 
			
		||||
   * @param {import('express').Response} res 
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('./ApiCacheManager')} apiCacheManager
 | 
			
		||||
   * @param {Backup} backup
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async requestApplyBackup(apiCacheManager, backup, res) {
 | 
			
		||||
    Logger.info(`[BackupManager] Applying backup at "${backup.fullPath}"`)
 | 
			
		||||
@ -176,7 +178,7 @@ class BackupManager {
 | 
			
		||||
    Logger.info(`[BackupManager] Extracted backup sqlite db to temp path ${tempDbPath}`)
 | 
			
		||||
 | 
			
		||||
    // Verify extract - Abandon backup if sqlite file did not extract
 | 
			
		||||
    if (!await fs.pathExists(tempDbPath)) {
 | 
			
		||||
    if (!(await fs.pathExists(tempDbPath))) {
 | 
			
		||||
      Logger.error(`[BackupManager] Sqlite file not found after extract - abandon backup apply and reconnect db`)
 | 
			
		||||
      await zip.close()
 | 
			
		||||
      await Database.reconnect()
 | 
			
		||||
@ -218,12 +220,12 @@ class BackupManager {
 | 
			
		||||
 | 
			
		||||
  async loadBackups() {
 | 
			
		||||
    try {
 | 
			
		||||
      const filesInDir = await fs.readdir(this.BackupPath)
 | 
			
		||||
      const filesInDir = await fs.readdir(this.backupPath)
 | 
			
		||||
 | 
			
		||||
      for (let i = 0; i < filesInDir.length; i++) {
 | 
			
		||||
        const filename = filesInDir[i]
 | 
			
		||||
        if (filename.endsWith('.audiobookshelf')) {
 | 
			
		||||
          const fullFilePath = Path.join(this.BackupPath, filename)
 | 
			
		||||
          const fullFilePath = Path.join(this.backupPath, filename)
 | 
			
		||||
 | 
			
		||||
          let zip = null
 | 
			
		||||
          let data = null
 | 
			
		||||
@ -239,14 +241,16 @@ class BackupManager {
 | 
			
		||||
 | 
			
		||||
          const backup = new Backup({ details, fullPath: fullFilePath })
 | 
			
		||||
 | 
			
		||||
          if (!backup.serverVersion) { // Backups before v2
 | 
			
		||||
          if (!backup.serverVersion) {
 | 
			
		||||
            // Backups before v2
 | 
			
		||||
            Logger.error(`[BackupManager] Old unsupported backup was found "${backup.filename}"`)
 | 
			
		||||
          } else if (!backup.key) { // Backups before sqlite migration
 | 
			
		||||
          } else if (!backup.key) {
 | 
			
		||||
            // Backups before sqlite migration
 | 
			
		||||
            Logger.warn(`[BackupManager] Old unsupported backup was found "${backup.filename}" (pre sqlite migration)`)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          backup.fileSize = await getFileSize(backup.fullPath)
 | 
			
		||||
          const existingBackupWithId = this.backups.find(b => b.id === backup.id)
 | 
			
		||||
          const existingBackupWithId = this.backups.find((b) => b.id === backup.id)
 | 
			
		||||
          if (existingBackupWithId) {
 | 
			
		||||
            Logger.warn(`[BackupManager] Backup already loaded with id ${backup.id} - ignoring`)
 | 
			
		||||
          } else {
 | 
			
		||||
@ -267,7 +271,7 @@ class BackupManager {
 | 
			
		||||
    // Check if Metadata Path is inside Config Path (otherwise there will be an infinite loop as the archiver tries to zip itself)
 | 
			
		||||
    Logger.info(`[BackupManager] Running Backup`)
 | 
			
		||||
    const newBackup = new Backup()
 | 
			
		||||
    newBackup.setData(this.BackupPath)
 | 
			
		||||
    newBackup.setData(this.backupPath)
 | 
			
		||||
 | 
			
		||||
    await fs.ensureDir(this.AuthorsMetadataPath)
 | 
			
		||||
 | 
			
		||||
@ -296,7 +300,7 @@ class BackupManager {
 | 
			
		||||
 | 
			
		||||
    newBackup.fileSize = await getFileSize(newBackup.fullPath)
 | 
			
		||||
 | 
			
		||||
    const existingIndex = this.backups.findIndex(b => b.id === newBackup.id)
 | 
			
		||||
    const existingIndex = this.backups.findIndex((b) => b.id === newBackup.id)
 | 
			
		||||
    if (existingIndex >= 0) {
 | 
			
		||||
      this.backups.splice(existingIndex, 1, newBackup)
 | 
			
		||||
    } else {
 | 
			
		||||
@ -318,7 +322,7 @@ class BackupManager {
 | 
			
		||||
    try {
 | 
			
		||||
      Logger.debug(`[BackupManager] Removing Backup "${backup.fullPath}"`)
 | 
			
		||||
      await fs.remove(backup.fullPath)
 | 
			
		||||
      this.backups = this.backups.filter(b => b.id !== backup.id)
 | 
			
		||||
      this.backups = this.backups.filter((b) => b.id !== backup.id)
 | 
			
		||||
      Logger.info(`[BackupManager] Backup "${backup.id}" Removed`)
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      Logger.error(`[BackupManager] Failed to remove backup`, error)
 | 
			
		||||
@ -425,4 +429,4 @@ class BackupManager {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
module.exports = BackupManager
 | 
			
		||||
module.exports = BackupManager
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
const Path = require('path')
 | 
			
		||||
const packageJson = require('../../../package.json')
 | 
			
		||||
const { BookshelfView } = require('../../utils/constants')
 | 
			
		||||
const Logger = require('../../Logger')
 | 
			
		||||
@ -25,6 +26,7 @@ class ServerSettings {
 | 
			
		||||
    this.rateLimitLoginWindow = 10 * 60 * 1000 // 10 Minutes
 | 
			
		||||
 | 
			
		||||
    // Backups
 | 
			
		||||
    this.backupPath = Path.join(global.MetadataPath, 'backups')
 | 
			
		||||
    this.backupSchedule = false // If false then auto-backups are disabled
 | 
			
		||||
    this.backupsToKeep = 2
 | 
			
		||||
    this.maxBackupSize = 1
 | 
			
		||||
@ -97,6 +99,7 @@ class ServerSettings {
 | 
			
		||||
    this.rateLimitLoginRequests = !isNaN(settings.rateLimitLoginRequests) ? Number(settings.rateLimitLoginRequests) : 10
 | 
			
		||||
    this.rateLimitLoginWindow = !isNaN(settings.rateLimitLoginWindow) ? Number(settings.rateLimitLoginWindow) : 10 * 60 * 1000 // 10 Minutes
 | 
			
		||||
 | 
			
		||||
    this.backupPath = settings.backupPath || Path.join(global.MetadataPath, 'backups')
 | 
			
		||||
    this.backupSchedule = settings.backupSchedule || false
 | 
			
		||||
    this.backupsToKeep = settings.backupsToKeep || 2
 | 
			
		||||
    this.maxBackupSize = settings.maxBackupSize || 1
 | 
			
		||||
@ -147,22 +150,26 @@ class ServerSettings {
 | 
			
		||||
      this.authActiveAuthMethods.splice(this.authActiveAuthMethods.indexOf('openid', 0), 1)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // fallback to local    
 | 
			
		||||
    // fallback to local
 | 
			
		||||
    if (!Array.isArray(this.authActiveAuthMethods) || this.authActiveAuthMethods.length == 0) {
 | 
			
		||||
      this.authActiveAuthMethods = ['local']
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Migrations
 | 
			
		||||
    if (settings.storeCoverWithBook != undefined) { // storeCoverWithBook was renamed to storeCoverWithItem in 2.0.0
 | 
			
		||||
    if (settings.storeCoverWithBook != undefined) {
 | 
			
		||||
      // storeCoverWithBook was renamed to storeCoverWithItem in 2.0.0
 | 
			
		||||
      this.storeCoverWithItem = !!settings.storeCoverWithBook
 | 
			
		||||
    }
 | 
			
		||||
    if (settings.storeMetadataWithBook != undefined) { // storeMetadataWithBook was renamed to storeMetadataWithItem in 2.0.0
 | 
			
		||||
    if (settings.storeMetadataWithBook != undefined) {
 | 
			
		||||
      // storeMetadataWithBook was renamed to storeMetadataWithItem in 2.0.0
 | 
			
		||||
      this.storeMetadataWithItem = !!settings.storeMetadataWithBook
 | 
			
		||||
    }
 | 
			
		||||
    if (settings.homeBookshelfView == undefined) { // homeBookshelfView was added in 2.1.3
 | 
			
		||||
    if (settings.homeBookshelfView == undefined) {
 | 
			
		||||
      // homeBookshelfView was added in 2.1.3
 | 
			
		||||
      this.homeBookshelfView = settings.bookshelfView
 | 
			
		||||
    }
 | 
			
		||||
    if (settings.metadataFileFormat == undefined) { // metadataFileFormat was added in 2.2.21
 | 
			
		||||
    if (settings.metadataFileFormat == undefined) {
 | 
			
		||||
      // metadataFileFormat was added in 2.2.21
 | 
			
		||||
      // All users using old settings will stay abs until changed
 | 
			
		||||
      this.metadataFileFormat = 'abs'
 | 
			
		||||
    }
 | 
			
		||||
@ -176,9 +183,15 @@ class ServerSettings {
 | 
			
		||||
    if (this.logLevel !== Logger.logLevel) {
 | 
			
		||||
      Logger.setLogLevel(this.logLevel)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (process.env.BACKUP_PATH && this.backupPath !== process.env.BACKUP_PATH) {
 | 
			
		||||
      Logger.info(`[ServerSettings] Using backup path from environment variable ${process.env.BACKUP_PATH}`)
 | 
			
		||||
      this.backupPath = process.env.BACKUP_PATH
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toJSON() { // Use toJSONForBrowser if sending to client
 | 
			
		||||
  toJSON() {
 | 
			
		||||
    // Use toJSONForBrowser if sending to client
 | 
			
		||||
    return {
 | 
			
		||||
      id: this.id,
 | 
			
		||||
      tokenSecret: this.tokenSecret, // Do not return to client
 | 
			
		||||
@ -192,6 +205,7 @@ class ServerSettings {
 | 
			
		||||
      metadataFileFormat: this.metadataFileFormat,
 | 
			
		||||
      rateLimitLoginRequests: this.rateLimitLoginRequests,
 | 
			
		||||
      rateLimitLoginWindow: this.rateLimitLoginWindow,
 | 
			
		||||
      backupPath: this.backupPath,
 | 
			
		||||
      backupSchedule: this.backupSchedule,
 | 
			
		||||
      backupsToKeep: this.backupsToKeep,
 | 
			
		||||
      maxBackupSize: this.maxBackupSize,
 | 
			
		||||
@ -249,14 +263,7 @@ class ServerSettings {
 | 
			
		||||
   * Auth settings required for openid to be valid
 | 
			
		||||
   */
 | 
			
		||||
  get isOpenIDAuthSettingsValid() {
 | 
			
		||||
    return this.authOpenIDIssuerURL &&
 | 
			
		||||
      this.authOpenIDAuthorizationURL &&
 | 
			
		||||
      this.authOpenIDTokenURL &&
 | 
			
		||||
      this.authOpenIDUserInfoURL &&
 | 
			
		||||
      this.authOpenIDJwksURL &&
 | 
			
		||||
      this.authOpenIDClientID &&
 | 
			
		||||
      this.authOpenIDClientSecret &&
 | 
			
		||||
      this.authOpenIDTokenSigningAlgorithm
 | 
			
		||||
    return this.authOpenIDIssuerURL && this.authOpenIDAuthorizationURL && this.authOpenIDTokenURL && this.authOpenIDUserInfoURL && this.authOpenIDJwksURL && this.authOpenIDClientID && this.authOpenIDClientSecret && this.authOpenIDTokenSigningAlgorithm
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get authenticationSettings() {
 | 
			
		||||
@ -297,8 +304,8 @@ class ServerSettings {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update server settings
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {Object} payload 
 | 
			
		||||
   *
 | 
			
		||||
   * @param {Object} payload
 | 
			
		||||
   * @returns {boolean} true if updates were made
 | 
			
		||||
   */
 | 
			
		||||
  update(payload) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user