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
PTR committed
Jun 9, 2025 at 19:38 UTC
ec3e06e37f81ab8337175d364802487d499d2530
2 files changed
+46
-74
db.js
+39
-73
@@ -3780,84 +3780,50 @@ module.exports.CreateDB = function (parent, func) {
3780
}
3781
}
3782
3783
- // Perform cloud backup
3784
- obj.performCloudBackup = function (filename, func) {
3785
- // WebDAV Backup
3786
- if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.webdav == 'object')) {
3787
- parent.debug( 'backup', 'Entering WebDAV backup');
3788
- if (func) { func('Entering WebDAV backup.'); }
3789
-
3790
- const xdateTimeSort = function (a, b) { if (a.xdate > b.xdate) return 1; if (a.xdate < b.xdate) return -1; return 0; }
3791
- // Fetch the folder name
3792
- var webdavfolderName = 'MeshCentral-Backups';
3793
- if (typeof parent.config.settings.autobackup.webdav.foldername == 'string') { webdavfolderName = parent.config.settings.autobackup.webdav.foldername; }
3794
-
3795
- // Clean up our WebDAV folder
3796
- function performWebDavCleanup(client) {
3797
- if ((typeof parent.config.settings.autobackup.webdav.maxfiles == 'number') && (parent.config.settings.autobackup.webdav.maxfiles > 1)) {
3798
- let fileName = parent.config.settings.autobackup.backupname;
3783
+ async function webDAVBackup(filename, func) {
3784
+ try {
3785
+ const webDAV = await import ('webdav');
3786
+ const wdConfig = parent.config.settings.autobackup.webdav;
3787
+ const client = webDAV.createClient(wdConfig.url, {
3788
+ username: wdConfig.username,
3789
+ password: wdConfig.password,
3790
+ maxContentLength: Infinity,
3791
+ maxBodyLength: Infinity
3792
+ });
3793
+ if (await client.exists(wdConfig.foldername) === false) {
3794
+ await client.createDirectory(wdConfig.foldername, { recursive: true});
3795
+ } else {
3796
+ // Clean up our WebDAV folder
3797
+ if ((typeof wdConfig.maxfiles == 'number') && (wdConfig.maxfiles > 1)) {
3798
+ const fileName = parent.config.settings.autobackup.backupname;
3799
//only files matching our backupfilename
3800
- let directoryItems = client.getDirectoryContents(webdavfolderName, { deep: false, glob: "/**/" + fileName + "*.zip" });
3801
- directoryItems.then(
3802
- function (files) {
3803
- for (var i in files) { files[i].xdate = new Date(files[i].lastmod); }
3804
- files.sort(xdateTimeSort);
3805
- parent.debug('backup','WebDAV filtered directory contents: ' + JSON.stringify(files, null, 4));
3806
- while (files.length >= parent.config.settings.autobackup.webdav.maxfiles) {
3807
- let delFile = files.shift().filename;
3808
- client.deleteFile(delFile).then(function (state) {
3809
- parent.debug('backup','WebDAV file deleted: ' + delFile);
3810
- if (func) { func('WebDAV file deleted: ' + delFile); }
3811
- }).catch(function (err) {
3812
- console.error(err);
3813
- if (func) { func('WebDAV (deleteFile) error: ' + err.message); }
3814
- });
3815
- }
3816
- }
3817
- ).catch(function (err) {
3818
- console.error(err);
3819
- if (func) { func('WebDAV (getDirectoryContents) error: ' + err.message); }
3820
- });
3800
+ let files = await client.getDirectoryContents(wdConfig.foldername, { deep: false, glob: "/**/" + fileName + "*.zip" });
3801
+ const xdateTimeSort = function (a, b) { if (a.xdate > b.xdate) return 1; if (a.xdate < b.xdate) return -1; return 0; }
3802
+ for (const i in files) { files[i].xdate = new Date(files[i].lastmod); }
3803
+ files.sort(xdateTimeSort);
3804
+ while (files.length >= wdConfig.maxfiles) {
3805
+ let delFile = files.shift().filename;
3806
+ await client.deleteFile(delFile);
3807
+ console.log('WebDAV file deleted: ' + delFile); if (func) { func('WebDAV file deleted: ' + delFile); }
3808
+ }
3809
}
3810
}
3823
-
3811
// Upload to the WebDAV folder
3825
- function performWebDavUpload(client, filepath) {
3826
- require('fs').stat(filepath, function(err,stat){
3827
- var fileStream = require('fs').createReadStream(filepath);
3828
- 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)); } })
3829
- fileStream.on('error', function (err) { console.error(err); if (func) { func('WebDAV (fileUpload) error: ' + err.message); } })
3830
- fileStream.pipe(client.createWriteStream('/' + webdavfolderName + '/' + require('path').basename(filepath), { headers: { "Content-Length": stat.size } }));
3831
- parent.debug('backup', 'Uploading using WebDAV to: ' + parent.config.settings.autobackup.webdav.url);
3832
- if (func) { func('Uploading using WebDAV to: ' + parent.config.settings.autobackup.webdav.url); }
3833
- });
3834
- }
3812
+ const { pipeline } = require('stream/promises');
3813
+ await pipeline(fs.createReadStream(filename), client.createWriteStream( wdConfig.foldername + path.basename(filename)));
3814
+ console.log('WebDAV upload completed: ' + wdConfig.foldername + path.basename(filename)); if (func) { func('WebDAV upload completed: ' + wdConfig.foldername + path.basename(filename)); }
3815
+ }
3816
+ catch(err) {
3817
+ console.error('WebDAV error: ' + err.message); if (func) { func('WebDAV error: ' + err.message);}
3818
+ }
3819
+ }
3820
3836
- const { createClient } = require('webdav');
3837
- const client = createClient(parent.config.settings.autobackup.webdav.url, {
3838
- username: parent.config.settings.autobackup.webdav.username,
3839
- password: parent.config.settings.autobackup.webdav.password,
3840
- maxContentLength: Infinity,
3841
- maxBodyLength: Infinity
3842
- });
3843
- client.exists(webdavfolderName).then(function(a){
3844
- if(a){
3845
- performWebDavCleanup(client);
3846
- performWebDavUpload(client, filename);
3847
- }else{
3848
- client.createDirectory(webdavfolderName, {recursive: true}).then(function (a) {
3849
- console.log('backup','WebDAV folder created: ' + webdavfolderName);
3850
- if (func) { func('WebDAV folder created: ' + webdavfolderName); }
3851
- performWebDavUpload(client, filename);
3852
- }).catch(function (err) {
3853
- console.error(err);
3854
- if (func) { func('WebDAV (createDirectory) error: ' + err.message); }
3855
- });
3856
- }
3857
- }).catch(function (err) {
3858
- console.error(err);
3859
- if (func) { func('WebDAV (exists) error: ' + err.message); }
3860
- });
3821
+ // Perform cloud backup
3822
+ obj.performCloudBackup = function (filename, func) {
3823
+ // WebDAV Backup
3824
+ if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.webdav == 'object')) {
3825
+ parent.debug( 'backup', 'Entering WebDAV backup'); if (func) { func('Entering WebDAV backup.'); }
3826
+ webDAVBackup(filename, func);
3827
}
3828
3829
// Google Drive Backup
meshcentral.js
+7
-1
@@ -2123,6 +2123,12 @@ function CreateMeshCentralServer(config, args) {
2123
else if (typeof obj.config.settings.autobackup.backupskipfoldersglob == 'string') { obj.config.settings.autobackup.backupskipfoldersglob = obj.config.settings.autobackup.backupskipfoldersglob.replaceAll(', ', ',').split(','); };
2124
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 };
2125
if (typeof obj.config.settings.autobackup.backupname != 'string') { obj.config.settings.autobackup.backupname = 'meshcentral-autobackup-'};
2126
+ if (typeof obj.config.settings.autobackup.webdav == 'object') {
2127
+ //make webdav compliant: http://www.webdav.org/specs/rfc4918.html#rfc.section.5.2, http://www.webdav.org/specs/rfc2518.html#METHOD_MKCOL
2128
+ // So with leading and trailing slash in the foldername, and no double and backslashes
2129
+ if (typeof obj.config.settings.autobackup.webdav.foldername != 'string') {obj.config.settings.autobackup.webdav.foldername = '/MeshCentral-Backups/'}
2130
+ else {obj.config.settings.autobackup.webdav.foldername = ('/' + obj.config.settings.autobackup.webdav.foldername + '/').replaceAll("\\", "/").replaceAll("//", "/").replaceAll("//", "/")};
2131
+ }
2132
}
2133
2134
// Check if the database is capable of performing a backup
@@ -4321,7 +4327,7 @@ function mainStart() {
4327
if (typeof config.settings.autobackup.googledrive == 'object') { modules.push('googleapis@128.0.0'); }
4328
// Enable WebDAV Support
4329
if (typeof config.settings.autobackup.webdav == 'object') {
4324
- 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'); }
4330
+ 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'); }
4331
}
4332
// Enable S3 Support
4333
if (typeof config.settings.autobackup.s3 == 'object') { modules.push('minio@8.0.2'); }