WebDAV: update to v5 (#6780)

* Convert to async-await

* use require instead of import

* Use pipeline instead of pipe

* Revert "use require instead of import"

This reverts commit b5635e89cc71e47e0b7a078bce223321dc519e51.

* Sanitize webdav foldername, move to setup defaults

* Check for webdav config
This commit is contained in:
PTR 2025-06-09 19:38:59 +02:00 committed by GitHub
parent fa75a96af0
commit ec3e06e37f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 47 additions and 75 deletions

114
db.js
View File

@ -3780,84 +3780,50 @@ module.exports.CreateDB = function (parent, func) {
} }
} }
async function webDAVBackup(filename, func) {
try {
const webDAV = await import ('webdav');
const wdConfig = parent.config.settings.autobackup.webdav;
const client = webDAV.createClient(wdConfig.url, {
username: wdConfig.username,
password: wdConfig.password,
maxContentLength: Infinity,
maxBodyLength: Infinity
});
if (await client.exists(wdConfig.foldername) === false) {
await client.createDirectory(wdConfig.foldername, { recursive: true});
} else {
// Clean up our WebDAV folder
if ((typeof wdConfig.maxfiles == 'number') && (wdConfig.maxfiles > 1)) {
const fileName = parent.config.settings.autobackup.backupname;
//only files matching our backupfilename
let files = await client.getDirectoryContents(wdConfig.foldername, { deep: false, glob: "/**/" + fileName + "*.zip" });
const xdateTimeSort = function (a, b) { if (a.xdate > b.xdate) return 1; if (a.xdate < b.xdate) return -1; return 0; }
for (const i in files) { files[i].xdate = new Date(files[i].lastmod); }
files.sort(xdateTimeSort);
while (files.length >= wdConfig.maxfiles) {
let delFile = files.shift().filename;
await client.deleteFile(delFile);
console.log('WebDAV file deleted: ' + delFile); if (func) { func('WebDAV file deleted: ' + delFile); }
}
}
}
// Upload to the WebDAV folder
const { pipeline } = require('stream/promises');
await pipeline(fs.createReadStream(filename), client.createWriteStream( wdConfig.foldername + path.basename(filename)));
console.log('WebDAV upload completed: ' + wdConfig.foldername + path.basename(filename)); if (func) { func('WebDAV upload completed: ' + wdConfig.foldername + path.basename(filename)); }
}
catch(err) {
console.error('WebDAV error: ' + err.message); if (func) { func('WebDAV error: ' + err.message);}
}
}
// Perform cloud backup // Perform cloud backup
obj.performCloudBackup = function (filename, func) { obj.performCloudBackup = function (filename, func) {
// WebDAV Backup // WebDAV Backup
if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.webdav == 'object')) { if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.webdav == 'object')) {
parent.debug( 'backup', 'Entering WebDAV backup'); parent.debug( 'backup', 'Entering WebDAV backup'); if (func) { func('Entering WebDAV backup.'); }
if (func) { func('Entering WebDAV backup.'); } webDAVBackup(filename, func);
const xdateTimeSort = function (a, b) { if (a.xdate > b.xdate) return 1; if (a.xdate < b.xdate) return -1; return 0; }
// Fetch the folder name
var webdavfolderName = 'MeshCentral-Backups';
if (typeof parent.config.settings.autobackup.webdav.foldername == 'string') { webdavfolderName = parent.config.settings.autobackup.webdav.foldername; }
// Clean up our WebDAV folder
function performWebDavCleanup(client) {
if ((typeof parent.config.settings.autobackup.webdav.maxfiles == 'number') && (parent.config.settings.autobackup.webdav.maxfiles > 1)) {
let fileName = parent.config.settings.autobackup.backupname;
//only files matching our backupfilename
let directoryItems = client.getDirectoryContents(webdavfolderName, { deep: false, glob: "/**/" + fileName + "*.zip" });
directoryItems.then(
function (files) {
for (var i in files) { files[i].xdate = new Date(files[i].lastmod); }
files.sort(xdateTimeSort);
parent.debug('backup','WebDAV filtered directory contents: ' + JSON.stringify(files, null, 4));
while (files.length >= parent.config.settings.autobackup.webdav.maxfiles) {
let delFile = files.shift().filename;
client.deleteFile(delFile).then(function (state) {
parent.debug('backup','WebDAV file deleted: ' + delFile);
if (func) { func('WebDAV file deleted: ' + delFile); }
}).catch(function (err) {
console.error(err);
if (func) { func('WebDAV (deleteFile) error: ' + err.message); }
});
}
}
).catch(function (err) {
console.error(err);
if (func) { func('WebDAV (getDirectoryContents) error: ' + err.message); }
});
}
}
// Upload to the WebDAV folder
function performWebDavUpload(client, filepath) {
require('fs').stat(filepath, function(err,stat){
var fileStream = require('fs').createReadStream(filepath);
fileStream.on('close', function () { console.log('WebDAV upload completed: ' + webdavfolderName + '/' + require('path').basename(filepath)); if (func) { func('WebDAV upload completed: ' + webdavfolderName + '/' + require('path').basename(filepath)); } })
fileStream.on('error', function (err) { console.error(err); if (func) { func('WebDAV (fileUpload) error: ' + err.message); } })
fileStream.pipe(client.createWriteStream('/' + webdavfolderName + '/' + require('path').basename(filepath), { headers: { "Content-Length": stat.size } }));
parent.debug('backup', 'Uploading using WebDAV to: ' + parent.config.settings.autobackup.webdav.url);
if (func) { func('Uploading using WebDAV to: ' + parent.config.settings.autobackup.webdav.url); }
});
}
const { createClient } = require('webdav');
const client = createClient(parent.config.settings.autobackup.webdav.url, {
username: parent.config.settings.autobackup.webdav.username,
password: parent.config.settings.autobackup.webdav.password,
maxContentLength: Infinity,
maxBodyLength: Infinity
});
client.exists(webdavfolderName).then(function(a){
if(a){
performWebDavCleanup(client);
performWebDavUpload(client, filename);
}else{
client.createDirectory(webdavfolderName, {recursive: true}).then(function (a) {
console.log('backup','WebDAV folder created: ' + webdavfolderName);
if (func) { func('WebDAV folder created: ' + webdavfolderName); }
performWebDavUpload(client, filename);
}).catch(function (err) {
console.error(err);
if (func) { func('WebDAV (createDirectory) error: ' + err.message); }
});
}
}).catch(function (err) {
console.error(err);
if (func) { func('WebDAV (exists) error: ' + err.message); }
});
} }
// Google Drive Backup // Google Drive Backup

View File

@ -2123,6 +2123,12 @@ function CreateMeshCentralServer(config, args) {
else if (typeof obj.config.settings.autobackup.backupskipfoldersglob == 'string') { obj.config.settings.autobackup.backupskipfoldersglob = obj.config.settings.autobackup.backupskipfoldersglob.replaceAll(', ', ',').split(','); }; else if (typeof obj.config.settings.autobackup.backupskipfoldersglob == 'string') { obj.config.settings.autobackup.backupskipfoldersglob = obj.config.settings.autobackup.backupskipfoldersglob.replaceAll(', ', ',').split(','); };
if (typeof obj.config.settings.autobackup.backuppath == 'string') { obj.backuppath = (obj.config.settings.autobackup.backuppath = (obj.path.resolve(obj.config.settings.autobackup.backuppath))) } else { obj.config.settings.autobackup.backuppath = obj.backuppath }; if (typeof obj.config.settings.autobackup.backuppath == 'string') { obj.backuppath = (obj.config.settings.autobackup.backuppath = (obj.path.resolve(obj.config.settings.autobackup.backuppath))) } else { obj.config.settings.autobackup.backuppath = obj.backuppath };
if (typeof obj.config.settings.autobackup.backupname != 'string') { obj.config.settings.autobackup.backupname = 'meshcentral-autobackup-'}; if (typeof obj.config.settings.autobackup.backupname != 'string') { obj.config.settings.autobackup.backupname = 'meshcentral-autobackup-'};
if (typeof obj.config.settings.autobackup.webdav == 'object') {
//make webdav compliant: http://www.webdav.org/specs/rfc4918.html#rfc.section.5.2, http://www.webdav.org/specs/rfc2518.html#METHOD_MKCOL
// So with leading and trailing slash in the foldername, and no double and backslashes
if (typeof obj.config.settings.autobackup.webdav.foldername != 'string') {obj.config.settings.autobackup.webdav.foldername = '/MeshCentral-Backups/'}
else {obj.config.settings.autobackup.webdav.foldername = ('/' + obj.config.settings.autobackup.webdav.foldername + '/').replaceAll("\\", "/").replaceAll("//", "/").replaceAll("//", "/")};
}
} }
// Check if the database is capable of performing a backup // Check if the database is capable of performing a backup
@ -4321,7 +4327,7 @@ function mainStart() {
if (typeof config.settings.autobackup.googledrive == 'object') { modules.push('googleapis@128.0.0'); } if (typeof config.settings.autobackup.googledrive == 'object') { modules.push('googleapis@128.0.0'); }
// Enable WebDAV Support // Enable WebDAV Support
if (typeof config.settings.autobackup.webdav == 'object') { if (typeof config.settings.autobackup.webdav == 'object') {
if ((typeof config.settings.autobackup.webdav.url != 'string') || (typeof config.settings.autobackup.webdav.username != 'string') || (typeof config.settings.autobackup.webdav.password != 'string')) { addServerWarning("Missing WebDAV parameters.", 2, null, !args.launch); } else { modules.push('webdav@4.11.4'); } if ((typeof config.settings.autobackup.webdav.url != 'string') || (typeof config.settings.autobackup.webdav.username != 'string') || (typeof config.settings.autobackup.webdav.password != 'string')) { addServerWarning("Missing WebDAV parameters.", 2, null, !args.launch); } else { modules.push('webdav@5.8.0'); }
} }
// Enable S3 Support // Enable S3 Support
if (typeof config.settings.autobackup.s3 == 'object') { modules.push('minio@8.0.2'); } if (typeof config.settings.autobackup.s3 == 'object') { modules.push('minio@8.0.2'); }