diff --git a/backups/!README.md b/backups/!README.md new file mode 100644 index 000000000..8c9a74cf2 --- /dev/null +++ b/backups/!README.md @@ -0,0 +1,9 @@ +# Looking for setting snapshots or chat backups? + +Individual user backups are now located in the data directory. + +Example for the default user under default data root: + +/data/default-user/backups + +This folder remains for historical purposes only. diff --git a/src/constants.js b/src/constants.js index 7ca267f74..b4945fa6f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -41,6 +41,7 @@ const USER_DIRECTORY_TEMPLATE = Object.freeze({ comfyWorkflows: 'user/workflows', files: 'user/files', vectors: 'vectors', + backups: 'backups', }); /** diff --git a/src/endpoints/chats.js b/src/endpoints/chats.js index ff55d3ff0..d88b27fa3 100644 --- a/src/endpoints/chats.js +++ b/src/endpoints/chats.js @@ -6,15 +6,16 @@ const sanitize = require('sanitize-filename'); const writeFileAtomicSync = require('write-file-atomic').sync; const { jsonParser, urlencodedParser } = require('../express-common'); -const { PUBLIC_DIRECTORIES, UPLOADS_PATH } = require('../constants'); +const { UPLOADS_PATH } = require('../constants'); const { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp, removeOldBackups } = require('../util'); /** * Saves a chat to the backups directory. + * @param {string} directory The user's backups directory. * @param {string} name The name of the chat. * @param {string} chat The serialized chat to save. */ -function backupChat(name, chat) { +function backupChat(directory, name, chat) { try { const isBackupDisabled = getConfigValue('disableChatBackup', false); @@ -22,17 +23,13 @@ function backupChat(name, chat) { return; } - if (!fs.existsSync(PUBLIC_DIRECTORIES.backups)) { - fs.mkdirSync(PUBLIC_DIRECTORIES.backups); - } - // replace non-alphanumeric characters with underscores name = sanitize(name).replace(/[^a-z0-9]/gi, '_').toLowerCase(); - const backupFile = path.join(PUBLIC_DIRECTORIES.backups, `chat_${name}_${generateTimestamp()}.jsonl`); + const backupFile = path.join(directory, `chat_${name}_${generateTimestamp()}.jsonl`); writeFileAtomicSync(backupFile, chat, 'utf-8'); - removeOldBackups(`chat_${name}_`); + removeOldBackups(directory, `chat_${name}_`); } catch (err) { console.log(`Could not backup chat for ${name}`, err); } @@ -151,7 +148,7 @@ router.post('/save', jsonParser, function (request, response) { const fileName = `${sanitize(String(request.body.file_name))}.jsonl`; const filePath = path.join(request.user.directories.chats, directoryName, fileName); writeFileAtomicSync(filePath, jsonlData, 'utf8'); - backupChat(directoryName, jsonlData); + backupChat(request.user.directories.backups, directoryName, jsonlData); return response.send({ result: 'ok' }); } catch (error) { response.send(error); @@ -455,7 +452,7 @@ router.post('/group/save', jsonParser, (request, response) => { let chat_data = request.body.chat; let jsonlData = chat_data.map(JSON.stringify).join('\n'); writeFileAtomicSync(pathToFile, jsonlData, 'utf8'); - backupChat(String(id), jsonlData); + backupChat(request.user.directories.backups, String(id), jsonlData); return response.send({ ok: true }); }); diff --git a/src/endpoints/settings.js b/src/endpoints/settings.js index a907fab5b..367ab6553 100644 --- a/src/endpoints/settings.js +++ b/src/endpoints/settings.js @@ -110,10 +110,6 @@ function readPresetsFromDirectory(directoryPath, options = {}) { async function backupSettings() { try { - if (!fs.existsSync(PUBLIC_DIRECTORIES.backups)) { - fs.mkdirSync(PUBLIC_DIRECTORIES.backups); - } - const userHandles = await getAllUserHandles(); for (const handle of userHandles) { @@ -131,7 +127,7 @@ async function backupSettings() { */ function backupUserSettings(handle) { const userDirectories = getUserDirectories(handle); - const backupFile = path.join(PUBLIC_DIRECTORIES.backups, `${getFilePrefix(handle)}${generateTimestamp()}.json`); + const backupFile = path.join(userDirectories.backups, `${getFilePrefix(handle)}${generateTimestamp()}.json`); const sourceFile = path.join(userDirectories.root, SETTINGS_FILE); if (!fs.existsSync(sourceFile)) { @@ -139,7 +135,7 @@ function backupUserSettings(handle) { } fs.copyFileSync(sourceFile, backupFile); - removeOldBackups(`settings_${handle}`); + removeOldBackups(userDirectories.backups, `settings_${handle}`); } const router = express.Router(); @@ -227,12 +223,12 @@ router.post('/get', jsonParser, (request, response) => { router.post('/get-snapshots', jsonParser, async (request, response) => { try { - const snapshots = fs.readdirSync(PUBLIC_DIRECTORIES.backups); + const snapshots = fs.readdirSync(request.user.directories.backups); const userFilesPattern = getFilePrefix(request.user.profile.handle); const userSnapshots = snapshots.filter(x => x.startsWith(userFilesPattern)); const result = userSnapshots.map(x => { - const stat = fs.statSync(path.join(PUBLIC_DIRECTORIES.backups, x)); + const stat = fs.statSync(path.join(request.user.directories.backups, x)); return { date: stat.ctimeMs, name: x, size: stat.size }; }); @@ -252,7 +248,7 @@ router.post('/load-snapshot', jsonParser, async (request, response) => { } const snapshotName = request.body.name; - const snapshotPath = path.join(PUBLIC_DIRECTORIES.backups, snapshotName); + const snapshotPath = path.join(request.user.directories.backups, snapshotName); if (!fs.existsSync(snapshotPath)) { return response.sendStatus(404); @@ -286,7 +282,7 @@ router.post('/restore-snapshot', jsonParser, async (request, response) => { } const snapshotName = request.body.name; - const snapshotPath = path.join(PUBLIC_DIRECTORIES.backups, snapshotName); + const snapshotPath = path.join(request.user.directories.backups, snapshotName); if (!fs.existsSync(snapshotPath)) { return response.sendStatus(404); diff --git a/src/users.js b/src/users.js index 10bea3fd0..d01419248 100644 --- a/src/users.js +++ b/src/users.js @@ -87,6 +87,7 @@ const STORAGE_KEYS = { * @property {string} comfyWorkflows - The directory where the ComfyUI workflows are stored * @property {string} files - The directory where the uploaded files are stored * @property {string} vectors - The directory where the vectors are stored + * @property {string} backups - The directory where the backups are stored */ /** diff --git a/src/util.js b/src/util.js index 31d134bb0..5a44933f5 100644 --- a/src/util.js +++ b/src/util.js @@ -9,8 +9,6 @@ const yaml = require('yaml'); const { default: simpleGit } = require('simple-git'); const { Readable } = require('stream'); -const { PUBLIC_DIRECTORIES } = require('./constants'); - /** * Parsed config object. */ @@ -360,14 +358,16 @@ function generateTimestamp() { } /** - * @param {string} prefix + * Remove old backups with the given prefix from a specified directory. + * @param {string} directory The root directory to remove backups from. + * @param {string} prefix File prefix to filter backups by. */ -function removeOldBackups(prefix) { +function removeOldBackups(directory, prefix) { const MAX_BACKUPS = 50; - let files = fs.readdirSync(PUBLIC_DIRECTORIES.backups).filter(f => f.startsWith(prefix)); + let files = fs.readdirSync(directory).filter(f => f.startsWith(prefix)); if (files.length > MAX_BACKUPS) { - files = files.map(f => path.join(PUBLIC_DIRECTORIES.backups, f)); + files = files.map(f => path.join(directory, f)); files.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs); fs.rmSync(files[0]);