Compare commits

...

2 Commits

Author SHA1 Message Date
73d5213c4e
🔨 retrive backup form webdav 2022-09-30 23:52:40 +02:00
229b9dab9a
🔨 Webdav -> add check credential + init folder 2022-09-30 19:13:39 +02:00
8 changed files with 277 additions and 20 deletions

View File

@ -19,6 +19,7 @@
"debug": "4.3.4", "debug": "4.3.4",
"errorhandler": "^1.5.1", "errorhandler": "^1.5.1",
"express": "4.18.1", "express": "4.18.1",
"fast-xml-parser": "^4.0.10",
"figlet": "^1.5.2", "figlet": "^1.5.2",
"form-data": "4.0.0", "form-data": "4.0.0",
"got": "12.3.0", "got": "12.3.0",

View File

@ -22,6 +22,7 @@ specifiers:
errorhandler: ^1.5.1 errorhandler: ^1.5.1
eslint: 7.19.0 eslint: 7.19.0
express: 4.18.1 express: 4.18.1
fast-xml-parser: ^4.0.10
figlet: ^1.5.2 figlet: ^1.5.2
form-data: 4.0.0 form-data: 4.0.0
got: 12.3.0 got: 12.3.0
@ -45,6 +46,7 @@ dependencies:
debug: 4.3.4 debug: 4.3.4
errorhandler: 1.5.1 errorhandler: 1.5.1
express: 4.18.1 express: 4.18.1
fast-xml-parser: 4.0.10
figlet: 1.5.2 figlet: 1.5.2
form-data: 4.0.0 form-data: 4.0.0
got: 12.3.0 got: 12.3.0
@ -1184,6 +1186,13 @@ packages:
strnum: 1.0.5 strnum: 1.0.5
dev: false dev: false
/fast-xml-parser/4.0.10:
resolution: {integrity: sha512-mYMMIk7Ho1QOiedyvafdyPamn1Vlda+5n95lcn0g79UiCQoLQ2xfPQ8m3pcxBMpVaftYXtoIE2wrNTjmLQnnkg==}
hasBin: true
dependencies:
strnum: 1.0.5
dev: false
/fastq/1.13.0: /fastq/1.13.0:
resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
dependencies: dependencies:

View File

@ -3,8 +3,12 @@ import logger from "./config/winston.js";
import * as homeAssistantService from "./services/homeAssistantService.js"; import * as homeAssistantService from "./services/homeAssistantService.js";
import * as settingsTools from "./tools/settingsTools.js"; import * as settingsTools from "./tools/settingsTools.js";
import * as statusTools from "./tools/status.js"; import * as statusTools from "./tools/status.js";
import kleur from 'kleur'; import kleur from "kleur";
import { checkWebdavLogin, createBackupFolder } from "./services/webdavService.js";
import {
getWebdavConfig,
validateWebdavConfig,
} from "./services/webdavConfigService.js";
function postInit() { function postInit() {
logger.info(`Log level: ${process.env.LOG_LEVEL}`); logger.info(`Log level: ${process.env.LOG_LEVEL}`);
@ -31,15 +35,34 @@ function postInit() {
} }
); );
// webdav.confIsValid().then( const webdavConf = getWebdavConfig();
// () => { validateWebdavConfig(webdavConf).then(
// newlog.info("Nextcloud connection : \x1b[32mGo !\x1b[0m"); () => {
// }, logger.info("Webdav config: " + kleur.green().bold("Go !"));
// (err) => { checkWebdavLogin(webdavConf).then(
// newlog.error("Nextcloud connection : \x1b[31;1mFAIL !\x1b[0m"); () => {
// newlog.error("... " + err); logger.info("Webdav : " + kleur.green().bold("Go !"));
// } createBackupFolder(webdavConf).then(
// ); () => {
logger.info("Webdav fodlers: " + kleur.green().bold("Go !"));
},
(reason) => {
logger.error("Webdav folders: " + kleur.red().bold("FAIL !"));
logger.error(reason);
}
);
},
(reason) => {
logger.error("Webdav : " + kleur.red().bold("FAIL !"));
logger.error(reason);
}
);
},
(reason) => {
logger.error("Webdav config: " + kleur.red().bold("FAIL !"));
logger.error(reason);
}
);
settingsTools.check(settingsTools.getSettings(), true); settingsTools.check(settingsTools.getSettings(), true);
// cronTools.init(); // cronTools.init();

View File

@ -1,11 +1,13 @@
import express from "express" import express from "express"
import configRouter from "./config.js"; import configRouter from "./config.js";
import homeAssistant from "./homeAssistant.js" import homeAssistant from "./homeAssistant.js"
import webdavRouter from "./webdav.js";
const router = express.Router(); const router = express.Router();
router.use("/homeAssistant", homeAssistant) router.use("/homeAssistant", homeAssistant)
router.use("/config", configRouter); router.use("/config", configRouter);
router.use("/webdav", webdavRouter);
export default router; export default router;

View File

@ -0,0 +1,51 @@
import express from "express";
import {
getWebdavConfig,
validateWebdavConfig,
} from "../services/webdavConfigService.js";
import * as webdavService from "../services/webdavService.js";
import * as pathTools from "../tools/pathTools.js";
const webdavRouter = express.Router();
webdavRouter.get("/backup/auto", (req, res, next) => {
const config = getWebdavConfig();
validateWebdavConfig(config)
.then(() => {
webdavService
.getBackups(pathTools.auto, config)
.then((value) => {
res.json(value);
})
.catch((reason) => {
res.status(500);
res.json(reason);
});
})
.catch((reason) => {
res.status(500);
res.json(reason);
});
});
webdavRouter.get("/backup/manual", (req, res, next) => {
const config = getWebdavConfig();
validateWebdavConfig(config)
.then(() => {
webdavService
.getBackups(pathTools.manual, config)
.then((value) => {
res.json(value);
})
.catch((reason) => {
res.status(500);
res.json(reason);
});
})
.catch((reason) => {
res.status(500);
res.json(reason);
});
});
export default webdavRouter;

View File

@ -1,13 +1,16 @@
import fs from "fs"; import fs from "fs";
import Joi from "joi" import Joi from "joi";
import logger from "../config/winston.js"; import logger from "../config/winston.js";
import { default_root } from "../tools/pathTools.js"; import { default_root } from "../tools/pathTools.js";
import WebdavConfigValidation from "../types/services/webdavConfigValidation.js"; import WebdavConfigValidation from "../types/services/webdavConfigValidation.js";
import { WebdavConfig, WebdavEndpointType } from "../types/services/webdavConfig.js" import {
WebdavConfig,
WebdavEndpointType,
} from "../types/services/webdavConfig.js";
const webdavConfigPath = "/data/webdavConfigV2.json"; const webdavConfigPath = "/data/webdavConfigV2.json";
const NEXTCLOUD_ENDPOINT = "/remote.php/dav/files/$username";
export function validateWebdavConfig(config: WebdavConfig) { export function validateWebdavConfig(config: WebdavConfig) {
const validator = Joi.object(WebdavConfigValidation); const validator = Joi.object(WebdavConfigValidation);
@ -20,7 +23,7 @@ export function saveWebdavConfig(config: WebdavConfig){
export function getWebdavConfig(): WebdavConfig { export function getWebdavConfig(): WebdavConfig {
if (!fs.existsSync(webdavConfigPath)) { if (!fs.existsSync(webdavConfigPath)) {
logger.warn("Webdav Config file not found, creating default one !") logger.warn("Webdav Config file not found, creating default one !");
const defaultConfig = getWebdavDefaultConfig(); const defaultConfig = getWebdavDefaultConfig();
saveWebdavConfig(defaultConfig); saveWebdavConfig(defaultConfig);
return defaultConfig; return defaultConfig;
@ -29,6 +32,19 @@ export function getWebdavConfig(): WebdavConfig {
} }
} }
export function getEndpoint(config: WebdavConfig) {
if (config.webdavEndpoint.type == WebdavEndpointType.NEXTCLOUD) {
return NEXTCLOUD_ENDPOINT.replace("$username", config.username);
} else if (config.webdavEndpoint.customEndpoint) {
return config.webdavEndpoint.customEndpoint.replace(
"$username",
config.username
);
} else {
return "";
}
}
export function getWebdavDefaultConfig(): WebdavConfig { export function getWebdavDefaultConfig(): WebdavConfig {
return { return {
url: "", url: "",
@ -38,6 +54,6 @@ export function getWebdavDefaultConfig(): WebdavConfig {
allowSelfSignedCerts: false, allowSelfSignedCerts: false,
webdavEndpoint: { webdavEndpoint: {
type: WebdavEndpointType.NEXTCLOUD, type: WebdavEndpointType.NEXTCLOUD,
} },
} };
} }

View File

@ -1,3 +1,150 @@
import got, { HTTPError, Method } from "got";
import logger from "../config/winston.js";
import messageManager from "../tools/messageManager.js";
import { WebdavConfig } from "../types/services/webdavConfig.js";
import { getEndpoint } from "./webdavConfigService.js";
import * as pathTools from "../tools/pathTools.js";
import { XMLParser } from "fast-xml-parser";
import { WebdavBackup } from "../types/services/webdav.js";
import { DateTime } from "luxon";
const PROPFIND_BODY =
'<?xml version="1.0" encoding="utf-8" ?>\
<d:propfind xmlns:d="DAV:">\
<d:prop>\
<d:getlastmodified />\
<d:getetag />\
<d:getcontenttype />\
<d:resourcetype />\
<d:getcontentlength />\
</d:prop>\
</d:propfind>';
export function checkWebdavLogin(config: WebdavConfig) {
const endpoint = getEndpoint(config);
return got(config.url + endpoint, {
method: "OPTIONS",
headers: {
authorization:
"Basic " +
Buffer.from(config.username + ":" + config.password).toString("base64"),
},
}).then(
(response) => {
return response;
},
(reason) => {
messageManager.error("Fail to connect to Webdav", reason?.message);
logger.error(`Fail to connect to Webdav`);
logger.error(reason);
return Promise.reject(reason);
}
);
}
export async function createBackupFolder(conf: WebdavConfig) {
const root_splited = conf.backupDir.split("/").splice(1);
let path = "/";
for (const elem of root_splited) {
if (elem != "") {
path = path + elem + "/";
try {
await createDirectory(path, conf);
logger.debug(`Path ${path} created.`);
} catch (error) {
if (error instanceof HTTPError && error.response.statusCode == 405)
logger.debug(`Path ${path} already exist.`);
else {
messageManager.error("Fail to create webdav root folder");
logger.error("Fail to create webdav root folder");
logger.error(error);
return Promise.reject(error);
}
}
}
}
for (const elem of [pathTools.auto, pathTools.manual]) {
try {
await createDirectory(conf.backupDir + elem, conf);
logger.debug(`Path ${conf.backupDir + elem} created.`);
} catch (error) {
if (error instanceof HTTPError && error.response.statusCode == 405) {
logger.debug(`Path ${conf.backupDir + elem} already exist.`);
} else {
messageManager.error("Fail to create webdav root folder");
logger.error("Fail to create webdav root folder");
logger.error(error);
return Promise.reject(error);
}
}
}
}
function createDirectory(path: string, config: WebdavConfig) {
const endpoint = getEndpoint(config);
return got(config.url + endpoint + path, {
method: "MKCOL" as Method,
headers: {
authorization:
"Basic " +
Buffer.from(config.username + ":" + config.password).toString("base64"),
},
});
}
export function getBackups(folder: string, config: WebdavConfig) {
const endpoint = getEndpoint(config);
return got(config.url + endpoint + config.backupDir + folder, {
method: "PROPFIND" as Method,
headers: {
authorization:
"Basic " +
Buffer.from(config.username + ":" + config.password).toString("base64"),
Depth: "1",
},
body: PROPFIND_BODY,
}).then(
(value) => {
return parseXmlBackupData(value.body);
},
(reason) => {
messageManager.error(
`Fail to retrive webdav backups in ${folder} folder`
);
logger.error(`Fail to retrive webdav backups in ${folder} folder`);
logger.error(reason);
return Promise.reject(reason);
}
);
}
function parseXmlBackupData(body: string){
const parser = new XMLParser();
const data = parser.parse(body);
const multistatus = data["d:multistatus"];
const backups: WebdavBackup[] = [];
if(Array.isArray(multistatus["d:response"])){
for(const elem of multistatus["d:response"]){
// If array -> base folder, ignoring it
if(!Array.isArray(elem["d:propstat"])){
const propstat = elem["d:propstat"];
const id = propstat["d:prop"]["d:getetag"].replaceAll("\"", "");
const href = decodeURI(elem["d:href"]);
const name = href.split("/").slice(-1)[0];
const lastEdit = DateTime.fromHTTP(propstat["d:prop"]["d:getlastmodified"]);
backups.push({
id: id,
lastEdit: lastEdit,
size: propstat["d:prop"]["d:getcontentlenght"],
name: name
})
}
}
}
return backups;
}
// import fs from "fs"; // import fs from "fs";
// import got from "got"; // import got from "got";
// import https from "https"; // import https from "https";

View File

@ -0,0 +1,8 @@
import { DateTime } from "luxon";
export interface WebdavBackup {
id: string;
name: string;
size: number;
lastEdit: DateTime;
}