mirror of
https://github.com/Sebclem/hassio-nextcloud-backup.git
synced 2024-11-26 02:52:59 +01:00
Compare commits
No commits in common. "73d5213c4e2220c9a071a866dd2d75b565cbce2b" and "f8579b2a162a9d390ab438b5851af45a845ae969" have entirely different histories.
73d5213c4e
...
f8579b2a16
@ -19,7 +19,6 @@
|
|||||||
"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",
|
||||||
|
@ -22,7 +22,6 @@ 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
|
||||||
@ -46,7 +45,6 @@ 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
|
||||||
@ -1186,13 +1184,6 @@ 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:
|
||||||
|
@ -3,12 +3,8 @@ 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}`);
|
||||||
@ -35,34 +31,15 @@ function postInit() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const webdavConf = getWebdavConfig();
|
// webdav.confIsValid().then(
|
||||||
validateWebdavConfig(webdavConf).then(
|
// () => {
|
||||||
() => {
|
// newlog.info("Nextcloud connection : \x1b[32mGo !\x1b[0m");
|
||||||
logger.info("Webdav config: " + kleur.green().bold("Go !"));
|
// },
|
||||||
checkWebdavLogin(webdavConf).then(
|
// (err) => {
|
||||||
() => {
|
// newlog.error("Nextcloud connection : \x1b[31;1mFAIL !\x1b[0m");
|
||||||
logger.info("Webdav : " + kleur.green().bold("Go !"));
|
// newlog.error("... " + err);
|
||||||
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();
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
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;
|
@ -1,51 +0,0 @@
|
|||||||
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;
|
|
@ -1,16 +1,13 @@
|
|||||||
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 {
|
import { WebdavConfig, WebdavEndpointType } from "../types/services/webdavConfig.js"
|
||||||
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);
|
||||||
@ -23,7 +20,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;
|
||||||
@ -32,19 +29,6 @@ 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: "",
|
||||||
@ -54,6 +38,6 @@ export function getWebdavDefaultConfig(): WebdavConfig {
|
|||||||
allowSelfSignedCerts: false,
|
allowSelfSignedCerts: false,
|
||||||
webdavEndpoint: {
|
webdavEndpoint: {
|
||||||
type: WebdavEndpointType.NEXTCLOUD,
|
type: WebdavEndpointType.NEXTCLOUD,
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
@ -1,150 +1,3 @@
|
|||||||
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";
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
import { DateTime } from "luxon";
|
|
||||||
|
|
||||||
export interface WebdavBackup {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
size: number;
|
|
||||||
lastEdit: DateTime;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user