mirror of
https://github.com/Sebclem/hassio-nextcloud-backup.git
synced 2024-08-18 05:44:54 +02:00
🔨 Refracto
This commit is contained in:
parent
98b91764cc
commit
79f1cfdf09
1
.gitignore
vendored
1
.gitignore
vendored
@ -103,7 +103,6 @@ dist
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
.vscode
|
||||
status.json
|
||||
conf.json
|
||||
webdav_conf.json
|
||||
|
6
nextcloud_backup/backend/.gitignore
vendored
6
nextcloud_backup/backend/.gitignore
vendored
@ -1 +1,5 @@
|
||||
dist/**
|
||||
dist/**
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/tasks.json
|
22
nextcloud_backup/backend/.vscode/launch.json
vendored
Normal file
22
nextcloud_backup/backend/.vscode/launch.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
11
nextcloud_backup/backend/.vscode/settings.json
vendored
Normal file
11
nextcloud_backup/backend/.vscode/settings.json
vendored
Normal 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",
|
||||
}
|
14
nextcloud_backup/backend/.vscode/tasks.json
vendored
Normal file
14
nextcloud_backup/backend/.vscode/tasks.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
@ -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'}
|
||||
|
@ -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;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import winston from "winston";
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: winston.format.combine(
|
||||
|
2
nextcloud_backup/backend/src/env.ts
Normal file
2
nextcloud_backup/backend/src/env.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
48
nextcloud_backup/backend/src/postInit.ts
Normal file
48
nextcloud_backup/backend/src/postInit.ts
Normal 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;
|
@ -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;
|
||||
|
9
nextcloud_backup/backend/src/routes/apiV2.ts
Normal file
9
nextcloud_backup/backend/src/routes/apiV2.ts
Normal 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;
|
19
nextcloud_backup/backend/src/routes/homeAssistant.ts
Normal file
19
nextcloud_backup/backend/src/routes/homeAssistant.ts
Normal 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;
|
@ -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;
|
@ -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);
|
||||
}
|
||||
);
|
||||
}
|
479
nextcloud_backup/backend/src/services/webdavService.ts
Normal file
479
nextcloud_backup/backend/src/services/webdavService.ts
Normal 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;
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -6,6 +6,7 @@
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "NodeNext",
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user