diff --git a/.gitignore b/.gitignore index 0d8a806..47e4f28 100644 --- a/.gitignore +++ b/.gitignore @@ -103,7 +103,6 @@ dist # TernJS port file .tern-port -.vscode status.json conf.json webdav_conf.json diff --git a/nextcloud_backup/backend/.gitignore b/nextcloud_backup/backend/.gitignore index 0021b4f..194a9c6 100644 --- a/nextcloud_backup/backend/.gitignore +++ b/nextcloud_backup/backend/.gitignore @@ -1 +1,5 @@ -dist/** \ No newline at end of file +dist/** +.vscode/* +!.vscode/settings.json +!.vscode/launch.json +!.vscode/tasks.json \ No newline at end of file diff --git a/nextcloud_backup/backend/.vscode/launch.json b/nextcloud_backup/backend/.vscode/launch.json new file mode 100644 index 0000000..b389ef9 --- /dev/null +++ b/nextcloud_backup/backend/.vscode/launch.json @@ -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": [ + "/**" + ], + "type": "node", + "preLaunchTask": "npm: build:watch" + } + ] +} \ No newline at end of file diff --git a/nextcloud_backup/backend/.vscode/settings.json b/nextcloud_backup/backend/.vscode/settings.json new file mode 100644 index 0000000..3553e38 --- /dev/null +++ b/nextcloud_backup/backend/.vscode/settings.json @@ -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", +} \ No newline at end of file diff --git a/nextcloud_backup/backend/.vscode/tasks.json b/nextcloud_backup/backend/.vscode/tasks.json new file mode 100644 index 0000000..b8c2d9f --- /dev/null +++ b/nextcloud_backup/backend/.vscode/tasks.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/nextcloud_backup/backend/package.json b/nextcloud_backup/backend/package.json index b8d7852..d17255d 100644 --- a/nextcloud_backup/backend/package.json +++ b/nextcloud_backup/backend/package.json @@ -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" } diff --git a/nextcloud_backup/backend/pnpm-lock.yaml b/nextcloud_backup/backend/pnpm-lock.yaml index 519bc79..87682d8 100644 --- a/nextcloud_backup/backend/pnpm-lock.yaml +++ b/nextcloud_backup/backend/pnpm-lock.yaml @@ -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'} diff --git a/nextcloud_backup/backend/src/app.ts b/nextcloud_backup/backend/src/app.ts index bde3457..d9f33ea 100644 --- a/nextcloud_backup/backend/src/app.ts +++ b/nextcloud_backup/backend/src/app.ts @@ -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; diff --git a/nextcloud_backup/backend/src/config/winston.ts b/nextcloud_backup/backend/src/config/winston.ts index 886d5ba..cbe26b9 100644 --- a/nextcloud_backup/backend/src/config/winston.ts +++ b/nextcloud_backup/backend/src/config/winston.ts @@ -1,5 +1,4 @@ import winston from "winston"; - const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( diff --git a/nextcloud_backup/backend/src/env.ts b/nextcloud_backup/backend/src/env.ts new file mode 100644 index 0000000..7ff27f7 --- /dev/null +++ b/nextcloud_backup/backend/src/env.ts @@ -0,0 +1,2 @@ +import dotenv from "dotenv"; +dotenv.config(); diff --git a/nextcloud_backup/backend/src/postInit.ts b/nextcloud_backup/backend/src/postInit.ts new file mode 100644 index 0000000..b027708 --- /dev/null +++ b/nextcloud_backup/backend/src/postInit.ts @@ -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; diff --git a/nextcloud_backup/backend/src/routes/api.ts b/nextcloud_backup/backend/src/routes/api.ts index 265ba07..61c98ce 100644 --- a/nextcloud_backup/backend/src/routes/api.ts +++ b/nextcloud_backup/backend/src/routes/api.ts @@ -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 '"' at start and end ? - for (const backup of contents) { - backup.etag = backup.etag.replace(/"/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 '"' at start and end ? +// for (const backup of contents) { +// backup.etag = backup.etag.replace(/"/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 '"' at start and end ? - for (const backup of contents) { - backup.etag = backup.etag.replace(/"/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 '"' at start and end ? +// for (const backup of contents) { +// backup.etag = backup.etag.replace(/"/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; diff --git a/nextcloud_backup/backend/src/routes/apiV2.ts b/nextcloud_backup/backend/src/routes/apiV2.ts new file mode 100644 index 0000000..582dd7f --- /dev/null +++ b/nextcloud_backup/backend/src/routes/apiV2.ts @@ -0,0 +1,9 @@ +import express from "express" +import homeAssistant from "./homeAssistant.js" + + +const router = express.Router(); + +router.use("/homeAssistant", homeAssistant) + +export default router; \ No newline at end of file diff --git a/nextcloud_backup/backend/src/routes/homeAssistant.ts b/nextcloud_backup/backend/src/routes/homeAssistant.ts new file mode 100644 index 0000000..2d503f1 --- /dev/null +++ b/nextcloud_backup/backend/src/routes/homeAssistant.ts @@ -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; \ No newline at end of file diff --git a/nextcloud_backup/backend/src/server.ts b/nextcloud_backup/backend/src/server.ts index c5df72f..a4d308c 100755 --- a/nextcloud_backup/backend/src/server.ts +++ b/nextcloud_backup/backend/src/server.ts @@ -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; \ No newline at end of file diff --git a/nextcloud_backup/backend/src/services/haOsService.ts b/nextcloud_backup/backend/src/services/homeAssistantService.ts similarity index 89% rename from nextcloud_backup/backend/src/services/haOsService.ts rename to nextcloud_backup/backend/src/services/homeAssistantService.ts index 75ef2be..98f98f4 100644 --- a/nextcloud_backup/backend/src/services/haOsService.ts +++ b/nextcloud_backup/backend/src/services/homeAssistantService.ts @@ -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> { - return got("http://hassio/core/info", { +function getVersion(): Promise>> { + return got>("http://hassio/core/info", { headers: { authorization: `Bearer ${token}` }, responseType: "json", }).then( @@ -41,19 +44,22 @@ function getVersion(): Promise> { ); logger.error(`Fail to fetch Home Assistant version`); logger.error(error); - return error; + return Promise.reject(error); } ); } -function getAddonList(): Promise> { +function getAddonList(): Promise>> { const option: OptionsOfJSONResponseBody = { headers: { authorization: `Bearer ${token}` }, responseType: "json", }; - return got<{ addons: AddonModel[] }>("http://hassio/addons", option).then( + return got>( + "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> { 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> { +function getBackups(): Promise>> { const option: OptionsOfJSONResponseBody = { headers: { authorization: `Bearer ${token}` }, responseType: "json", }; - return got<{ backups: BackupModel[] }>("http://hassio/backups", option).then( + return got>( + "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 { "Fail to download Home Assistant backup", reason.message ); - return reason; + return Promise.reject(reason); } ); } -function delSnap(id: string): Promise> { +function delSnap(id: string) { const option = { headers: { authorization: `Bearer ${token}` }, }; @@ -197,23 +204,23 @@ function delSnap(id: string): Promise> { ); logger.error("Fail to retrive Homme assistant backup detail."); logger.error(reason); - return reason; + return Promise.reject(reason); } ); } -function checkSnap(id: string): Promise> { +function checkSnap(id: string) { const option: OptionsOfJSONResponseBody = { headers: { authorization: `Bearer ${token}` }, responseType: "json", }; - return got( + return got>( `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> { ); 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> { +) { 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>( + `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); } ); } diff --git a/nextcloud_backup/backend/src/services/webdavService.ts b/nextcloud_backup/backend/src/services/webdavService.ts new file mode 100644 index 0000000..f79e8d0 --- /dev/null +++ b/nextcloud_backup/backend/src/services/webdavService.ts @@ -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((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((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; diff --git a/nextcloud_backup/backend/src/tools/cronTools.ts b/nextcloud_backup/backend/src/tools/cronTools.ts index c14e331..7e613d9 100644 --- a/nextcloud_backup/backend/src/tools/cronTools.ts +++ b/nextcloud_backup/backend/src/tools/cronTools.ts @@ -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; diff --git a/nextcloud_backup/backend/src/tools/messageManager.ts b/nextcloud_backup/backend/src/tools/messageManager.ts index 0c1791d..205b8d2 100644 --- a/nextcloud_backup/backend/src/tools/messageManager.ts +++ b/nextcloud_backup/backend/src/tools/messageManager.ts @@ -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) { diff --git a/nextcloud_backup/backend/src/tools/status.ts b/nextcloud_backup/backend/src/tools/status.ts index 8c2f021..3c1caa7 100644 --- a/nextcloud_backup/backend/src/tools/status.ts +++ b/nextcloud_backup/backend/src/tools/status.ts @@ -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); } } diff --git a/nextcloud_backup/backend/src/tools/webdavTools.ts b/nextcloud_backup/backend/src/tools/webdavTools.ts deleted file mode 100644 index b42a7f5..0000000 --- a/nextcloud_backup/backend/src/tools/webdavTools.ts +++ /dev/null @@ -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((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((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; diff --git a/nextcloud_backup/backend/src/types/message.ts b/nextcloud_backup/backend/src/types/message.ts index 81a7ea4..069a018 100644 --- a/nextcloud_backup/backend/src/types/message.ts +++ b/nextcloud_backup/backend/src/types/message.ts @@ -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; } \ No newline at end of file diff --git a/nextcloud_backup/backend/src/types/services/ha_os_response.ts b/nextcloud_backup/backend/src/types/services/ha_os_response.ts index 552d650..b14a12b 100644 --- a/nextcloud_backup/backend/src/types/services/ha_os_response.ts +++ b/nextcloud_backup/backend/src/types/services/ha_os_response.ts @@ -1,3 +1,9 @@ + +export interface SupervisorResponse { + 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; diff --git a/nextcloud_backup/backend/tsconfig.json b/nextcloud_backup/backend/tsconfig.json index 3272f8e..bf23c74 100644 --- a/nextcloud_backup/backend/tsconfig.json +++ b/nextcloud_backup/backend/tsconfig.json @@ -6,6 +6,7 @@ "module": "ES2022", "moduleResolution": "NodeNext", "target": "es6", + "sourceMap": true, }, "include": ["src/**/*"] } \ No newline at end of file