mirror of
https://github.com/Lissy93/dashy.git
synced 2026-06-06 00:34:30 -04:00
By default, everything functions the same. But if the `DISABLE_CONFIG_BACKUPS` env var is set, then when the user saves the config to disk (via UI, calling the save-config endpoint), then the previous config will not get coppied/backedup to the BACKUP_DIR (user-data/config-backups) anymore. Also added docs for this change. This option might be useful on file systems with restricted permissions (if /app/user-data/config-backups doesn't have create/write access), or when the user just doesn't want a ton of backup files for every single litttle config change.
79 lines
2.8 KiB
JavaScript
79 lines
2.8 KiB
JavaScript
/**
|
|
* This file exports a function, used by the write config endpoint.
|
|
* It will make a backup of the users conf.yml file
|
|
* and then write their new config into the main conf.yml file.
|
|
* Finally, it will call a function with the status message
|
|
*/
|
|
const fsPromises = require('fs').promises;
|
|
const path = require('path');
|
|
|
|
const MAX_CONFIG_BYTES = 256 * 1024;
|
|
|
|
// Disallow paths having path separators, control chars (NUL/CR/LF), or ..
|
|
const SAFE_FILENAME = /^(?!\.+$)[^\\/\0\r\n]+\.ya?ml$/i;
|
|
|
|
module.exports = async (newConfig, render) => {
|
|
const respond = (success, message) => render(JSON.stringify({ success, message }));
|
|
|
|
// Validate request body
|
|
if (!newConfig || typeof newConfig.config !== 'string' || newConfig.config.length === 0) {
|
|
respond(false, "Request body is missing or has an invalid 'config' field");
|
|
return;
|
|
}
|
|
if (newConfig.config.length > MAX_CONFIG_BYTES) {
|
|
respond(false, `Config exceeds maximum size of ${MAX_CONFIG_BYTES / 1024} KB`);
|
|
return;
|
|
}
|
|
|
|
// If `filename` (for sub-pages) is specified validate and set it
|
|
let usersFileName;
|
|
if (typeof newConfig.filename === 'string' && newConfig.filename) {
|
|
const base = path.basename(newConfig.filename);
|
|
if (!SAFE_FILENAME.test(base)) {
|
|
respond(false, 'Invalid filename: must be a basename ending in .yml or .yaml');
|
|
return;
|
|
}
|
|
usersFileName = base;
|
|
}
|
|
|
|
// Resolve paths
|
|
const userDataDirectory = process.env.USER_DATA_DIR || './user-data/';
|
|
const backupLocation = process.env.BACKUP_DIR || path.join(userDataDirectory, 'config-backups');
|
|
const backupsEnabled = process.env.DISABLE_CONFIG_BACKUPS !== 'true';
|
|
const targetFile = usersFileName || 'conf.yml';
|
|
const targetFilePath = path.join(userDataDirectory, targetFile);
|
|
|
|
const backupBase = targetFile.replace(/\.ya?ml$/i, '');
|
|
const backupFilePath = path.join(backupLocation, `${backupBase}-${Date.now()}.backup.yml`);
|
|
|
|
// Backup current config before proceeding (unless disabled via DISABLE_CONFIG_BACKUPS)
|
|
let backedUp = false;
|
|
if (backupsEnabled) {
|
|
try {
|
|
await fsPromises.mkdir(backupLocation, { recursive: true });
|
|
await fsPromises.copyFile(targetFilePath, backupFilePath);
|
|
backedUp = true;
|
|
} catch (error) {
|
|
if (error.code !== 'ENOENT') {
|
|
respond(false, `Unable to backup ${targetFile}: ${error}`);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write the new config
|
|
try {
|
|
await fsPromises.writeFile(targetFilePath, newConfig.config, { encoding: 'utf8' });
|
|
} catch (error) {
|
|
respond(false, `Unable to write to ${targetFile}: ${error}`);
|
|
return;
|
|
}
|
|
|
|
// If successful, then render hasn't yet been called- call it
|
|
let responseMsg = `Config saved successfully in ${targetFilePath}.`;
|
|
if (backedUp) {
|
|
responseMsg += ` Previous ${targetFile} was backed up to ${backupFilePath}.`;
|
|
}
|
|
respond(true, responseMsg);
|
|
};
|