🔨 Refracto

This commit is contained in:
SebClem 2022-09-27 23:38:40 +02:00
parent 98b91764cc
commit 79f1cfdf09
Signed by: sebclem
GPG Key ID: 5A4308F6A359EA50
24 changed files with 1147 additions and 1062 deletions

1
.gitignore vendored
View File

@ -103,7 +103,6 @@ dist
# TernJS port file
.tern-port
.vscode
status.json
conf.json
webdav_conf.json

View File

@ -1 +1,5 @@
dist/**
dist/**
.vscode/*
!.vscode/settings.json
!.vscode/launch.json
!.vscode/tasks.json

View File

@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"name": "nodemon",
"program": "${workspaceFolder}/dist/server.js",
"request": "launch",
"restart": true,
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/nodemon",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"preLaunchTask": "npm: build:watch"
}
]
}

View File

@ -0,0 +1,11 @@
{
"typescript.inlayHints.enumMemberValues.enabled": true,
"typescript.inlayHints.propertyDeclarationTypes.enabled": true,
"typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": true,
"typescript.inlayHints.parameterNames.enabled": "literals",
"typescript.inlayHints.functionLikeReturnTypes.enabled": true,
"javascript.inlayHints.enumMemberValues.enabled": true,
"javascript.inlayHints.propertyDeclarationTypes.enabled": true,
"javascript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": true,
"javascript.inlayHints.parameterNames.enabled": "literals",
}

View File

@ -0,0 +1,14 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build:watch",
"group": "build",
"label": "npm: build:watch",
"detail": "tsc -w",
"isBackground": true,
"problemMatcher": "$tsc-watch"
}
]
}

View File

@ -4,15 +4,12 @@
"private": true,
"type": "module",
"scripts": {
"build": "tsc -p .",
"debug": "pnpm run build && pnpm run watch-debug",
"build:watch": "tsc -w",
"build": "tsc --build --verbose",
"dev": "pnpm build && concurrently -n tsc,node \"pnpm build:watch\" \"pnpm serve:watch\"",
"lint": "tsc --noEmit && eslint \"**/*.{js,ts}\" --quiet --fix",
"serve-debug": "nodemon --inspect dist/server.js",
"serve": "node dist/server.js",
"watch-debug": "concurrently -k -p \"[{name}]\" -n \"TypeScript,Node\" -c \"cyan.bold,green.bold\" \"pnpm run watch-ts\" \"pnpm run serve-debug\"",
"watch-node": "nodemon dist/server.js",
"watch-ts": "tsc -w",
"watch": "concurrently -k -p \"[{name}]\" -n \"TypeScript,Node\" -c \"cyan.bold,green.bold\" \"pnpm run watch-ts\" \"pnpm run watch-node\""
"serve:watch": "nodemon dist/server.js",
"serve": "node dist/server.js"
},
"dependencies": {
"@typescript-eslint/eslint-plugin": "^5.38.0",
@ -22,13 +19,14 @@
"debug": "4.3.4",
"errorhandler": "^1.5.1",
"express": "4.18.1",
"figlet": "^1.5.2",
"form-data": "4.0.0",
"got": "12.3.0",
"http-errors": "2.0.0",
"jquery": "3.6.0",
"kleur": "^4.1.5",
"luxon": "3.0.1",
"morgan": "1.10.0",
"swagger-ui-express": "^4.5.0",
"webdav": "4.10.0",
"winston": "3.8.1"
},
@ -39,14 +37,16 @@
"@types/cron": "^2.0.0",
"@types/errorhandler": "^1.5.0",
"@types/express": "^4.17.14",
"@types/figlet": "^1.5.5",
"@types/http-errors": "^1.8.2",
"@types/luxon": "^3.0.1",
"@types/morgan": "^1.9.3",
"@types/node": "^18.7.18",
"@typescript-eslint/parser": "^5.38.0",
"concurrently": "6.0.2",
"dotenv": "^16.0.2",
"eslint": "7.19.0",
"nodemon": "^2.0.7",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"typescript": "^4.8.3"
}

View File

@ -1,12 +1,12 @@
lockfileVersion: 5.4
specifiers:
'@fortawesome/fontawesome-free': 6.1.2
'@tsconfig/recommended': ^1.0.1
'@types/cookie-parser': ^1.4.3
'@types/cron': ^2.0.0
'@types/errorhandler': ^1.5.0
'@types/express': ^4.17.14
'@types/figlet': ^1.5.5
'@types/http-errors': ^1.8.2
'@types/luxon': ^3.0.1
'@types/morgan': ^1.9.3
@ -14,46 +14,44 @@ specifiers:
'@typescript-eslint/eslint-plugin': ^5.38.0
'@typescript-eslint/parser': ^5.38.0
app-root-path: 3.0.0
bootstrap: 5.2.0
concurrently: 6.0.2
cookie-parser: 1.4.6
cron: 2.1.0
debug: 4.3.4
ejs: 3.1.8
dotenv: ^16.0.2
errorhandler: ^1.5.1
eslint: 7.19.0
express: 4.18.1
figlet: ^1.5.2
form-data: 4.0.0
got: 12.3.0
http-errors: 2.0.0
jquery: 3.6.0
kleur: ^4.1.5
luxon: 3.0.1
morgan: 1.10.0
nodemon: ^2.0.7
swagger-ui-express: ^4.5.0
nodemon: ^2.0.20
ts-node: ^10.9.1
typescript: ^4.8.3
webdav: 4.10.0
winston: 3.8.1
dependencies:
'@fortawesome/fontawesome-free': 6.1.2
'@typescript-eslint/eslint-plugin': 5.38.0_icxzmwhpfrmelnyzbhqbg2q4qi
app-root-path: 3.0.0
bootstrap: 5.2.0
cookie-parser: 1.4.6
cron: 2.1.0
debug: 4.3.4
ejs: 3.1.8
errorhandler: 1.5.1
express: 4.18.1
figlet: 1.5.2
form-data: 4.0.0
got: 12.3.0
http-errors: 2.0.0
jquery: 3.6.0
kleur: 4.1.5
luxon: 3.0.1
morgan: 1.10.0
swagger-ui-express: 4.5.0_express@4.18.1
webdav: 4.10.0_debug@4.3.4
winston: 3.8.1
@ -63,12 +61,14 @@ devDependencies:
'@types/cron': 2.0.0
'@types/errorhandler': 1.5.0
'@types/express': 4.17.14
'@types/figlet': 1.5.5
'@types/http-errors': 1.8.2
'@types/luxon': 3.0.1
'@types/morgan': 1.9.3
'@types/node': 18.7.18
'@typescript-eslint/parser': 5.38.0_xkpbw63dclu5vuec6llpbh3ip4
concurrently: 6.0.2
dotenv: 16.0.2
eslint: 7.19.0
nodemon: 2.0.20
ts-node: 10.9.1_bidgzm5cq2du6gnjtweqqjrrn4
@ -131,12 +131,6 @@ packages:
transitivePeerDependencies:
- supports-color
/@fortawesome/fontawesome-free/6.1.2:
resolution: {integrity: sha512-XwWADtfdSN73/udaFm+1mnGIj/ShDZNFMe/PRoqv3FhQ4GNI2PUN70yFTPsjq65Lw2C9i4TG5/hTbxXIXVCiqQ==}
engines: {node: '>=6'}
requiresBuild: true
dev: false
/@jridgewell/resolve-uri/3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'}
@ -261,6 +255,10 @@ packages:
'@types/serve-static': 1.15.0
dev: true
/@types/figlet/1.5.5:
resolution: {integrity: sha512-0sMBeFoqdGgdXoR/hgKYSWMpFufSpToosNsI2VgmkPqZJgeEXsXNu2hGr0FN401dBro2tNO5y2D6uw3UxVaxbg==}
dev: true
/@types/http-cache-semantics/4.0.1:
resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
dev: false
@ -603,12 +601,6 @@ packages:
- supports-color
dev: false
/bootstrap/5.2.0:
resolution: {integrity: sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==}
peerDependencies:
'@popperjs/core': ^2.11.5
dev: false
/brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
@ -919,18 +911,15 @@ packages:
dependencies:
esutils: 2.0.3
/dotenv/16.0.2:
resolution: {integrity: sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==}
engines: {node: '>=12'}
dev: true
/ee-first/1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false
/ejs/3.1.8:
resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==}
engines: {node: '>=0.10.0'}
hasBin: true
dependencies:
jake: 10.8.5
dev: false
/emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@ -1178,18 +1167,17 @@ packages:
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
dev: false
/figlet/1.5.2:
resolution: {integrity: sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==}
engines: {node: '>= 0.4.0'}
dev: false
/file-entry-cache/6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
dependencies:
flat-cache: 3.0.4
/filelist/1.0.4:
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
dependencies:
minimatch: 5.1.0
dev: false
/fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
@ -1505,17 +1493,6 @@ packages:
/isexe/2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
/jake/10.8.5:
resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==}
engines: {node: '>=10'}
hasBin: true
dependencies:
async: 3.2.4
chalk: 4.1.2
filelist: 1.0.4
minimatch: 3.1.2
dev: false
/jquery/3.6.0:
resolution: {integrity: sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==}
dev: false
@ -1553,6 +1530,11 @@ packages:
json-buffer: 3.0.1
dev: false
/kleur/4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
dev: false
/kuler/2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
dev: false
@ -2224,20 +2206,6 @@ packages:
engines: {node: '>= 0.4'}
dev: true
/swagger-ui-dist/4.14.0:
resolution: {integrity: sha512-TBzhheU15s+o54Cgk9qxuYcZMiqSm/SkvKnapoGHOF66kz0Y5aGjpzj5BT/vpBbn6rTPJ9tUYXQxuDWfsjiGMw==}
dev: false
/swagger-ui-express/4.5.0_express@4.18.1:
resolution: {integrity: sha512-DHk3zFvsxrkcnurGvQlAcLuTDacAVN1JHKDgcba/gr2NFRE4HGwP1YeHIXMiGznkWR4AeS7X5vEblNn4QljuNA==}
engines: {node: '>= v0.10.32'}
peerDependencies:
express: '>=4.0.0'
dependencies:
express: 4.18.1
swagger-ui-dist: 4.14.0
dev: false
/table/6.8.0:
resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==}
engines: {node: '>=10.0.0'}

View File

@ -1,23 +1,19 @@
import createError from "http-errors";
import express, { NextFunction, Request, Response } from "express";
import path from "path";
import cookieParser from "cookie-parser";
import fs from "fs"
import newlog from "./config/winston.js"
import * as statusTools from "./tools/status.js"
import * as settingsTools from "./tools/settingsTools.js"
import cronTools from "./tools/cronTools.js"
import webdav from "./services/webdavService.js";
import apiRouter from "./routes/api.js"
import express, { NextFunction, Request, Response } from "express";
import createError from "http-errors";
import morgan from "morgan";
import path from "path";
import { fileURLToPath } from "url";
import logger from "./config/winston.js";
import apiV2Router from "./routes/apiV2.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
app.set("port", process.env.PORT || 3000);
// app.use(
// logger("dev", {
// skip: function (req, res) {
@ -25,13 +21,16 @@ const app = express();
// },
// })
// );
app.use(
morgan("dev", { stream: { write: (message) => logger.info(message) } })
);
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));
app.use("/api", apiRouter);
app.use("/v2/api/", apiV2Router);
/*
-----------------------------------------------------------
Error handler
@ -39,61 +38,19 @@ app.use("/api", apiRouter);
*/
// catch 404 and forward to error handler
app.use((req, res, next) => {
next(createError(404));
next(createError(404));
});
// error handler
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page
res.status(err.status || 500);
res.render("error");
// render the error page
res.status(err.status || 500);
res.render("error");
});
/*
-----------------------------------------------------------
Init app
----------------------------------------------------------
*/
newlog.info(`Log level: ${ process.env.LOG_LEVEL }`);
newlog.info(`Backup timeout: ${ (process.env.CREATE_BACKUP_TIMEOUT ? parseInt(process.env.CREATE_BACKUP_TIMEOUT) : false) || ( 90 * 60 * 1000 ) }`)
if (!fs.existsSync("/data")) fs.mkdirSync("/data");
statusTools.init();
newlog.info("Satus : \x1b[32mGo !\x1b[0m");
// TODO Change this
// hassioApiTools.getSnapshots().then(
// () => {
// newlog.info("Hassio API : \x1b[32mGo !\x1b[0m");
// },
// (err) => {
// newlog.error("Hassio API : \x1b[31;1mFAIL !\x1b[0m");
// newlog.error("... " + err);
// }
// );
webdav.confIsValid().then(
() => {
newlog.info("Nextcloud connection : \x1b[32mGo !\x1b[0m");
},
(err) => {
newlog.error("Nextcloud connection : \x1b[31;1mFAIL !\x1b[0m");
newlog.error("... " + err);
}
);
settingsTools.check(settingsTools.getSettings(), true);
cronTools.init();
export default app;

View File

@ -1,5 +1,4 @@
import winston from "winston";
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(

View File

@ -0,0 +1,2 @@
import dotenv from "dotenv";
dotenv.config();

View File

@ -0,0 +1,48 @@
import { existsSync, mkdirSync } from "fs";
import logger from "./config/winston.js";
import * as homeAssistantService from "./services/homeAssistantService.js";
import * as settingsTools from "./tools/settingsTools.js";
import * as statusTools from "./tools/status.js";
import kleur from 'kleur';
function postInit() {
logger.info(`Log level: ${process.env.LOG_LEVEL}`);
logger.info(
`Backup timeout: ${
(process.env.CREATE_BACKUP_TIMEOUT
? parseInt(process.env.CREATE_BACKUP_TIMEOUT)
: false) || 90 * 60 * 1000
}`
);
if (!existsSync("/data")) mkdirSync("/data");
statusTools.init();
logger.info("Satus : " + kleur.green().bold("Go !"));
homeAssistantService.getBackups().then(
() => {
logger.info("Hassio API : " + kleur.green().bold("Go !"));
},
(err) => {
logger.error("Hassio API : " + kleur.red().bold("FAIL !"));
logger.error("... " + err);
}
);
// webdav.confIsValid().then(
// () => {
// newlog.info("Nextcloud connection : \x1b[32mGo !\x1b[0m");
// },
// (err) => {
// newlog.error("Nextcloud connection : \x1b[31;1mFAIL !\x1b[0m");
// newlog.error("... " + err);
// }
// );
settingsTools.check(settingsTools.getSettings(), true);
// cronTools.init();
}
export default postInit;

View File

@ -1,279 +1,279 @@
import express from "express";
import * as statusTools from "../tools/status.js";
import webdav from "../tools/webdavTools.js";
import * as settingsTools from "../tools/settingsTools.js";
import * as pathTools from "../tools/pathTools.js";
import * as hassioApiTools from "../tools/hassioApiTools.js";
import { humanFileSize } from "../tools/toolbox.js";
import cronTools from "../tools/cronTools.js";
import logger from "../config/winston.js";
import { DateTime } from "luxon";
// import express from "express";
// import * as statusTools from "../tools/status.js";
// import webdav from "../services/webdavService.js";
// import * as settingsTools from "../tools/settingsTools.js";
// import * as pathTools from "../tools/pathTools.js";
// import * as hassioApiTools from "../tools/hassioApiTools.js";
// import { humanFileSize } from "../tools/toolbox.js";
// import cronTools from "../tools/cronTools.js";
// import logger from "../config/winston.js";
// import { DateTime } from "luxon";
const router = express.Router();
// const router = express.Router();
router.get("/status", (req, res, next) => {
cronTools.updateNextDate();
const status = statusTools.getStatus();
res.json(status);
});
// router.get("/status", (req, res, next) => {
// cronTools.updateNextDate();
// const status = statusTools.getStatus();
// res.json(status);
// });
router.get("/formated-local-snap", function (req, res, next) {
hassioApiTools
.getSnapshots()
.then((snaps) => {
snaps.sort((a, b) => {
return a.date < b.date ? 1 : -1;
});
// router.get("/formated-local-snap", function (req, res, next) {
// hassioApiTools
// .getSnapshots()
// .then((snaps) => {
// snaps.sort((a, b) => {
// return a.date < b.date ? 1 : -1;
// });
res.render("localSnaps", { snaps: snaps, DateTime: DateTime });
})
.catch((err) => {
logger.error(err);
res.status(500);
res.send("");
});
});
// res.render("localSnaps", { snaps: snaps, DateTime: DateTime });
// })
// .catch((err) => {
// logger.error(err);
// res.status(500);
// res.send("");
// });
// });
router.get("/formated-backup-manual", function (req, res, next) {
if (webdav.getConf() == null) {
res.send("");
return;
}
webdav
.getFolderContent(webdav.getConf()?.back_dir + pathTools.manual)
.then((contents: any) => {
contents.sort((a: any, b: any) => {
return a.date < b.date ? 1 : -1;
});
//TODO Remove this when bug is fixed, etag contain '&quot;' at start and end ?
for (const backup of contents) {
backup.etag = backup.etag.replace(/&quot;/g, "");
}
res.render("backupSnaps", {
backups: contents,
DateTime: DateTime,
humanFileSize: humanFileSize,
});
})
.catch((err) => {
res.status(500);
res.send(err);
});
});
// router.get("/formated-backup-manual", function (req, res, next) {
// if (webdav.getConf() == null) {
// res.send("");
// return;
// }
// webdav
// .getFolderContent(webdav.getConf()?.back_dir + pathTools.manual)
// .then((contents: any) => {
// contents.sort((a: any, b: any) => {
// return a.date < b.date ? 1 : -1;
// });
// //TODO Remove this when bug is fixed, etag contain '&quot;' at start and end ?
// for (const backup of contents) {
// backup.etag = backup.etag.replace(/&quot;/g, "");
// }
// res.render("backupSnaps", {
// backups: contents,
// DateTime: DateTime,
// humanFileSize: humanFileSize,
// });
// })
// .catch((err) => {
// res.status(500);
// res.send(err);
// });
// });
router.get("/formated-backup-auto", function (req, res, next) {
if (webdav.getConf() == null) {
res.send("");
return;
}
const url = webdav.getConf()?.back_dir + pathTools.auto;
webdav
.getFolderContent(url)
.then((contents: any) => {
contents.sort((a: any, b: any) => {
return a.date < b.date ? 1 : -1;
});
//TODO Remove this when bug is fixed, etag contain '&quot;' at start and end ?
for (const backup of contents) {
backup.etag = backup.etag.replace(/&quot;/g, "");
}
res.render("backupSnaps", {
backups: contents,
DateTime: DateTime,
humanFileSize: humanFileSize,
});
})
.catch((err) => {
res.status(500);
res.send(err);
});
});
// router.get("/formated-backup-auto", function (req, res, next) {
// if (webdav.getConf() == null) {
// res.send("");
// return;
// }
// const url = webdav.getConf()?.back_dir + pathTools.auto;
// webdav
// .getFolderContent(url)
// .then((contents: any) => {
// contents.sort((a: any, b: any) => {
// return a.date < b.date ? 1 : -1;
// });
// //TODO Remove this when bug is fixed, etag contain '&quot;' at start and end ?
// for (const backup of contents) {
// backup.etag = backup.etag.replace(/&quot;/g, "");
// }
// res.render("backupSnaps", {
// backups: contents,
// DateTime: DateTime,
// humanFileSize: humanFileSize,
// });
// })
// .catch((err) => {
// res.status(500);
// res.send(err);
// });
// });
router.post("/nextcloud-settings", function (req, res, next) {
const settings = req.body;
if (
settings.ssl != null &&
settings.host != null &&
settings.host !== "" &&
settings.username != null &&
settings.password != null
) {
webdav.setConf(settings);
webdav
.confIsValid()
.then(() => {
res.status(201);
res.send();
})
.catch((err) => {
res.status(406);
res.json({ message: err });
});
} else {
res.status(400);
res.send();
}
});
// router.post("/nextcloud-settings", function (req, res, next) {
// const settings = req.body;
// if (
// settings.ssl != null &&
// settings.host != null &&
// settings.host !== "" &&
// settings.username != null &&
// settings.password != null
// ) {
// webdav.setConf(settings);
// webdav
// .confIsValid()
// .then(() => {
// res.status(201);
// res.send();
// })
// .catch((err) => {
// res.status(406);
// res.json({ message: err });
// });
// } else {
// res.status(400);
// res.send();
// }
// });
router.get("/nextcloud-settings", function (req, res, next) {
const conf = webdav.getConf();
if (conf == null) {
res.status(404);
res.send();
} else {
res.json(conf);
}
});
// router.get("/nextcloud-settings", function (req, res, next) {
// const conf = webdav.getConf();
// if (conf == null) {
// res.status(404);
// res.send();
// } else {
// res.json(conf);
// }
// });
router.post("/manual-backup", function (req, res, next) {
const id = req.query.id;
const name = req.query.name;
const status = statusTools.getStatus();
if (
status.status == "creating" ||
status.status == "upload" ||
status.status == "download"
) {
res.status(503);
res.send();
return;
}
// router.post("/manual-backup", function (req, res, next) {
// const id = req.query.id;
// const name = req.query.name;
// const status = statusTools.getStatus();
// if (
// status.status == "creating" ||
// status.status == "upload" ||
// status.status == "download"
// ) {
// res.status(503);
// res.send();
// return;
// }
hassioApiTools
.downloadSnapshot(id as string)
.then(() => {
webdav
.uploadFile(
id as string,
webdav.getConf()?.back_dir + pathTools.manual + name + ".tar"
)
.then(() => {
res.status(201);
res.send();
})
.catch(() => {
res.status(500);
res.send();
});
})
.catch(() => {
res.status(500);
res.send();
});
});
// hassioApiTools
// .downloadSnapshot(id as string)
// .then(() => {
// webdav
// .uploadFile(
// id as string,
// webdav.getConf()?.back_dir + pathTools.manual + name + ".tar"
// )
// .then(() => {
// res.status(201);
// res.send();
// })
// .catch(() => {
// res.status(500);
// res.send();
// });
// })
// .catch(() => {
// res.status(500);
// res.send();
// });
// });
router.post("/new-backup", function (req, res, next) {
const status = statusTools.getStatus();
if (
status.status == "creating" ||
status.status == "upload" ||
status.status == "download" ||
status.status == "stopping" ||
status.status == "starting"
) {
res.status(503);
res.send();
return;
}
hassioApiTools
.stopAddons()
.then(() => {
hassioApiTools
.getVersion()
.then((version) => {
const name = settingsTools.getFormatedName(true, version);
hassioApiTools
.createNewBackup(name)
.then((id) => {
hassioApiTools
.downloadSnapshot(id)
.then(() => {
webdav
.uploadFile(
id,
webdav.getConf()?.back_dir +
pathTools.manual +
name +
".tar"
)
.then(() => {
hassioApiTools.startAddons().catch(() => {
// Skip
});
})
.catch(() => {
// Skip
});
})
.catch(() => {
// Skip
});
})
.catch(() => {
// Skip
});
})
.catch(() => {
// Skip
});
})
.catch(() => {
hassioApiTools.startAddons().catch(() => {
// Skip
});
});
// router.post("/new-backup", function (req, res, next) {
// const status = statusTools.getStatus();
// if (
// status.status == "creating" ||
// status.status == "upload" ||
// status.status == "download" ||
// status.status == "stopping" ||
// status.status == "starting"
// ) {
// res.status(503);
// res.send();
// return;
// }
// hassioApiTools
// .stopAddons()
// .then(() => {
// hassioApiTools
// .getVersion()
// .then((version) => {
// const name = settingsTools.getFormatedName(true, version);
// hassioApiTools
// .createNewBackup(name)
// .then((id) => {
// hassioApiTools
// .downloadSnapshot(id)
// .then(() => {
// webdav
// .uploadFile(
// id,
// webdav.getConf()?.back_dir +
// pathTools.manual +
// name +
// ".tar"
// )
// .then(() => {
// hassioApiTools.startAddons().catch(() => {
// // Skip
// });
// })
// .catch(() => {
// // Skip
// });
// })
// .catch(() => {
// // Skip
// });
// })
// .catch(() => {
// // Skip
// });
// })
// .catch(() => {
// // Skip
// });
// })
// .catch(() => {
// hassioApiTools.startAddons().catch(() => {
// // Skip
// });
// });
res.status(201);
res.send();
});
// res.status(201);
// res.send();
// });
router.get("/backup-settings", function (req, res, next) {
hassioApiTools.getAddonList().then((addonList) => {
const data = {
folder: hassioApiTools.getFolderList(),
addonList: addonList,
settings: settingsTools.getSettings()
};
res.send(data);
});
});
// router.get("/backup-settings", function (req, res, next) {
// hassioApiTools.getAddonList().then((addonList) => {
// const data = {
// folder: hassioApiTools.getFolderList(),
// addonList: addonList,
// settings: settingsTools.getSettings()
// };
// res.send(data);
// });
// });
router.post("/backup-settings", function (req, res, next) {
const [result, message] = settingsTools.check(req.body);
if (result) {
settingsTools.setSettings(req.body);
cronTools.init();
res.send();
} else {
res.status(400);
res.send(message);
}
});
// router.post("/backup-settings", function (req, res, next) {
// const [result, message] = settingsTools.check(req.body);
// if (result) {
// settingsTools.setSettings(req.body);
// cronTools.init();
// res.send();
// } else {
// res.status(400);
// res.send(message);
// }
// });
router.post("/clean-now", function (req, res, next) {
webdav
.clean()
.then(() => {
hassioApiTools.clean().catch();
})
.catch(() => {
hassioApiTools.clean().catch();
});
res.status(201);
res.send();
});
// router.post("/clean-now", function (req, res, next) {
// webdav
// .clean()
// .then(() => {
// hassioApiTools.clean().catch();
// })
// .catch(() => {
// hassioApiTools.clean().catch();
// });
// res.status(201);
// res.send();
// });
router.post("/restore", function (req, res, next) {
if (req.body["path"] != null) {
webdav.downloadFile(req.body["path"]).then((path) => {
hassioApiTools.uploadSnapshot(path).catch();
});
res.status(200);
res.send();
} else {
res.status(400);
res.send();
}
});
// router.post("/restore", function (req, res, next) {
// if (req.body["path"] != null) {
// webdav.downloadFile(req.body["path"]).then((path) => {
// hassioApiTools.uploadSnapshot(path).catch();
// });
// res.status(200);
// res.send();
// } else {
// res.status(400);
// res.send();
// }
// });
export default router;
// export default router;

View File

@ -0,0 +1,9 @@
import express from "express"
import homeAssistant from "./homeAssistant.js"
const router = express.Router();
router.use("/homeAssistant", homeAssistant)
export default router;

View File

@ -0,0 +1,19 @@
import express from "express";
import * as haOsService from "../services/homeAssistantService.js"
const router = express.Router();
router.get("/backups/", (req, res, next) => {
haOsService.getBackups()
.then((value)=>{
res.json(value.body.data.backups);
}).catch((reason)=>{
res.status(500);
res.json(reason);
})
});
router.get("")
export default router;

View File

@ -1,5 +1,10 @@
import errorHandler from "errorhandler";
import "./env.js";
import app from "./app.js";
import logger from "./config/winston.js";
import postInit from "./postInit.js";
import figlet from "figlet";
import kleur from 'kleur';
/**
@ -14,12 +19,12 @@ if (process.env.NODE_ENV === "development") {
* Start Express server.
*/
const server = app.listen(app.get("port"), () => {
console.log(
" App is running at http://localhost:%d in %s mode",
app.get("port"),
app.get("env")
console.log(kleur.yellow().bold(figlet.textSync("NC Backup")))
logger.info(
`App is running at ` + kleur.green().bold(`http://localhost:${app.get("port")}`) + " in " + kleur.green().bold(app.get("env")) + " mode"
);
console.log(" Press CTRL-C to stop\n");
logger.info(kleur.red().bold("Press CTRL-C to stop"));
postInit();
});
export default server;

View File

@ -10,10 +10,13 @@ import * as settingsTools from "../tools/settingsTools.js";
import * as statusTools from "../tools/status.js";
import { NewPartialBackupPayload } from "../types/services/ha_os_payload.js";
import {
AddonData,
AddonModel,
BackupData,
BackupDetailModel,
BackupModel,
CoreInfoBody
CoreInfoBody,
SupervisorResponse,
} from "../types/services/ha_os_response.js";
import { Status } from "../types/status.js";
@ -26,8 +29,8 @@ const create_snap_timeout = process.env.CREATE_BACKUP_TIMEOUT
? parseInt(process.env.CREATE_BACKUP_TIMEOUT)
: 90 * 60 * 1000;
function getVersion(): Promise<Response<CoreInfoBody>> {
return got<CoreInfoBody>("http://hassio/core/info", {
function getVersion(): Promise<Response<SupervisorResponse<CoreInfoBody>>> {
return got<SupervisorResponse<CoreInfoBody>>("http://hassio/core/info", {
headers: { authorization: `Bearer ${token}` },
responseType: "json",
}).then(
@ -41,19 +44,22 @@ function getVersion(): Promise<Response<CoreInfoBody>> {
);
logger.error(`Fail to fetch Home Assistant version`);
logger.error(error);
return error;
return Promise.reject(error);
}
);
}
function getAddonList(): Promise<Response<{ addons: AddonModel[] }>> {
function getAddonList(): Promise<Response<SupervisorResponse<AddonData>>> {
const option: OptionsOfJSONResponseBody = {
headers: { authorization: `Bearer ${token}` },
responseType: "json",
};
return got<{ addons: AddonModel[] }>("http://hassio/addons", option).then(
return got<SupervisorResponse<AddonData>>(
"http://hassio/addons",
option
).then(
(result) => {
result.body.addons.sort((a, b) => {
result.body.data.addons.sort((a, b) => {
const textA = a.name.toUpperCase();
const textB = b.name.toUpperCase();
return textA < textB ? -1 : textA > textB ? 1 : 0;
@ -64,22 +70,20 @@ function getAddonList(): Promise<Response<{ addons: AddonModel[] }>> {
messageManager.error("Fail to fetch addons list", error?.message);
logger.error(`Fail to fetch addons list (${error?.message})`);
logger.error(error);
return error;
return Promise.reject(error);
}
);
}
function getAddonToBackup() {
function getAddonToBackup(addons: AddonModel[]) {
const excluded_addon = settingsTools.getSettings().exclude_addon;
return getAddonList().then((response) => {
const slugs: string[] = [];
for (const addon of response.body.addons) {
if (!excluded_addon.includes(addon.slug)) slugs.push(addon.slug);
}
logger.debug("Addon to backup:");
logger.debug(slugs);
return slugs;
});
const slugs: string[] = [];
for (const addon of addons) {
if (!excluded_addon.includes(addon.slug)) slugs.push(addon.slug);
}
logger.debug("Addon to backup:");
logger.debug(slugs);
return slugs;
}
function getFolderList() {
@ -119,18 +123,21 @@ function getFolderToBackup() {
return slugs;
}
function getBackups(): Promise<Response<{ backups: BackupModel[] }>> {
function getBackups(): Promise<Response<SupervisorResponse<BackupData>>> {
const option: OptionsOfJSONResponseBody = {
headers: { authorization: `Bearer ${token}` },
responseType: "json",
};
return got<{ backups: BackupModel[] }>("http://hassio/backups", option).then(
return got<SupervisorResponse<BackupData>>(
"http://hassio/backups",
option
).then(
(result) => {
return result;
},
(error) => {
messageManager.error("Fail to fetch Hassio backups", error?.message);
return error;
return Promise.reject(error);
}
);
}
@ -177,12 +184,12 @@ function downloadSnapshot(id: string): Promise<string> {
"Fail to download Home Assistant backup",
reason.message
);
return reason;
return Promise.reject(reason);
}
);
}
function delSnap(id: string): Promise<Response<string>> {
function delSnap(id: string) {
const option = {
headers: { authorization: `Bearer ${token}` },
};
@ -197,23 +204,23 @@ function delSnap(id: string): Promise<Response<string>> {
);
logger.error("Fail to retrive Homme assistant backup detail.");
logger.error(reason);
return reason;
return Promise.reject(reason);
}
);
}
function checkSnap(id: string): Promise<Response<BackupDetailModel>> {
function checkSnap(id: string) {
const option: OptionsOfJSONResponseBody = {
headers: { authorization: `Bearer ${token}` },
responseType: "json",
};
return got<BackupDetailModel>(
return got<SupervisorResponse<BackupDetailModel>>(
`http://hassio/backups/${id}/info`,
option
).then(
(result) => {
logger.info("Backup found !");
logger.debug(`Backup size: ${result.body.size}`);
logger.debug(`Backup size: ${result.body.data.size}`);
return result;
},
(reason) => {
@ -223,7 +230,7 @@ function checkSnap(id: string): Promise<Response<BackupDetailModel>> {
);
logger.error("Fail to retrive Homme assistant backup detail");
logger.error(reason);
return reason;
return Promise.reject(reason);
}
);
}
@ -232,7 +239,7 @@ function createNewBackup(
name: string,
addonSlugs: string[],
folders: string[]
): Promise<Response<{ slug: string }>> {
) {
const status = statusTools.getStatus();
status.status = "creating";
status.progress = -1;
@ -258,17 +265,20 @@ function createNewBackup(
json: body,
};
return got
.post<{ slug: string }>(`http://hassio/backups/new/partial`, option)
.post<SupervisorResponse<{ slug: string }>>(
`http://hassio/backups/new/partial`,
option
)
.then(
(result) => {
logger.info(`Snapshot created with id ${result.body.slug}`);
logger.info(`Snapshot created with id ${result.body.data.slug}`);
return result;
},
(reason) => {
messageManager.error("Fail to create new backup.", reason.message);
logger.error("Fail to create new backup");
logger.error(reason);
return reason;
return Promise.reject(reason);
}
);
}

View File

@ -0,0 +1,479 @@
// import fs from "fs";
// import got from "got";
// import https from "https";
// import path from "path";
// import stream from "stream";
// import { promisify } from "util";
// import { createClient, WebDAVClient } from "webdav";
// import { DateTime } from "luxon";
// import logger from "../config/winston.js";
// import { WebdavSettings } from "../types/settings.js";
// import * as pathTools from "../tools/pathTools.js";
// import * as settingsTools from "../tools/settingsTools.js";
// import * as statusTools from "../tools/status.js";
// const endpoint = "/remote.php/webdav";
// const configPath = "/data/webdav_conf.json";
// const pipeline = promisify(stream.pipeline);
// class WebdavTools {
// host: string | undefined;
// client: WebDAVClient | undefined;
// baseUrl: string | undefined;
// username: string | undefined;
// password: string | undefined;
// init(
// ssl: boolean,
// host: string,
// username: string,
// password: string,
// accept_selfsigned_cert: boolean
// ) {
// return new Promise((resolve, reject) => {
// this.host = host;
// const status = statusTools.getStatus();
// logger.info("Initializing and checking webdav client...");
// this.baseUrl = (ssl ? "https" : "http") + "://" + host + endpoint;
// this.username = username;
// this.password = password;
// const agent_option = ssl
// ? { rejectUnauthorized: !accept_selfsigned_cert }
// : {};
// try {
// this.client = createClient(this.baseUrl, {
// username: username,
// password: password,
// httpsAgent: new https.Agent(agent_option),
// });
// this.client
// .getDirectoryContents("/")
// .then(() => {
// if (status.error_code === 3) {
// status.status = "idle";
// status.message = undefined;
// status.error_code = undefined;
// statusTools.setStatus(status);
// }
// logger.debug("Nextcloud connection: \x1b[32mSuccess !\x1b[0m");
// this.initFolder().then(() => {
// resolve(undefined);
// });
// })
// .catch((error) => {
// this.__cant_connect_status(error);
// this.client = undefined;
// reject("Can't connect to Nextcloud (" + error + ") !");
// });
// } catch (err) {
// this.__cant_connect_status(err);
// this.client = undefined;
// reject("Can't connect to Nextcloud (" + err + ") !");
// }
// });
// }
// __cant_connect_status(err: any) {
// statusTools.setError(`Can't connect to Nextcloud (${err})`, 3);
// }
// async __createRoot() {
// const conf = this.getConf();
// if (this.client && conf) {
// const root_splited = conf.back_dir.split("/").splice(1);
// let path = "/";
// for (const elem of root_splited) {
// if (elem !== "") {
// path = path + elem + "/";
// try {
// await this.client.createDirectory(path);
// logger.debug(`Path ${path} created.`);
// } catch (error) {
// if ((error as any).status === 405)
// logger.debug(`Path ${path} already exist.`);
// else logger.error(error);
// }
// }
// }
// }
// }
// initFolder() {
// return new Promise((resolve, reject) => {
// this.__createRoot()
// .catch((err) => {
// logger.error(err);
// })
// .then(() => {
// if (!this.client) {
// return;
// }
// this.client
// .createDirectory(this.getConf()?.back_dir + pathTools.auto)
// .catch(() => {
// // Ignore
// })
// .then(() => {
// if (!this.client) {
// return;
// }
// this.client
// .createDirectory(this.getConf()?.back_dir + pathTools.manual)
// .catch(() => {
// // Ignore
// })
// .then(() => {
// resolve(undefined);
// });
// });
// });
// });
// }
// /**
// * Check if theh webdav config is valid, if yes, start init of webdav client
// */
// confIsValid() {
// return new Promise((resolve, reject) => {
// const status = statusTools.getStatus();
// const conf = this.getConf();
// if (conf != undefined) {
// if (
// conf.ssl != undefined &&
// conf.host != undefined &&
// conf.username != undefined &&
// conf.password != undefined
// ) {
// if (status.error_code === 2) {
// status.status = "idle";
// status.message = undefined;
// status.error_code = undefined;
// statusTools.setStatus(status);
// }
// // Check if self_signed option exist
// if (conf.self_signed == undefined) {
// conf.self_signed = false;
// this.setConf(conf);
// }
// this.init(
// conf.ssl,
// conf.host,
// conf.username,
// conf.password,
// conf.self_signed
// )
// .then(() => {
// resolve(undefined);
// })
// .catch((err) => {
// reject(err);
// });
// } else {
// status.status = "error";
// status.error_code = 2;
// status.message = "Nextcloud config invalid !";
// statusTools.setStatus(status);
// logger.error(status.message);
// reject("Nextcloud config invalid !");
// }
// if (conf.back_dir == null || conf.back_dir === "") {
// logger.info("Backup dir is null, initializing it.");
// conf.back_dir = pathTools.default_root;
// this.setConf(conf);
// } else {
// if (!conf.back_dir.startsWith("/")) {
// logger.warn("Backup dir not starting with '/', fixing this...");
// conf.back_dir = `/${conf.back_dir}`;
// this.setConf(conf);
// }
// if (!conf.back_dir.endsWith("/")) {
// logger.warn("Backup dir not ending with '/', fixing this...");
// conf.back_dir = `${conf.back_dir}/`;
// this.setConf(conf);
// }
// }
// } else {
// status.status = "error";
// status.error_code = 2;
// status.message = "Nextcloud config not found !";
// statusTools.setStatus(status);
// logger.error(status.message);
// reject("Nextcloud config not found !");
// }
// });
// }
// getConf(): WebdavSettings | undefined {
// if (fs.existsSync(configPath)) {
// return JSON.parse(fs.readFileSync(configPath).toString());
// } else return undefined;
// }
// setConf(conf: WebdavSettings) {
// fs.writeFileSync(configPath, JSON.stringify(conf));
// }
// uploadFile(id: string, path: string) {
// return new Promise((resolve, reject) => {
// if (this.client == null) {
// this.confIsValid()
// .then(() => {
// this._startUpload(id, path)
// .then(() => resolve(undefined))
// .catch((err) => reject(err));
// })
// .catch((err) => {
// reject(err);
// });
// } else
// this._startUpload(id, path)
// .then(() => resolve(undefined))
// .catch((err) => reject(err));
// });
// }
// _startUpload(id: string, path: string) {
// return new Promise((resolve, reject) => {
// const status = statusTools.getStatus();
// status.status = "upload";
// status.progress = 0;
// status.message = undefined;
// status.error_code = undefined;
// statusTools.setStatus(status);
// logger.info("Uploading snap...");
// const tmpFile = `./temp/${id}.tar`;
// const stats = fs.statSync(tmpFile);
// const stream = fs.createReadStream(tmpFile);
// const conf = this.getConf();
// const options = {
// body: stream,
// headers: {
// authorization:
// "Basic " +
// Buffer.from(this.username + ":" + this.password).toString("base64"),
// "content-length": String(stats.size),
// },
// https: undefined as any | undefined,
// };
// if (conf?.ssl) {
// options["https"] = { rejectUnauthorized: !conf.self_signed };
// }
// logger.debug(
// `...URI: ${encodeURI(
// this.baseUrl?.replace(this.host as string, "host.hiden") + path
// )}`
// );
// if (conf?.ssl)
// logger.debug(
// `...rejectUnauthorized: ${options.https?.rejectUnauthorized}`
// );
// got.stream
// .put(encodeURI(this.baseUrl + path), options)
// .on("uploadProgress", (e) => {
// const percent = e.percent;
// if (status.progress !== percent) {
// status.progress = percent;
// statusTools.setStatus(status);
// }
// if (percent >= 1) {
// logger.info("Upload done...");
// }
// })
// .on("response", (res) => {
// if (res.statusCode !== 201 && res.statusCode !== 204) {
// status.status = "error";
// status.error_code = 4;
// status.message = `Fail to upload snapshot to nextcloud (Status code: ${res.statusCode})!`;
// statusTools.setStatus(status);
// logger.error(status.message);
// fs.unlinkSync(tmpFile);
// reject(status.message);
// } else {
// logger.info(`...Upload finish ! (status: ${res.statusCode})`);
// status.status = "idle";
// status.progress = -1;
// status.message = undefined;
// status.error_code = undefined;
// status.last_backup = DateTime.now().toFormat("dd MMM yyyy, HH:mm");
// statusTools.setStatus(status);
// cleanTempFolder();
// const autoCleanCloud =
// settingsTools.getSettings().auto_clean_backup;
// if (autoCleanCloud != null && autoCleanCloud === "true") {
// this.clean().catch();
// }
// const autoCleanlocal = settingsTools.getSettings().auto_clean_local;
// if (autoCleanlocal != null && autoCleanlocal === "true") {
// hassioApiTools.clean().catch();
// }
// resolve(undefined);
// }
// })
// .on("error", (err) => {
// fs.unlinkSync(tmpFile);
// status.status = "error";
// status.error_code = 4;
// status.message = `Fail to upload snapshot to nextcloud (${err}) !`;
// statusTools.setStatus(status);
// logger.error(status.message);
// logger.error(err.stack);
// reject(status.message);
// });
// });
// }
// downloadFile(path: string) {
// return new Promise<string>((resolve, reject) => {
// if (this.client == null) {
// this.confIsValid()
// .then(() => {
// this._startDownload(path)
// .then((path) => resolve(path))
// .catch(() => reject());
// })
// .catch((err) => {
// reject(err);
// });
// } else
// this._startDownload(path)
// .then((path) => resolve(path))
// .catch(() => reject());
// });
// }
// _startDownload(path: string) {
// return new Promise<string>((resolve, reject) => {
// const status = statusTools.getStatus();
// status.status = "download-b";
// status.progress = 0;
// status.message = undefined;
// status.error_code = undefined;
// statusTools.setStatus(status);
// logger.info("Downloading backup...");
// if (!fs.existsSync("./temp/")) fs.mkdirSync("./temp/");
// const tmpFile = `./temp/restore_${DateTime.now().toFormat(
// "MMM-dd-yyyy_HH_mm"
// )}.tar`;
// const stream = fs.createWriteStream(tmpFile);
// const conf = this.getConf();
// const options = {
// headers: {
// authorization:
// "Basic " +
// Buffer.from(this.username + ":" + this.password).toString("base64"),
// },
// https: undefined as any | undefined,
// };
// if (conf?.ssl) {
// options["https"] = { rejectUnauthorized: !conf?.self_signed };
// }
// logger.debug(
// `...URI: ${encodeURI(
// this.baseUrl?.replace(this.host as string, "host.hiden") + path
// )}`
// );
// if (conf?.ssl)
// logger.debug(
// `...rejectUnauthorized: ${options.https?.rejectUnauthorized}`
// );
// pipeline(
// got.stream
// .get(encodeURI(this.baseUrl + path), options)
// .on("downloadProgress", (e) => {
// const percent = Math.round(e.percent * 100) / 100;
// if (status.progress !== percent) {
// status.progress = percent;
// statusTools.setStatus(status);
// }
// }),
// stream
// )
// .then((res) => {
// logger.info("Download success !");
// status.progress = 1;
// statusTools.setStatus(status);
// logger.debug(
// "Backup dl size : " + fs.statSync(tmpFile).size / 1024 / 1024
// );
// resolve(tmpFile);
// })
// .catch((err) => {
// if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile);
// status.status = "error";
// status.message =
// "Fail to download Hassio snapshot (" + err.message + ")";
// status.error_code = 7;
// statusTools.setStatus(status);
// logger.error(status.message);
// logger.error(err.stack);
// reject(err.message);
// });
// });
// }
// getFolderContent(path: string) {
// return new Promise((resolve, reject) => {
// if (this.client == null) {
// reject();
// return;
// }
// this.client
// .getDirectoryContents(path)
// .then((contents) => resolve(contents))
// .catch((error) => reject(error));
// });
// }
// clean() {
// let limit = settingsTools.getSettings().auto_clean_backup_keep;
// if (limit == null) limit = 5;
// return new Promise((resolve, reject) => {
// this.getFolderContent(this.getConf()?.back_dir + pathTools.auto)
// .then(async (contents: any) => {
// if (contents.length < limit) {
// resolve(undefined);
// return;
// }
// contents.sort((a: any, b: any) => {
// return a.date < b.date ? 1 : -1;
// });
// const toDel = contents.slice(limit);
// for (const i in toDel) {
// await this.client?.deleteFile(toDel[i].filename);
// }
// logger.info("Cloud clean done.");
// resolve(undefined);
// })
// .catch((error) => {
// const status = statusTools.getStatus();
// status.status = "error";
// status.error_code = 6;
// status.message = "Fail to clean Nexcloud (" + error + ") !";
// statusTools.setStatus(status);
// logger.error(status.message);
// reject(status.message);
// });
// });
// }
// }
// function cleanTempFolder() {
// fs.readdir("./temp/", (err, files) => {
// if (err) throw err;
// for (const file of files) {
// fs.unlink(path.join("./temp/", file), (err) => {
// if (err) throw err;
// });
// }
// });
// }
// const INSTANCE = new WebdavTools();
// export default INSTANCE;

View File

@ -1,149 +1,149 @@
import { CronJob } from "cron";
import * as settingsTools from "./settingsTools.js";
import * as hassioApiTools from "./hassioApiTools.js";
import * as statusTools from "./status.js";
import * as pathTools from "./pathTools.js";
import webdav from "../services/webdavService.js";
// import { CronJob } from "cron";
// import * as settingsTools from "./settingsTools.js";
// import * as hassioApiTools from "../services/haOsService.js";
// import * as statusTools from "./status.js";
// import * as pathTools from "./pathTools.js";
// // import webdav from "../services/webdavService.js";
import logger from "../config/winston.js";
// import logger from "../config/winston.js";
class CronContainer {
cronJob: CronJob | undefined;
cronClean: CronJob | undefined;
// class CronContainer {
// cronJob: CronJob | undefined;
// cronClean: CronJob | undefined;
init() {
const settings = settingsTools.getSettings();
let cronStr = "";
if (!this.cronClean) {
logger.info("Starting auto clean cron...");
this.cronClean = new CronJob(
"0 1 * * *",
this._clean,
null,
false,
Intl.DateTimeFormat().resolvedOptions().timeZone
);
this.cronClean.start();
}
if (this.cronJob) {
logger.info("Stopping Cron...");
this.cronJob.stop();
this.cronJob = undefined;
}
if (!settingsTools.check_cron(settingsTools.getSettings())) {
logger.warn("No Cron settings available.");
return;
}
// init() {
// const settings = settingsTools.getSettings();
// let cronStr = "";
// if (!this.cronClean) {
// logger.info("Starting auto clean cron...");
// this.cronClean = new CronJob(
// "0 1 * * *",
// this._clean,
// null,
// false,
// Intl.DateTimeFormat().resolvedOptions().timeZone
// );
// this.cronClean.start();
// }
// if (this.cronJob) {
// logger.info("Stopping Cron...");
// this.cronJob.stop();
// this.cronJob = undefined;
// }
// if (!settingsTools.check_cron(settingsTools.getSettings())) {
// logger.warn("No Cron settings available.");
// return;
// }
switch (settings.cron_base) {
case "0":
logger.warn("No Cron settings available.");
return;
case "1": {
const splited = settings.cron_hour.split(":");
cronStr = "" + splited[1] + " " + splited[0] + " * * *";
break;
}
// switch (settings.cron_base) {
// case "0":
// logger.warn("No Cron settings available.");
// return;
// case "1": {
// const splited = settings.cron_hour.split(":");
// cronStr = "" + splited[1] + " " + splited[0] + " * * *";
// break;
// }
case "2": {
const splited = settings.cron_hour.split(":");
cronStr =
"" + splited[1] + " " + splited[0] + " * * " + settings.cron_weekday;
break;
}
// case "2": {
// const splited = settings.cron_hour.split(":");
// cronStr =
// "" + splited[1] + " " + splited[0] + " * * " + settings.cron_weekday;
// break;
// }
case "3": {
const splited = settings.cron_hour.split(":");
cronStr =
"" +
splited[1] +
" " +
splited[0] +
" " +
settings.cron_month_day +
" * *";
break;
}
case "4": {
cronStr = settings.cron_custom;
break;
}
}
logger.info("Starting Cron...");
this.cronJob = new CronJob(
cronStr,
this._createBackup,
null,
false,
Intl.DateTimeFormat().resolvedOptions().timeZone
);
this.cronJob.start();
this.updateNextDate();
}
// case "3": {
// const splited = settings.cron_hour.split(":");
// cronStr =
// "" +
// splited[1] +
// " " +
// splited[0] +
// " " +
// settings.cron_month_day +
// " * *";
// break;
// }
// case "4": {
// cronStr = settings.cron_custom;
// break;
// }
// }
// logger.info("Starting Cron...");
// this.cronJob = new CronJob(
// cronStr,
// this._createBackup,
// null,
// false,
// Intl.DateTimeFormat().resolvedOptions().timeZone
// );
// this.cronJob.start();
// this.updateNextDate();
// }
updateNextDate() {
let date;
if (this.cronJob) {
date = this.cronJob
.nextDate()
.setLocale("en")
.toFormat("dd MMM yyyy, HH:mm");
}
const status = statusTools.getStatus();
status.next_backup = date;
statusTools.setStatus(status);
}
// updateNextDate() {
// let date;
// if (this.cronJob) {
// date = this.cronJob
// .nextDate()
// .setLocale("en")
// .toFormat("dd MMM yyyy, HH:mm");
// }
// const status = statusTools.getStatus();
// status.next_backup = date;
// statusTools.setStatus(status);
// }
_createBackup() {
logger.debug("Cron triggered !");
const status = statusTools.getStatus();
if (
status.status === "creating" ||
status.status === "upload" ||
status.status === "download" ||
status.status === "stopping" ||
status.status === "starting"
)
return;
hassioApiTools
.stopAddons()
.then(() => {
hassioApiTools.getVersion().then((version) => {
const name = settingsTools.getFormatedName(false, version);
hassioApiTools.createNewBackup(name).then((id) => {
hassioApiTools.downloadSnapshot(id).then(() => {
webdav
.uploadFile(
id,
webdav.getConf()?.back_dir + pathTools.auto + name + ".tar"
)
.then(() => {
hassioApiTools.startAddons().catch(() => {
// Skip
});
});
});
});
});
})
.catch(() => {
hassioApiTools.startAddons().catch(() => {
// Skip
});
});
}
// _createBackup() {
// logger.debug("Cron triggered !");
// const status = statusTools.getStatus();
// if (
// status.status === "creating" ||
// status.status === "upload" ||
// status.status === "download" ||
// status.status === "stopping" ||
// status.status === "starting"
// )
// return;
// hassioApiTools
// .stopAddons()
// .then(() => {
// hassioApiTools.getVersion().then((version) => {
// const name = settingsTools.getFormatedName(false, version);
// hassioApiTools.createNewBackup(name).then((id) => {
// hassioApiTools.downloadSnapshot(id).then(() => {
// webdav
// .uploadFile(
// id,
// webdav.getConf()?.back_dir + pathTools.auto + name + ".tar"
// )
// .then(() => {
// hassioApiTools.startAddons().catch(() => {
// // Skip
// });
// });
// });
// });
// });
// })
// .catch(() => {
// hassioApiTools.startAddons().catch(() => {
// // Skip
// });
// });
// }
_clean() {
const autoCleanlocal = settingsTools.getSettings().auto_clean_local;
if (autoCleanlocal && autoCleanlocal == "true") {
hassioApiTools.clean().catch();
}
const autoCleanCloud = settingsTools.getSettings().auto_clean_backup;
if (autoCleanCloud && autoCleanCloud == "true") {
webdav.clean().catch();
}
}
}
// _clean() {
// const autoCleanlocal = settingsTools.getSettings().auto_clean_local;
// if (autoCleanlocal && autoCleanlocal == "true") {
// hassioApiTools.clean().catch();
// }
// const autoCleanCloud = settingsTools.getSettings().auto_clean_backup;
// if (autoCleanCloud && autoCleanCloud == "true") {
// webdav.clean().catch();
// }
// }
// }
const INSTANCE = new CronContainer();
export default INSTANCE;
// const INSTANCE = new CronContainer();
// export default INSTANCE;

View File

@ -6,11 +6,12 @@ const maxMessageLength = 255;
class MessageManager {
private messages: Message[] = [];
public addMessage(type: MessageType, message: string, detail?: string) {
public addMessage(type: MessageType, message: string, detail?: string, isImportant = false) {
this.messages.push({
message: message,
type: type,
time: DateTime.now(),
viewed: !isImportant,
detail: detail
});
if (this.messages.length > maxMessageLength) {
@ -19,7 +20,7 @@ class MessageManager {
}
public error(message: string, detail?: string) {
this.addMessage(MessageType.ERROR, message, detail);
this.addMessage(MessageType.ERROR, message, detail, true);
}
public warn(message: string, detail?: string) {

View File

@ -1,4 +1,4 @@
import * as hassioApiTools from "./hassioApiTools.js";
import { publish_state } from "../services/homeAssistantService.js";
import logger from "../config/winston.js"
import { type Status } from "../types/status.js";
@ -25,7 +25,7 @@ export function setStatus(new_state: Status) {
const old_state_str = JSON.stringify(status);
if(old_state_str !== JSON.stringify(new_state)){
status = new_state;
hassioApiTools.publish_state(status);
publish_state(status);
}
}

View File

@ -1,480 +0,0 @@
import fs from "fs";
import got from "got";
import https from "https";
import path from "path";
import stream from "stream";
import { promisify } from "util";
import { createClient, WebDAVClient } from "webdav";
import { DateTime } from "luxon";
import logger from "../config/winston.js";
import { WebdavSettings } from "../types/settings.js";
import * as hassioApiTools from "./hassioApiTools.js";
import * as pathTools from "./pathTools.js";
import * as settingsTools from "./settingsTools.js";
import * as statusTools from "./status.js";
const endpoint = "/remote.php/webdav";
const configPath = "/data/webdav_conf.json";
const pipeline = promisify(stream.pipeline);
class WebdavTools {
host: string | undefined;
client: WebDAVClient | undefined;
baseUrl: string | undefined;
username: string | undefined;
password: string | undefined;
init(
ssl: boolean,
host: string,
username: string,
password: string,
accept_selfsigned_cert: boolean
) {
return new Promise((resolve, reject) => {
this.host = host;
const status = statusTools.getStatus();
logger.info("Initializing and checking webdav client...");
this.baseUrl = (ssl ? "https" : "http") + "://" + host + endpoint;
this.username = username;
this.password = password;
const agent_option = ssl
? { rejectUnauthorized: !accept_selfsigned_cert }
: {};
try {
this.client = createClient(this.baseUrl, {
username: username,
password: password,
httpsAgent: new https.Agent(agent_option),
});
this.client
.getDirectoryContents("/")
.then(() => {
if (status.error_code === 3) {
status.status = "idle";
status.message = undefined;
status.error_code = undefined;
statusTools.setStatus(status);
}
logger.debug("Nextcloud connection: \x1b[32mSuccess !\x1b[0m");
this.initFolder().then(() => {
resolve(undefined);
});
})
.catch((error) => {
this.__cant_connect_status(error);
this.client = undefined;
reject("Can't connect to Nextcloud (" + error + ") !");
});
} catch (err) {
this.__cant_connect_status(err);
this.client = undefined;
reject("Can't connect to Nextcloud (" + err + ") !");
}
});
}
__cant_connect_status(err: any) {
statusTools.setError(`Can't connect to Nextcloud (${err})`, 3);
}
async __createRoot() {
const conf = this.getConf();
if (this.client && conf) {
const root_splited = conf.back_dir.split("/").splice(1);
let path = "/";
for (const elem of root_splited) {
if (elem !== "") {
path = path + elem + "/";
try {
await this.client.createDirectory(path);
logger.debug(`Path ${path} created.`);
} catch (error) {
if ((error as any).status === 405)
logger.debug(`Path ${path} already exist.`);
else logger.error(error);
}
}
}
}
}
initFolder() {
return new Promise((resolve, reject) => {
this.__createRoot()
.catch((err) => {
logger.error(err);
})
.then(() => {
if (!this.client) {
return;
}
this.client
.createDirectory(this.getConf()?.back_dir + pathTools.auto)
.catch(() => {
// Ignore
})
.then(() => {
if (!this.client) {
return;
}
this.client
.createDirectory(this.getConf()?.back_dir + pathTools.manual)
.catch(() => {
// Ignore
})
.then(() => {
resolve(undefined);
});
});
});
});
}
/**
* Check if theh webdav config is valid, if yes, start init of webdav client
*/
confIsValid() {
return new Promise((resolve, reject) => {
const status = statusTools.getStatus();
const conf = this.getConf();
if (conf != undefined) {
if (
conf.ssl != undefined &&
conf.host != undefined &&
conf.username != undefined &&
conf.password != undefined
) {
if (status.error_code === 2) {
status.status = "idle";
status.message = undefined;
status.error_code = undefined;
statusTools.setStatus(status);
}
// Check if self_signed option exist
if (conf.self_signed == undefined) {
conf.self_signed = false;
this.setConf(conf);
}
this.init(
conf.ssl,
conf.host,
conf.username,
conf.password,
conf.self_signed
)
.then(() => {
resolve(undefined);
})
.catch((err) => {
reject(err);
});
} else {
status.status = "error";
status.error_code = 2;
status.message = "Nextcloud config invalid !";
statusTools.setStatus(status);
logger.error(status.message);
reject("Nextcloud config invalid !");
}
if (conf.back_dir == null || conf.back_dir === "") {
logger.info("Backup dir is null, initializing it.");
conf.back_dir = pathTools.default_root;
this.setConf(conf);
} else {
if (!conf.back_dir.startsWith("/")) {
logger.warn("Backup dir not starting with '/', fixing this...");
conf.back_dir = `/${conf.back_dir}`;
this.setConf(conf);
}
if (!conf.back_dir.endsWith("/")) {
logger.warn("Backup dir not ending with '/', fixing this...");
conf.back_dir = `${conf.back_dir}/`;
this.setConf(conf);
}
}
} else {
status.status = "error";
status.error_code = 2;
status.message = "Nextcloud config not found !";
statusTools.setStatus(status);
logger.error(status.message);
reject("Nextcloud config not found !");
}
});
}
getConf(): WebdavSettings | undefined {
if (fs.existsSync(configPath)) {
return JSON.parse(fs.readFileSync(configPath).toString());
} else return undefined;
}
setConf(conf: WebdavSettings) {
fs.writeFileSync(configPath, JSON.stringify(conf));
}
uploadFile(id: string, path: string) {
return new Promise((resolve, reject) => {
if (this.client == null) {
this.confIsValid()
.then(() => {
this._startUpload(id, path)
.then(() => resolve(undefined))
.catch((err) => reject(err));
})
.catch((err) => {
reject(err);
});
} else
this._startUpload(id, path)
.then(() => resolve(undefined))
.catch((err) => reject(err));
});
}
_startUpload(id: string, path: string) {
return new Promise((resolve, reject) => {
const status = statusTools.getStatus();
status.status = "upload";
status.progress = 0;
status.message = undefined;
status.error_code = undefined;
statusTools.setStatus(status);
logger.info("Uploading snap...");
const tmpFile = `./temp/${id}.tar`;
const stats = fs.statSync(tmpFile);
const stream = fs.createReadStream(tmpFile);
const conf = this.getConf();
const options = {
body: stream,
headers: {
authorization:
"Basic " +
Buffer.from(this.username + ":" + this.password).toString("base64"),
"content-length": String(stats.size),
},
https: undefined as any | undefined,
};
if (conf?.ssl) {
options["https"] = { rejectUnauthorized: !conf.self_signed };
}
logger.debug(
`...URI: ${encodeURI(
this.baseUrl?.replace(this.host as string, "host.hiden") + path
)}`
);
if (conf?.ssl)
logger.debug(
`...rejectUnauthorized: ${options.https?.rejectUnauthorized}`
);
got.stream
.put(encodeURI(this.baseUrl + path), options)
.on("uploadProgress", (e) => {
const percent = e.percent;
if (status.progress !== percent) {
status.progress = percent;
statusTools.setStatus(status);
}
if (percent >= 1) {
logger.info("Upload done...");
}
})
.on("response", (res) => {
if (res.statusCode !== 201 && res.statusCode !== 204) {
status.status = "error";
status.error_code = 4;
status.message = `Fail to upload snapshot to nextcloud (Status code: ${res.statusCode})!`;
statusTools.setStatus(status);
logger.error(status.message);
fs.unlinkSync(tmpFile);
reject(status.message);
} else {
logger.info(`...Upload finish ! (status: ${res.statusCode})`);
status.status = "idle";
status.progress = -1;
status.message = undefined;
status.error_code = undefined;
status.last_backup = DateTime.now().toFormat("dd MMM yyyy, HH:mm");
statusTools.setStatus(status);
cleanTempFolder();
const autoCleanCloud =
settingsTools.getSettings().auto_clean_backup;
if (autoCleanCloud != null && autoCleanCloud === "true") {
this.clean().catch();
}
const autoCleanlocal = settingsTools.getSettings().auto_clean_local;
if (autoCleanlocal != null && autoCleanlocal === "true") {
hassioApiTools.clean().catch();
}
resolve(undefined);
}
})
.on("error", (err) => {
fs.unlinkSync(tmpFile);
status.status = "error";
status.error_code = 4;
status.message = `Fail to upload snapshot to nextcloud (${err}) !`;
statusTools.setStatus(status);
logger.error(status.message);
logger.error(err.stack);
reject(status.message);
});
});
}
downloadFile(path: string) {
return new Promise<string>((resolve, reject) => {
if (this.client == null) {
this.confIsValid()
.then(() => {
this._startDownload(path)
.then((path) => resolve(path))
.catch(() => reject());
})
.catch((err) => {
reject(err);
});
} else
this._startDownload(path)
.then((path) => resolve(path))
.catch(() => reject());
});
}
_startDownload(path: string) {
return new Promise<string>((resolve, reject) => {
const status = statusTools.getStatus();
status.status = "download-b";
status.progress = 0;
status.message = undefined;
status.error_code = undefined;
statusTools.setStatus(status);
logger.info("Downloading backup...");
if (!fs.existsSync("./temp/")) fs.mkdirSync("./temp/");
const tmpFile = `./temp/restore_${DateTime.now().toFormat(
"MMM-dd-yyyy_HH_mm"
)}.tar`;
const stream = fs.createWriteStream(tmpFile);
const conf = this.getConf();
const options = {
headers: {
authorization:
"Basic " +
Buffer.from(this.username + ":" + this.password).toString("base64"),
},
https: undefined as any | undefined,
};
if (conf?.ssl) {
options["https"] = { rejectUnauthorized: !conf?.self_signed };
}
logger.debug(
`...URI: ${encodeURI(
this.baseUrl?.replace(this.host as string, "host.hiden") + path
)}`
);
if (conf?.ssl)
logger.debug(
`...rejectUnauthorized: ${options.https?.rejectUnauthorized}`
);
pipeline(
got.stream
.get(encodeURI(this.baseUrl + path), options)
.on("downloadProgress", (e) => {
const percent = Math.round(e.percent * 100) / 100;
if (status.progress !== percent) {
status.progress = percent;
statusTools.setStatus(status);
}
}),
stream
)
.then((res) => {
logger.info("Download success !");
status.progress = 1;
statusTools.setStatus(status);
logger.debug(
"Backup dl size : " + fs.statSync(tmpFile).size / 1024 / 1024
);
resolve(tmpFile);
})
.catch((err) => {
if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile);
status.status = "error";
status.message =
"Fail to download Hassio snapshot (" + err.message + ")";
status.error_code = 7;
statusTools.setStatus(status);
logger.error(status.message);
logger.error(err.stack);
reject(err.message);
});
});
}
getFolderContent(path: string) {
return new Promise((resolve, reject) => {
if (this.client == null) {
reject();
return;
}
this.client
.getDirectoryContents(path)
.then((contents) => resolve(contents))
.catch((error) => reject(error));
});
}
clean() {
let limit = settingsTools.getSettings().auto_clean_backup_keep;
if (limit == null) limit = 5;
return new Promise((resolve, reject) => {
this.getFolderContent(this.getConf()?.back_dir + pathTools.auto)
.then(async (contents: any) => {
if (contents.length < limit) {
resolve(undefined);
return;
}
contents.sort((a: any, b: any) => {
return a.date < b.date ? 1 : -1;
});
const toDel = contents.slice(limit);
for (const i in toDel) {
await this.client?.deleteFile(toDel[i].filename);
}
logger.info("Cloud clean done.");
resolve(undefined);
})
.catch((error) => {
const status = statusTools.getStatus();
status.status = "error";
status.error_code = 6;
status.message = "Fail to clean Nexcloud (" + error + ") !";
statusTools.setStatus(status);
logger.error(status.message);
reject(status.message);
});
});
}
}
function cleanTempFolder() {
fs.readdir("./temp/", (err, files) => {
if (err) throw err;
for (const file of files) {
fs.unlink(path.join("./temp/", file), (err) => {
if (err) throw err;
});
}
});
}
const INSTANCE = new WebdavTools();
export default INSTANCE;

View File

@ -1,10 +1,10 @@
import { DateTime } from "luxon";
export enum MessageType {
ERROR,
WARN,
INFO,
SUCCESS
ERROR = "ERROR",
WARN = "WARN",
INFO = "INFO",
SUCCESS = "SUCCESS"
}
@ -12,5 +12,6 @@ export interface Message {
time: DateTime;
type: MessageType;
message: string;
viewed: boolean;
detail?: string;
}

View File

@ -1,3 +1,9 @@
export interface SupervisorResponse<T> {
result: string;
data: T;
}
export interface CoreInfoBody {
version: string;
version_latest: string;
@ -15,6 +21,11 @@ export interface CoreInfoBody {
audio_output: string;
}
export interface AddonData {
addons: AddonModel[];
}
export interface AddonModel {
name: string;
slug: string;
@ -31,6 +42,11 @@ export interface AddonModel {
state: string;
}
export interface BackupData {
backups: BackupModel[]
}
export interface BackupModel {
slug: string;
date: string;

View File

@ -6,6 +6,7 @@
"module": "ES2022",
"moduleResolution": "NodeNext",
"target": "es6",
"sourceMap": true,
},
"include": ["src/**/*"]
}