🔨 Refracto

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

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
lockfileVersion: 5.4 lockfileVersion: 5.4
specifiers: specifiers:
'@fortawesome/fontawesome-free': 6.1.2
'@tsconfig/recommended': ^1.0.1 '@tsconfig/recommended': ^1.0.1
'@types/cookie-parser': ^1.4.3 '@types/cookie-parser': ^1.4.3
'@types/cron': ^2.0.0 '@types/cron': ^2.0.0
'@types/errorhandler': ^1.5.0 '@types/errorhandler': ^1.5.0
'@types/express': ^4.17.14 '@types/express': ^4.17.14
'@types/figlet': ^1.5.5
'@types/http-errors': ^1.8.2 '@types/http-errors': ^1.8.2
'@types/luxon': ^3.0.1 '@types/luxon': ^3.0.1
'@types/morgan': ^1.9.3 '@types/morgan': ^1.9.3
@ -14,46 +14,44 @@ specifiers:
'@typescript-eslint/eslint-plugin': ^5.38.0 '@typescript-eslint/eslint-plugin': ^5.38.0
'@typescript-eslint/parser': ^5.38.0 '@typescript-eslint/parser': ^5.38.0
app-root-path: 3.0.0 app-root-path: 3.0.0
bootstrap: 5.2.0
concurrently: 6.0.2 concurrently: 6.0.2
cookie-parser: 1.4.6 cookie-parser: 1.4.6
cron: 2.1.0 cron: 2.1.0
debug: 4.3.4 debug: 4.3.4
ejs: 3.1.8 dotenv: ^16.0.2
errorhandler: ^1.5.1 errorhandler: ^1.5.1
eslint: 7.19.0 eslint: 7.19.0
express: 4.18.1 express: 4.18.1
figlet: ^1.5.2
form-data: 4.0.0 form-data: 4.0.0
got: 12.3.0 got: 12.3.0
http-errors: 2.0.0 http-errors: 2.0.0
jquery: 3.6.0 jquery: 3.6.0
kleur: ^4.1.5
luxon: 3.0.1 luxon: 3.0.1
morgan: 1.10.0 morgan: 1.10.0
nodemon: ^2.0.7 nodemon: ^2.0.20
swagger-ui-express: ^4.5.0
ts-node: ^10.9.1 ts-node: ^10.9.1
typescript: ^4.8.3 typescript: ^4.8.3
webdav: 4.10.0 webdav: 4.10.0
winston: 3.8.1 winston: 3.8.1
dependencies: dependencies:
'@fortawesome/fontawesome-free': 6.1.2
'@typescript-eslint/eslint-plugin': 5.38.0_icxzmwhpfrmelnyzbhqbg2q4qi '@typescript-eslint/eslint-plugin': 5.38.0_icxzmwhpfrmelnyzbhqbg2q4qi
app-root-path: 3.0.0 app-root-path: 3.0.0
bootstrap: 5.2.0
cookie-parser: 1.4.6 cookie-parser: 1.4.6
cron: 2.1.0 cron: 2.1.0
debug: 4.3.4 debug: 4.3.4
ejs: 3.1.8
errorhandler: 1.5.1 errorhandler: 1.5.1
express: 4.18.1 express: 4.18.1
figlet: 1.5.2
form-data: 4.0.0 form-data: 4.0.0
got: 12.3.0 got: 12.3.0
http-errors: 2.0.0 http-errors: 2.0.0
jquery: 3.6.0 jquery: 3.6.0
kleur: 4.1.5
luxon: 3.0.1 luxon: 3.0.1
morgan: 1.10.0 morgan: 1.10.0
swagger-ui-express: 4.5.0_express@4.18.1
webdav: 4.10.0_debug@4.3.4 webdav: 4.10.0_debug@4.3.4
winston: 3.8.1 winston: 3.8.1
@ -63,12 +61,14 @@ devDependencies:
'@types/cron': 2.0.0 '@types/cron': 2.0.0
'@types/errorhandler': 1.5.0 '@types/errorhandler': 1.5.0
'@types/express': 4.17.14 '@types/express': 4.17.14
'@types/figlet': 1.5.5
'@types/http-errors': 1.8.2 '@types/http-errors': 1.8.2
'@types/luxon': 3.0.1 '@types/luxon': 3.0.1
'@types/morgan': 1.9.3 '@types/morgan': 1.9.3
'@types/node': 18.7.18 '@types/node': 18.7.18
'@typescript-eslint/parser': 5.38.0_xkpbw63dclu5vuec6llpbh3ip4 '@typescript-eslint/parser': 5.38.0_xkpbw63dclu5vuec6llpbh3ip4
concurrently: 6.0.2 concurrently: 6.0.2
dotenv: 16.0.2
eslint: 7.19.0 eslint: 7.19.0
nodemon: 2.0.20 nodemon: 2.0.20
ts-node: 10.9.1_bidgzm5cq2du6gnjtweqqjrrn4 ts-node: 10.9.1_bidgzm5cq2du6gnjtweqqjrrn4
@ -131,12 +131,6 @@ packages:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - 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: /@jridgewell/resolve-uri/3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@ -261,6 +255,10 @@ packages:
'@types/serve-static': 1.15.0 '@types/serve-static': 1.15.0
dev: true dev: true
/@types/figlet/1.5.5:
resolution: {integrity: sha512-0sMBeFoqdGgdXoR/hgKYSWMpFufSpToosNsI2VgmkPqZJgeEXsXNu2hGr0FN401dBro2tNO5y2D6uw3UxVaxbg==}
dev: true
/@types/http-cache-semantics/4.0.1: /@types/http-cache-semantics/4.0.1:
resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
dev: false dev: false
@ -603,12 +601,6 @@ packages:
- supports-color - supports-color
dev: false dev: false
/bootstrap/5.2.0:
resolution: {integrity: sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==}
peerDependencies:
'@popperjs/core': ^2.11.5
dev: false
/brace-expansion/1.1.11: /brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies: dependencies:
@ -919,18 +911,15 @@ packages:
dependencies: dependencies:
esutils: 2.0.3 esutils: 2.0.3
/dotenv/16.0.2:
resolution: {integrity: sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==}
engines: {node: '>=12'}
dev: true
/ee-first/1.1.1: /ee-first/1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false 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: /emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@ -1178,18 +1167,17 @@ packages:
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
dev: false 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: /file-entry-cache/6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
dependencies: dependencies:
flat-cache: 3.0.4 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: /fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1505,17 +1493,6 @@ packages:
/isexe/2.0.0: /isexe/2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 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: /jquery/3.6.0:
resolution: {integrity: sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==} resolution: {integrity: sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==}
dev: false dev: false
@ -1553,6 +1530,11 @@ packages:
json-buffer: 3.0.1 json-buffer: 3.0.1
dev: false dev: false
/kleur/4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
dev: false
/kuler/2.0.0: /kuler/2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
dev: false dev: false
@ -2224,20 +2206,6 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true 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: /table/6.8.0:
resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==} resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}

View File

@ -1,23 +1,19 @@
import createError from "http-errors";
import express, { NextFunction, Request, Response } from "express";
import path from "path";
import cookieParser from "cookie-parser"; import cookieParser from "cookie-parser";
import fs from "fs" import express, { NextFunction, Request, Response } from "express";
import newlog from "./config/winston.js" import createError from "http-errors";
import * as statusTools from "./tools/status.js" import morgan from "morgan";
import * as settingsTools from "./tools/settingsTools.js" import path from "path";
import cronTools from "./tools/cronTools.js"
import webdav from "./services/webdavService.js";
import apiRouter from "./routes/api.js"
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
import logger from "./config/winston.js";
import apiV2Router from "./routes/apiV2.js";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const app = express(); const app = express();
app.set("port", process.env.PORT || 3000);
// app.use( // app.use(
// logger("dev", { // logger("dev", {
// skip: function (req, res) { // 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.json());
app.use(express.urlencoded({ extended: false })); app.use(express.urlencoded({ extended: false }));
app.use(cookieParser()); app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public"))); app.use(express.static(path.join(__dirname, "public")));
app.use("/api", apiRouter); app.use("/v2/api/", apiV2Router);
/* /*
----------------------------------------------------------- -----------------------------------------------------------
Error handler Error handler
@ -54,46 +53,4 @@ app.use((err: any, req: Request, res: Response, next: NextFunction) => {
}); });
/*
-----------------------------------------------------------
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; export default app;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import * as hassioApiTools from "./hassioApiTools.js"; import { publish_state } from "../services/homeAssistantService.js";
import logger from "../config/winston.js" import logger from "../config/winston.js"
import { type Status } from "../types/status.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); const old_state_str = JSON.stringify(status);
if(old_state_str !== JSON.stringify(new_state)){ if(old_state_str !== JSON.stringify(new_state)){
status = new_state; status = new_state;
hassioApiTools.publish_state(status); publish_state(status);
} }
} }

View File

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

View File

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

View File

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

View File

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