diff --git a/index.js b/index.js index a8686b31..141c5826 100644 --- a/index.js +++ b/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 || '' } diff --git a/server/controllers/BackupController.js b/server/controllers/BackupController.js index bd6caa0b..08ecd6cd 100644 --- a/server/controllers/BackupController.js +++ b/server/controllers/BackupController.js @@ -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) } diff --git a/server/managers/BackupManager.js b/server/managers/BackupManager.js index 40a74f14..97ede72c 100644 --- a/server/managers/BackupManager.js +++ b/server/managers/BackupManager.js @@ -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 \ No newline at end of file +module.exports = BackupManager diff --git a/server/objects/settings/ServerSettings.js b/server/objects/settings/ServerSettings.js index f107d707..6ade11a9 100644 --- a/server/objects/settings/ServerSettings.js +++ b/server/objects/settings/ServerSettings.js @@ -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) {